Python中类的内置方法

    • __getattr__ __setattr__ __delattr__
    • __getattribute__
    • __getitem__ __setitem__ __delitem__
    • __repr__ __str__ __format__
      • __str__
      • __repr__
      • __format__
    • __slots__
    • __del__
    • __call__
    • __iter__ __next__
    • __get__ __set__ __delete__
      • 1 描述符概念
      • 2 描述符的作用
      • 3 描述符种类
      • 4 描述符注意事项
        • 41 描述符优先级之类属性数据描述符
        • 42 描述符优先级之数据描述符实例属性
        • 43 描述符优先级之实例属性非数据描述符
        • 44 描述符优先级之非数据描述符属性不存在
      • 5 描述符应用
        • 51 简单描述符应用
      • 52 类装饰器
      • 53
      • 54 描述符的威力
    • __enter__ __exit__
    • __copy__ __deepcopy__
    • __hash__
    • 操作符重载
      • 1 比较操作符
      • 2 运算操作符
      • 3 关系操作符

1. __getattr__, __setattr__, __delattr__

调用对象的一个不存在的属性时会触发__getattr方法
删除对象的一个属性的时候会触发__delattr__方法
设置对象属性和删除对象属性会触发__setattr____delattr__ 方法,但要注意的是,在调用这两个方法时,方法内部必须操作类的属性字典,否则会造成无递归

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print('run __getattr__')


    def __setattr__(self, key, value):
        print('run __setattr__')
        # self.key=value # 增加/修改类属性的操作会触发__setattr__方法,这个方法在__setattr__方法内,造成无限递归
        # self.__dict__[key]=value # 使用这种方法会完成增加/修改类属性的操作

    def __delattr__(self, item):
        print('run __delattr__')
        # del self.item # 造成无限递归
        self.__dict__.pop(item)

#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__)  # 因为重写了__setattr__,凡是赋值操作都会触发它的运行,除非直接操作属性字典,否则永远无法完成赋值
f1.z=3
print(f1.__dict__)

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3 #我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx

2. __getattribute__

前面介绍了__getattr__,那么__getattribute__与之有什么关系呢?


class Foo:
    def __init__(self,x):
        self.x = x

    def __getattribute__(self, item):
    print('不管属性[%s]是否存在,我都会执行' % item)

f1=Foo(10)
f1.x
f1.xxxxxx

>>不管属性[x]是否存在,我都会执行
不管属性[xxxxxx]是否存在,我都会执行

__getattr____getattribute__共存时,执行顺序是怎样的?
#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

class Foo:
    def __init__(self,x):
        self.x=x

    def __getattr__(self, item):
        print('执行__getattr__, 操作的属性[%s]' % item)
        # return self.__dict__[item]

    def __getattribute__(self, item):
        print('不管属性[%s]是否存在, __getattribute__都会执行' % item)
        # raise AttributeError('哈哈')

f1=Foo(10)
f1.x
f1.xxxxxx

# 没有抛出异常的执行结果
>>不管属性[x]是否存在, __getattribute__都会执行
不管属性[xxxxxx]是否存在, __getattribute__都会执行

#抛出异常的执行结果
>>不管属性[x]是否存在, __getattribute__都会执行
执行__getattr__, 操作的属性[x]
不管属性[xxxxxx]是否存在, __getattribute__都会执行
执行__getattr__, 操作的属性[xxxxxx]

3. __getitem__, __setitem__, __delitem__

将对类属性的操作伪装成字典形式的操作

class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print('run __getitem__')
        return self.__dict__[item]

    def __setitem__(self, key, value):
        print('run __setitem__')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('run __delitem__')
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print('run __delattr__')
        self.__dict__.pop(item)

f1=Foo('jack')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='Joe'
print(f1.__dict__)

4. __repr__, __str__, __format__

__str__

假定f为一个类的实例,print(f)等价于调用str(f),等价于调用f.__str__()
使用f.__str__()可以定制打印方法

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return 'name is %s, age is %s' % (self.name, self.age)

f = Foo('Jack', 18)
print(f)

>>name is Jack, age is 18

__repr__

__repr____str__实现的功能是一样的,如果两个共存,只会调用__str__,在没有定义__str__的情况下调用__repr__
此外在终端中__repr__可以实现定制打印输出。
Python中类的内置方法_第1张图片

__format__

date_dic={
    'ymd':'{0.year}:{0.month}:{0.day}',
    'dmy':'{0.day}/{0.month}/{0.year}',
    'mdy':'{0.month}-{0.day}-{0.year}',
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec='ymd'
        fmt=date_dic[format_spec]
        return fmt.format(self)

d1=Date(2016,12,29)
print(format(d1, 'dmy'))
print('{:mdy}'.format(d1))
print(date_dic['mdy'].format(d1))

>>29/12/2016
12-29-2016
12-29-2016

5. __slots__

  1. __slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
  2. 引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
  3. 为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
    当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
    实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
  4. 注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
    只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
    关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
class Bar:
    __slots__ = ['x', 'y']

n = Bar()
n.x, n.y = 1, 2
n.z = 3  # 报错

>>AttributeError: 'Bar' object has no attribute 'z'

6. __del__

__del__称作析构方法
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为在Python中,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
在程序执行结束之后,执行此方法

class Foo:

    def __del__(self):
        print('run __del__')

f1=Foo()
# del f1
print('------->')

>>------->
run __del__
#程序中没有调用del方法,在整个程序执行结束之后调用__del__方法
class Foo:

    def __del__(self):
        print('run __del__')

f1=Foo()
del f1
print('------->')

>>run __del__
------->
# 调用del方法则先执行析构方法,然后执行

7. __call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于__call__方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 执行 __init__
obj()       # 执行 __call__

8. __iter__, __next__

使用__iter__, __next__实现迭代器协议

class Foo:
    def __init__(self,start,stop):
        self.num=start
        self.stop=stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.num >= self.stop:
            raise StopIteration
        n=self.num
        self.num+=1
        return n

for i in Foo(1,5):
    print(i)

>>1
2
3
4

9. __get__, __set__, __delete__

9.1 描述符概念

描述符本质上就是一个新式类,在这个类中至少实现了__get__, __set__, __delete__中的一个。
上述概念也可以称之为描述符协议。
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

9.2 描述符的作用

描述符是用来代理另外一个类的属性,描述符只能定义为类的属性,而不能在__init__函数中定义。

class Foo(object):
    def __get__(self, instance, owner):
        print('run __get__')

    def __set__(self, instance, value):
        print('run __set__')

    def __delete__(self, instance):
        print('run __delete__')


class Bar(object):
    def __init__(self, n):
        self.x = Foo()

b1 = Bar(9)

# Foo类就是一个描述符,在Bar类中的`__init__`方法中定义了描述符Foo,这样并没有触发描述符的三种操作,也就是上文提到的不可以在`__init__`中定义描述符
class Foo(object):
    def __get__(self, instance, owner):
        print('run __get__')

    def __set__(self, instance, value):
        print('instance: %s, value: %s' % (instance, value))
        print('run __set__')

    def __delete__(self, instance):
        print('run __delete__')


class Bar(object):
    x = Foo()

    def __init__(self, n):
        self.x = n

b1 = Bar(9)

>>instance: <__main__.Bar object at 0x000001C45102D7B8>, value: 9
>>run __set__

# 可以看到在类中定义描述符,并对描述符进行了赋值操作触发了描述符的`__set__`方法

上面这个例子中还体现出了代理 的概念,x作为Bar类的属性,其功能全部交由Foo实现,这就是x被Foo代理了,从打印结果可以看出Foo接收到了其代理的Bar类实例化得到的对象和参数,也就可以使用这个对象和参数实现一些功能。

9.3 描述符种类

  1. 数据描述符:至少实现了__get__()__set__()
  2. 非数据描述符:没有实现__set__()

9.4 描述符注意事项

  • 描述符本身应该定义成新式类,被代理的类也应该是新式类
  • 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  • 要严格遵循下述优先级,优先级由高到底分别是

    • 类属性
    • 数据描述符
    • 实例属性
    • 非数据描述符
    • 属性不存在触发__getattr__()

9.4.1 描述符优先级之[类属性>数据描述符]

类调用类的属性实质上就是在调用类的属性字典里的属性,如果类内存在于描述符同名的属性,那么类内的属性对类属性字典中该属性名对应的属性进行了重写,因此类在调用该方法时调用的是类的属性而不是数据描述符,下面通过一个例子说明这一点。

class Foo(object):
    def __get__(self, instance, owner):
        print('run __get__')

    def __set__(self, instance, value):
        print('instance: %s, value: %s' % (instance, value))
        print('run __set__')

    def __delete__(self, instance):
        print('run __delete__')


class Bar(object):
    x = Foo()

    def __init__(self, m):
        self.x = m

b1 = Bar(7)
print(Bar.__dict__)
Bar.x = 1
print(Bar.__dict__)

>>run __set__
>>'x': <__main__.Foo object at 0x000001308921D748>
>>'x': 1

# 上述程序执行结果对字典的打印只截取了与属性 x 相关的部分,可以看出在Bar类中没有x属性时,Bar调用的 x 属性时类Foo的一个对象
# 在为Bar类中 x 属性重新赋值后,Bar类有了自己的 x 属性,也就不会去调用Foo类中的x属性,也就不会触发Foo中的__set__方法

9.4.2 描述符优先级之[数据描述符>实例属性]

类的实例对所要调用的属性的查找属性是: —>对象属性字典—>类属性字典。
类实例化得到的对象调用的属性在属性字典中找不到时会去类的属性字典找,即使对象对属性进行重新赋值,操作的也是类的属性,而不会在自己的对象属性字典中添加属性。那么,当对象调用的属性是由数据描述符代理的,那么在类中没有同名的类属性时就会去描述符所在的类属性字典中去查找。
下面通过一个例子说明这一点

# 类的定义依然使用上述例子
b1 = Bar(7)
b1.x
b1.x = 8

>>instance: <__main__.Bar object at 0x000001F00263D7F0>, value: 7
run __set__
run __get__
instance: <__main__.Bar object at 0x000001F00263D7F0>, value: 8
run __set__

# 可以看出在执行类实例化时执行了Bar类的__set__方法,在实例对象调用该属性时调用了Bar类的__get__方法,对x属性进行重新赋值的操作时,再一次触发了Bar类的__set__方法
# 如果打印b1.__dict__,可以返现b1的属性字典中没有x这个属性
# 如果将Bar类中 x = Foo()这个描述符去掉,替换为一个不同类属性,那么再次打印b1的属性字典会发现b1的属性字典中添加了x这个属性

9.4.3 描述符优先级之[实例属性>非数据描述符]

首先引入一个例子

class Foo(object):
    def __get__(self, instance, owner):
        print('run __get__')


class Bar(object):
    x = Foo()
    def __init__(self, m):
        self.x = m

b1 = Bar(7)
b1.x = 8

>>{'x': 8}

从上述例子可以看出由于描述符中没有__set__方法,在对象对x属性进行赋值操作中无法触发描述符中不存在的__set__方法, 这导致结果是b1在自己的属性字典中添加了属性。
这体现了实例属性的优先级高于非数据描述符,优先级其实就是一种查找顺序。

9.4.4 描述符优先级之[非数据描述符>属性不存在]

当属性不存在时,调用该属性(只调用不赋值)时,存在非数据描述符就调用描述符中的__get__方法,如果不存在描述符也就无法调用非数据描述符中的__get__方法,如果对象本身所在的类中没有定义__getattr__方法就报错,如果定义了就触发__getattr__方法。

9.5 描述符应用

9.5.1 简单描述符应用

通过描述符可以为控制类内属性的行为,比如可以强行控制类内数据属性的类型,初始化时使用的数据类型不是目标数据类型则报错

class Typed(object):
    def __init__(self, val, typ):
        self.val = val
        self.typ = typ

    def __get__(self, instance, owner):
        print('instance: [%s], owner: [%s] in __get__' % (instance, owner))
        if instance is None:
            return self

        return instance.__dict__[self.val]

    def __set__(self, instance, value):
        print('instance: [%s], value: [%s] in __set__' % (instance, value))
        if not isinstance(value, self.typ):
            raise TypeError('Type of %s should be %s' % (self.val, self.typ))
        instance.__dict__[self.val] = value

    def __delete__(self, instance):
        print('instance: [%s]' % instance)
        instance.__dict__.pop(self.val)


class People(object):
    name = Typed('name', str)
    age = Typed('age', int)
    salary = Typed('salary', float)

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary

# 类型没有错误的输出
p1 = People('Jack', 3, 3.0)
>>
instance: [<__main__.People object at 0x000001EA58E6DC50>], value: [Jack] in __set__
instance: [<__main__.People object at 0x000001EA58E6DC50>], value: [3] in __set__
instance: [<__main__.People object at 0x000001EA58E6DC50>], value: [3.0] in __set__

# 类型出错
p1 = People(777, 3, 3.0)
>>
TypeError: Type of name should be <class 'str'>
# 可以看出这里抛出的异常是Typed类中`__set__`中定义的错误输出方式

在描述符简单应用这一节中已经实现了对类属性的定制,但这样仅仅是实现了功能,如果添加属性(例如People类中添加gender属性), 那么就需要在类中再添加一行salary = Typed('salary', float),这样就弄的很麻烦。可不可以使用一种方法,为类内没一个属性都添加一个类型检测的功能呢?答案是有的,可以通过为类设计装饰器试下这一想法,下面对类的装饰器的使用方法简要介绍一下。

9.5.2 类装饰器

无参装饰器


#decorator with no arguments
def deco(cls):
    print('cls decorator')
    return cls

@deco
class People(object):
    def __init__(self, name, age, gender, salary):
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary

p1 = People('J', 18, 'male', 9.9)
>>
cls decorator

有参装饰器

# decorator with arguments
def deco(*args, **kwargs):
    def wrapper(cls):
        for k, v in kwargs.items():
            print('key: %s, value: %s' % (k, v))
        return cls
    return wrapper


@deco(name=str, age=int, gender=str, salary=float)  # People = deco(People)
class People(object):
    def __init__(self, name, age, gender, salary):
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary

p1 = People('J', 18, 'male', 9.9)

>>
key: name, value: <class 'str'>
key: age, value: <class 'int'>
key: gender, value: <class 'str'>
key: salary, value: <class 'float'>

9.5.3

在9.5.1中实现了为类属性限制其类型,但实现方法比较麻烦,无法做到通用,装饰器可以为类添加功能,并通用于这个类,结合这两点,使用装饰器搭配描述符实现类属性类型限制,如下所示

class TypeCheck(object):
    def __init__(self, name, expect_type):
        self.name = name
        self.expect_type = expect_type

    def __get__(self, instance, owner):
        if instance in None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expect_type):
            raise TypeError('%s should be type of %s' % (self.name, self.expect_type))
        instance.__dict__[self.name] = self.expect_type

    def __delete__(self, instance):
        instance.__dict__.pop(self.name)

# decorator with arguments
def deco(*args, **kwargs):
    def wrapper(cls):
        for k, v in kwargs.items():
            setattr(cls, k, TypeCheck(k, v))
        return cls
    return wrapper


@deco(name=str, age=int, gender=str, salary=float) # 添加salary的属性检测
class People(object):
    def __init__(self, name, age, gender, salary):  # 添加salary属性
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary

p1 = People(7, 18, 'male', 9.9)

>>
TypeError: name should be type of <class 'str'>

9.5.4 描述符的威力

上述例子已经展现出描述符的一些特性,或许看起来没什么,但Python中包括@classmethod,@staticmethd,@property甚至是__slots__属性都是基于描述符实现的

10. __enter__, __exit__

使用__enter__, __exit__可以实现with语句,通过下面这个例子简要说明一下这两个函数在with语句中的执行顺序

class Open(object):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('run __enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('run __exit__')
        print('exc_type: [%s]' % exc_type)
        print('exc_val: [%s]' % exc_val)
        print('exc_tb: [%s]' % exc_tb)
        return True

with Open('a.txt') as f:
    print('test start...')
    print(f.name)
    print(dfsdfasdfasdasfsa)
    print('anything else')

print('---end---')

>>
run __enter__
test start...
a.txt
run __exit__
exc_type: [<class 'NameError'>] # 异常类型
exc_val: [name 'dfsdfasdfasdasfsa' is not defined] # 异常内容
exc_tb: [0x00000170FAD639C8>] # traceback
---end---

# 执行with语句触发`__enter__`,拿到返回值赋值给f,执行with里面的代码块,如果没有异常,代码块执行结束后执行`__exit__`,exit的三个返回值都为None
# 如果with中的语句执行过程中发生异常,则后续程序不再执行,exit语句中如果最后return True则with整个代码块后的语句还可以继续执行,如果没有return True那么则直接抛出异常终止程序。

with语句的应用:

  • 使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
  • 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,无须再去关心这个问题。

11. __copy__, __deepcopy__

12. __hash__

13. 操作符重载

在C++中有一种强大的特性叫做操作符重载,可以实现类与类之间的运算与比较,例如两个类的相加,在Python中通过重新定义相应的内置方法可以实现同样的功能
下面从两个角度进行介绍:比较操作符,运算操作符

13.1 比较操作符

__gt__, __lt__, __ge__, __le__, __eq__

function name description
__gt__(self, other) 判断self对象是否大于other对象
__lt__(self, other) 判断self对象是否小于other对象
__ge__(self, other) 判断self对象是否大于等于other对象
__le__(self, other) 判断self对象是否小于等于other对象
__eq__(self, other) 判断self对象是否等于other对象

13.2 运算操作符

__add__, __sub__, __mul__, __divmod__

13.3 关系操作符

__and__, __or__, __xor__

class A:
    def __init__(self, val):
        self.val = val

    def __or__(self, other):
        return bool(self.val | other.val)

    def __and__(self, other):
        return bool(self.val & other.val)

    def __xor__(self, other):
        return bool(self.val ^ other.val)


a1 = A(1)
a2 = A(0)

print(a1 | a2)
print(a1 & a2)
print(a1 ^ a2)

>>
True
False
True

你可能感兴趣的:(python)