__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
__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]
__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__)
__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__
可以实现定制打印输出。
__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
__slots__
__slots__
是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)__dict__
属性字典(类的字典是共享的,而每个实例的是独立的)__slots__
:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__
取代实例的__dict__
__slots__
后,__slots__
就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__
中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__
一个不好的地方就是我们不能再给 __slots__
中定义的那些属性名。__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'
__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方法则先执行析构方法,然后执行
__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于__call__
方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
__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
__get__, __set__, __delete__
描述符本质上就是一个新式类,在这个类中至少实现了__get__, __set__, __delete__
中的一个。
上述概念也可以称之为描述符协议。
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
描述符是用来代理另外一个类的属性,描述符只能定义为类的属性,而不能在__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类实例化得到的对象和参数,也就可以使用这个对象和参数实现一些功能。
__get__()
和__set__()
__set__()
要严格遵循下述优先级,优先级由高到底分别是
__getattr__()
类调用类的属性实质上就是在调用类的属性字典里的属性,如果类内存在于描述符同名的属性,那么类内的属性对类属性字典中该属性名对应的属性进行了重写,因此类在调用该方法时调用的是类的属性而不是数据描述符,下面通过一个例子说明这一点。
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__方法
类的实例对所要调用的属性的查找属性是: —>对象属性字典—>类属性字典。
类实例化得到的对象调用的属性在属性字典中找不到时会去类的属性字典找,即使对象对属性进行重新赋值,操作的也是类的属性,而不会在自己的对象属性字典中添加属性。那么,当对象调用的属性是由数据描述符代理的,那么在类中没有同名的类属性时就会去描述符所在的类属性字典中去查找。
下面通过一个例子说明这一点
# 类的定义依然使用上述例子
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这个属性
首先引入一个例子
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在自己的属性字典中添加了属性。
这体现了实例属性的优先级高于非数据描述符,优先级其实就是一种查找顺序。
当属性不存在时,调用该属性(只调用不赋值)时,存在非数据描述符就调用描述符中的__get__
方法,如果不存在描述符也就无法调用非数据描述符中的__get__
方法,如果对象本身所在的类中没有定义__getattr__
方法就报错,如果定义了就触发__getattr__
方法。
通过描述符可以为控制类内属性的行为,比如可以强行控制类内数据属性的类型,初始化时使用的数据类型不是目标数据类型则报错
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)
,这样就弄的很麻烦。可不可以使用一种方法,为类内没一个属性都添加一个类型检测的功能呢?答案是有的,可以通过为类设计装饰器试下这一想法,下面对类的装饰器的使用方法简要介绍一下。
无参装饰器
#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.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'>
上述例子已经展现出描述符的一些特性,或许看起来没什么,但Python中包括@classmethod,@staticmethd,@property甚至是__slots__属性都是基于描述符实现的
__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语句的应用:
__copy__, __deepcopy__
__hash__
在C++中有一种强大的特性叫做操作符重载,可以实现类与类之间的运算与比较,例如两个类的相加,在Python中通过重新定义相应的内置方法可以实现同样的功能
下面从两个角度进行介绍:比较操作符,运算操作符
__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对象 |
__add__, __sub__, __mul__, __divmod__
__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