Python中的魔术方法
所谓魔法函数(Magic Methods),是Python的一种高级语法,允许你在类中自定义函数,并绑定到类的特殊方法中。比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用__str__()函数,并返回相应的结果。
Python 的类以其神奇的方法而闻名,通常称为 dunder(双下划线)方法。下面先列举Python里面的魔术方法,挑一些常用的魔术方法进行学习。
魔术方法索引
二元操作符:
+ object.__add__(self, other)
- object.__sub__(self, other)
* object.__mul__(self, other)
// object.__floordiv__(self, other)
/ object.__div__(self, other)
% object.__mod__(self, other)
** object.__pow__(self, other[, modulo])
<< object.__lshift__(self, other)
>> object.__rshift__(self, other)
& object.__and__(self, other)
^ object.__xor__(self, other)
| object.__or__(self, other)
扩展二元操作符:
+= object.__iadd__(self, other)
-= object.__isub__(self, other)
*= object.__imul__(self, other)
/= object.__idiv__(self, other)
//= object.__ifloordiv__(self, other)
%= object.__imod__(self, other)
**= object.__ipow__(self, other[, modulo])
<<= object.__ilshift__(self, other)
>>= object.__irshift__(self, other)
&= object.__iand__(self, other)
^= object.__ixor__(self, other)
|= object.__ior__(self, other)
一元操作符:
- object.__neg__(self)
+ object.__pos__(self)
abs() object.__abs__(self)
~ object.__invert__(self)
complex() object.__complex__(self)
int() object.__int__(self)
long() object.__long__(self)
float() object.__float__(self)
oct() object.__oct__(self)
hex() object.__hex__(self)
round() object.__round__(self, n)
floor() object__floor__(self)
ceil() object.__ceil__(self)
trunc() object.__trunc__(self)
比较函数:
< object.__lt__(self, other)
<= object.__le__(self, other)
== object.__eq__(self, other)
!= object.__ne__(self, other)
>= object.__ge__(self, other)
> object.__gt__(self, other)
类的表示、输出:
str() object.__str__(self)
repr() object.__repr__(self)
len() object.__len__(self)
hash() object.__hash__(self)
bool() object.__nonzero__(self)
dir() object.__dir__(self)
sys.getsizeof() object.__sizeof__(self)
类容器:
len() object.__len__(self)
self[key] object.__getitem__(self, key)
self[key] = value object.__setitem__(self, key, value)
del[key] object.__delitem__(self, key)
iter() object.__iter__(self)
reversed() object.__reversed__(self)
in操作 object.__contains__(self, item)
字典key不存在时 object.__missing__(self, key)
常用魔术方法
重头戏来啦!22个常用的魔术方法,一起学起来~
__init__
这个方法很常见。它是一个类初始化器。 每当创建一个类的实例时,都会调用其 __init__()方法。 例如:
class GetTest(object):
def __init__(self):
print('Greetings!!')
def another_method(self):
print('I am another method which is not automatically called')
可以看到在实例在创建后会立即调用 __init__。 你还可以在初始化期间将参数传递给类。像这样:
class GetTest(object):
def __init__(self, name):
print('Greetings!! {0}'.format(name))
def another_method(self):
print('I am another method which is not automatically called')
__getitem__
在类中实现 __getitem__ 允许其实例使用[](索引器)运算符。这是一个例子:
class GetTest(object):
def __init__(self):
self.info = {
'name':'Yasoob',
'country':'Pakistan',
'number':12345812
}
def __getitem__(self,i):
return self.info[i]
>>> foo = GetTest()
>>> foo['name']
'Yasoob'
>>> foo['number']
12345812
如果没有__getitem__方法,我们会遇到以下错误:
>>> foo['name']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'GetTest' object has no attribute '__getitem__'
__del__
__del__()是delete的缩写,这是析构魔术方法。当一块空间没有了任何引用时 默认执行__del__回收这个类地址,一般我们不自定义__del__
- 触发时机:当对象被内存回收的时候自动触发,有下面两种情况:
- 页面执行完毕回收所有变量
- 当多个对象指向同一地址,所有对象被del的时候
- 功能:对象使用完毕后资源回收
- 参数:一个self接受对象
- 返回值:无
__del__()方法,不需要我们手动调用。
来看例子:现在我们有一个类,在__del__()中打印一些信息,以便我们看到__del__()的调用时机。
class Cat:
def eat(self):
print("我嘎嘎能吃")
def __del__(self):
print("ohohoh,我被销毁了")
现在我们对类实例化:
print("<=======start======>")
cat1 = Cat()
print("<=======ends======>")
程序的输出为:
<=======start======>
<=======ends======>
ohohoh,我被销毁了
出现上面现象的原因是在没有手动执行del的情况下,程序执行结束后自动触发析构方法。
继续,现在加一个实例化,并且我们delcat1:
cat1 = Cat()
cat2 = cat1
print("<=======start======>")
del cat1
print("<=======ends======>")
程序的输出为:
<=======start======>
<=======ends======>
ohohoh,我被销毁了
既然我们在start与end中间删除了cat1,为啥程序还是在执行完后触发析构方法?这主要是因为cat2还在继续占用cat1的内存地址,所以会在cat2执行完毕后触发析构方法。
只要同时删除cat1和cat2,内存地址没有指向的值,这块内存被释放就会触发析构方法:
cat1 = Cat()
cat2 = cat1
print("<=======start======>")
del cat1
del cat2
print("<=======ends======>")
程序的输出为:
<=======start======>
ohohoh,我被销毁了
<=======ends======>
注意:如果这个对象产生了循环引用,并且实现了__del__方法,那么这个对象将得不到释放,从而产生内存泄漏。这里有必要去学习一下Python的内存管理机制。
如果学过其他语言,比如C++,相信对析构有更多的理解~
__call__
__call__()方法可以让类的实例具有类似于函数的行为,这进一步模糊了函数和对象之间的概念。
- 触发时机:把对象当作函数调用的时候自动触发
- 功能:模拟函数化操作
- 参数:参数不固定,至少一个self参数
- 返回值:看需求
其使用方式为:对象后面加括号,触发执行。即:对象() 或者 类()()
class A(object):
def __call__(self, *args, **kwargs):
print('call....')
a = A()
a() # 自动调用__call__()
来看个例子:使用__call__()方法实现斐波那契数列
class Fibonacci(object):
def __call__(self, num):
a, b = 1, 1
lst = []
if num <= 2:
lst.append(a)
lst.append(b)
else:
for i in range(num):
lst.append(a)
a, b = b, a + b
return lst
>>> f = Fibonacci()
>>> print(f(5))
[1, 1, 2, 3, 5]
__slots__
Python是一门动态语言,这使得我们可以在程序运行的时候给对象绑定新的属性或方法,这就是动态语言的灵活性。
我们先定义一个类:
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
然后创建一个实例并动态为这个实例绑定一个属性和方法,但是为实例绑定的属性和方法对另一个实例并不起作用,如果需要应用在多个实例上,则需要将其绑定到类上:
from types import MethodType
def fly(self):
print('I can fly~')
>>> p1 = Person('王大锤', 18)
>>> p2 = Person('爱丽丝', 16)
>>>
>>> p1.fly = MethodType(fly, p1)
>>> p1.fly()
I can fly~
>>>
>>> p2.fly() # p1绑定的属性和方法对p2无效
Traceback (most recent call last):
File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-31-07b12cf8c526>", line 1, in <module>
p2.fly()
AttributeError: 'Person' object has no attribute 'fly'
动态绑定确实方便,但是如果我们需要限定自定义类型的对象只能绑定某些属性要怎么办呢?__slots__可以解决这个需求。
定义类的时候,可以定义一个特殊的__slots__变量,来限制该类实例能添加的属性:
class Person(object):
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
实例就不能再添加不在限定范围内的属性了:
>>> p = Person('陆七岁', 7)
>>> p._gender = '男'
>>> p._gender
'男'
>>> p._school = 'Q小学'
Traceback (most recent call last):
File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-36-c90af696bffa>", line 1, in <module>
p._school = 'Q小学'
AttributeError: 'Person' object has no attribute '_school'
但要注意的是,限制只是针对实例,类本身并不会被限制。怎么理解能?就是我们仍然可以通过类去添加属性或方法:
>>> Person._school = 'Q小学'
>>> Person._school
'Q小学'
>>> p1._school
Traceback (most recent call last):
File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-38-f12357d4471f>", line 1, in <module>
p1._school
AttributeError: 'Person' object has no attribute '_school'
总结:
- __slots__是针对类实例的限制,要添加属性或方法仍可以通过类去添加;
- __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。
__str__
__str__()方法可以改变对象的字符串显示。在打印某个对象的时候,会调用这个对象的__str__方法,打印这个方法的返回值。
- 触发时机:使用print(对象)或str(对象)时触发。
class Cat:
def __init__(self, name, sex):
self.name = name
self.sex = sex
def __str__(self):
return f"我是一只可爱的小{self.sex}猫咪,我的名字是{self.name}"
>>> cat = Cat("小白", "公")
>>> print(cat)
我是一只可爱的小公猫咪,我的名字是小白
__repr__
__repr__()方法可以改变对象的字符串显示。__repr__魔术方法是用来表述某个对象在内存中的展示形式。如果在终端直接输入一个对象,然后按回车,那么将会执行这个对象的__repr__方法。
- 此方法是__str__()的“备胎”,如果找不到__str__()就会找__repr__()方法。
- %r默认调用的是__repr__()方法,%s调用__str__()方法
- repr()方法默认调用__repr__()方法
class A(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
msg = 'name:{},age:{}'.format(self.name, self.age)
return msg
def __repr__(self):
msg = 'name--->{},age--->{}'.format(self.name, self.age)
return msg
>>> a = A('za', 34)
>>> print('%s' % a)
name:za, age:34
>>> print('%r' % a) # 用 %r,默认调用__repr__()方法
name-->za, age-->34
>>> print(a) # 有__str__()方法就会调用__str__()方法,没有就调用__repr__()方法
name:za, age:34
>>> print(repr(a)) # repr()方法默认调用__repr__()方法
name-->za, age-->34
注意:如果将几个对象扔到一个容器中(比如:列表),那么在打印这个容器的时候,会依次调用这个容器中的元素的__repr__方法。如果没有实现这个__repr__方法,那么得到的将是一个类名+地址的形式,这种形式的是不好理解的。
__new__
__new__()是实例化魔术方法。先触发__new__才会触发__init__。
- 触发时机: 在实例化对时触发
- 参数:至少一个cls 接收当前类
- 返回值:必须返回一个对象实例
- 作用:实例化对象
Object类底层实现,其他类继承了 Object的 __new__才能够实现实例化对象。
第一个例子:查看__new__方法的执行时机。
class Person(object):
def __init__(self):
print('__init__(): 我也被调用啦~')
def __new__(cls, *args, **kwargs): # 重写后,不再创建对象
print('__new__(): 哈哈我被调用啦~')
>>> per = Person()
__new__(): 哈哈我被调用啦~
>>> print(per)
None
None说明没有创建对象,因为我们重写了__new__方法,__new__方法不再具有创建对象的功能,只有打印的功能。
第二个例子:调用父类的__new__方法,创建当前对象。
class Person(object):
def __init__(self):
print('__init__(): 我也被调用啦~')
def __new__(cls, *args, **kwargs):
print('__new__(): 哈哈我被调用啦~')
ret = super().__new__(cls) # 调用父类object的__new__方法创建对象
return ret
>>> per = Person()
__new__(): 哈哈我被调用啦~
__init__(): 我也被调用啦~
>>> print(per)
<__main__.Person object at 0x0000020FA3892848>
__eq__
说到__eq__()魔法方法,就必须提到Python中的is和==。先来看看这两者的区别:
- is 比较两个对象的 id 值是否相等,是否指向同一个内存地址;
- == 比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了。
==在比较类时,会默认调用object.__eq__方法,默认比较两个对象的地址(id)。
第一个例子:list1和list2的值相同,但id不同,来看看is和==的区别。
>>> list1 = [1, 2, 3]
>>> list2 = [1, 2, 3]
>>> print(id(list1))
1759352803720
>>> print(id(list2))
1759352804232
>>> print(list1 == list2)
True
>>> print(list1 is list2)
False
第二个例子:类的比较:
class Cat:
def __init__(self, name, sex):
self.name = name
self.sex = sex
>>> c1 = Cat('小白', 2)
>>> c2 = Cat('小白', 2)
>>> print(c1.__dict__)
{'name': '小白', 'sex': 2}
>>> print(c2.__dict__)
{'name': '小白', 'sex': 2}
>>> print(c1 == c2) # ==比较时默认调用object.__eq__方法,默认比较两个对象的地址
False
>>> print(c1 is c2)
False
重写__eq__()方法:
class Cat:
def __init__(self, name, sex):
self.name = name
self.sex = sex
def __eq__(self, other):
return self.__dict__ == other.__dict__
>>> c1 = Cat('小白', 2)
>>> c2 = Cat('小白', 2)
>>> print(c1.__dict__)
{'name': '小白', 'sex': 2}
>>> print(c2.__dict__)
{'name': '小白', 'sex': 2}
>>> print(c1 == c2) # ==比较时默认调用object.__eq__方法,默认比较两个对象的地址
True
>>> print(c1 is c2)
False
__hash__
哈希也翻译作散列。Hash算法是将一个不定长的输入,通过哈希函数变换成一个定长的输出,即哈希值。这种哈希变换是一种单向运算,具有不可逆性即不能根据哈希值还原出输入信息。常见的Hash算法有:SM3、MD5、SHA-1等。
Hash主要应用在数据结构以及密码学领域。在不同的应用场景下,Hash函数的选择也会有所侧重。比如在管理数据结构时,主要要考虑运算的快速性。
在Python中有内置的哈希函数hash(),返回一个对象的哈希值。示例代码如下 :
>>> hash("djifc")
-2043476019875189555
>>> hash([1,2])
Traceback (most recent call last):
File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-6-9ce67481a686>", line 1, in <module>
hash([1,2])
TypeError: unhashable type: 'list'
必须要注意:hash()只能输入数字或者字符串,不能直接用于 list、set、dictionary。
在Python中set集合要求数据类型是可哈希的,因为set集合会默认调用对象的__hash__函数进行快速查询,如果找到了则调用对象的__eq__判断两个是是否相同,如果相同则不添加。这保证了数据的唯一性(自动去重功能)。
>>> sets = {1, 'v', [1, 2, 3, 32]}
>>> print(set)
Traceback (most recent call last):
File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-7-382668131d05>", line 1, in <module>
sets = {1, 'v', [1, 2, 3, 32]}
TypeError: unhashable type: 'list'
那么元组是不是可哈希的呢?
>>> hash((1, 2, 3, 32))
485696758965239209
>>> sets = {1, 'v', (1, 2, 3, 32)}
>>> print(set)
<class 'set'>
所以,元组是可哈希的!并且可以使用hash()函数返回元组的哈希值!这是因为Python中元组不可更改!
此外,dict数据结构的key也必须是可哈希的,因为dict是无序的因此通过key的hash算法来快速查询,节约时间。
十万个为什么来了!类可以算哈希值吗?object的哈希值是多少?
class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
>>> hash(People)
-9223371873168924114
>>> hash(object)
8795472504755
>>> id(object) / 16
8795472504755.0
相信你已经对于上面的两个问题有了答案。此外,可以看到object的哈希值是其id的1/16。
问题继续,自定义的类可以添加到集合中吗?
>>> person1 = People('zz', 23)
>>> person2 = People('zs', 23)
>>> sets = {person1, person2}
>>> sets
{<__main__.People at 0x261c7a6ed48>, <__main__.People at 0x261c7a6ed88>}
所以,自定义的类其实时可哈希的,所以类可以添加到集合中。那么,自定义对象添加到集合中,怎么才叫同一个对象?(要去重!)自定义对象添加到集合中,一般认为两个对象的属性值相同就是同一个对象
自定义对象添加到集合中,我们一般认为两个对象的属性值相同就是同一个对象。因此需要我们手动复写__eq__方法和__hash__方法。
class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __hash__(self):
return hash(self.name) + hash(self.age)
>>> person1 = People('zz', 23)
>>> person2 = People('zz', 23)
>>> set1 = {person1}
>>> set1.add(person2)
>>> print(set1)
{<__main__.People object at 0x00000261C7A8C7C8>}
需要注意,如果只定义了__eq__方法,没有定义__hash__方法,__hash__方法会隐式设置成None,在自定义类中,如果没有实现__eq__()和__hash__()方法,会继承object的__eq__()方法和__hash__()方法。
class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.__dict__ == other.__dict__
>>> person1 = People('zz', 23)
>>> person2 = People('zz', 23)
>>> set1 = {person1}
Traceback (most recent call last):
File "D:\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 3331, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-23-af8600420087>", line 3, in <module>
set1 = {person1}
TypeError: unhashable type: 'People'
__bool__
- 触发时机:使用bool(对象)的时候自动触发
- 功能:强转对象
- 参数:一个self接受当前对象
- 返回值:必须是布尔类型
class Cat:
def __init__(self, name, sex):
self.name = name
self.sex = sex
def __bool__(self):
return True
>>> cat = Cat("小白", "公")
>>> print(bool(cat))
True
__add__
__add__与__radd__都是做加法,只是加法的顺序不一样,会调用不同的魔法函数。
- 触发时机:使用对象进行运算相加的时候自动触发
- 功能:对象运算
- 参数:两个对象参数
- 返回值:运算后的值
对象在加号的左侧时,自动调用__add__()函数。
class Sum:
def __init__(self, num):
self.num = num
def __add__(self, other): # 对象在加号+的左侧时,自动触发
return self.num + other
>>> value = Sum(7)
>>> res = value + 8
>>> print(res)
15
对象在加号的右侧时,自动调用__radd__()函数。
class Sum():
def __init__(self, num):
self.num = num
def __radd__(self, other):
return self.num + other
>>> value = Sum(7)
>>> res = 10 + value
>>> print(res)
17
思考一哈,下面的程序的结果是多少?
class Sum1:
def __init__(self, num):
self.num = num
def __add__(self, other):
return self.num + other
class Sum2:
def __init__(self, num):
self.num = num
def __radd__(self, other):
return self.num * 2 + other
>>> value1 = Sum1(10)
>>> value2 = Sum2(7)
>>> res = value1 + value2
>>> print(res)
分析:首先将res=value1+value2传入Sum1中,得出值res=10+value2,再将res=10+value2传入Sum2中,所以res=14+10=24。
__len__
- 触发时机:使用len对象的时候自动触发
- 功能:用于检测对象中或者类中成员个数
- 参数:一个self接收当前对象
- 返回值:必须是整型
class List:
def __init__(self):
self.num = []
def add(self, x):
self.num.append(x)
def __len__(self):
return len(self.num)
>>> l = List()
>>> l.add(2)
>>> print(len(l))
__dict__
获取类或对象的的内部成员结构。主要用来获取用户自定义的属性,以及这个属性对应的值。返回的是一个字典。
class MyClass():
name1 = "Lsir"
name2 = "Wsir"
name3 = "Zsir"
def task1(self):
print("task1")
def task2(self):
print("tesk2")
def task3(self):
print("task3")
>>> print(MyClass.__dict__)
{'__module__': '__main__', 'name1': 'Lsir', 'name2': 'Wsir', 'name3': 'Zsir', 'task1': <function MyClass.task1 at 0x0000020C16385558>, 'task2': <function MyClass.task2 at 0x0000020C16385D38>, 'task3': <function MyClass.task3 at 0x0000020C16385708>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
和dir函数做一个区分。dir函数返回的是这个对象上拥有的所有属性,包括Python内置的属性和用户自己添加的,并且只是获取属性名字,不会获取这个属性对应的值。
__doc__
获取类或对象内部文档。
class MyClass():
"""
我是一个类,这里说明一些有用的信息
"""
def __init__(self):
pass
print(MyClass.__doc__)
我是一个类,这里说明一些有用的信息
__name__
获取类名或函数名
class Class1:
pass
class MyClass:
def task1(self, func1):
print(func1.__name__)
def func():
print("我是func1函数")
>>> obj = MyClass()
>>> obj.task1(func)
func
>>> obj.task1(Class1)
Class1
__class__
获取当前对象获取的类
class Class1:
pass
>>> obj = Class1()
>>> print(obj.__class__)
<class '__main__.Class1'>
>>> print(obj.__class__.__name__)
Class1
__bases__
获取一个类直接继承的所有父类,返回元组。
class Class1:
pass
class Class2:
pass
class MyClass(Class1, Class2):
pass
>>> print(MyClass.__bases__)
(<class '__main__.Class1'>, <class '__main__.Class2'>)
__getattr__
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
return item
>>> p1.x
4
>>> p1.z
6
>>> p1.n
0
>>> p1.t
't'
实例属性会按照继承关系寻找,如果找不到,就会执行__getattr__()方法,如果没有这个方法,就会抛出AttributeError异常标识找不到属性。
__setattr__
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
return item
def __setattr__(self, key, value):
print(key, value)
# --------------------------------------------------
>>> p1 = Point(4, 5)
x 4
y 5
>>> print(p1.x)
x
>>> print(p1.z)
6
>>> print(p1.n)
0
>>> print(p1.t)
t
# --------------------------------------------------
>>> p1.x = 50
>>> print(p1.x)
x
>>> print(p1.__dict__)
{}
>>> p1.__dict__['x'] = 60
>>> print(p1.__dict__)
{'x': 60}
>>> p1.x
60
实例通过.点号设置属性,例如self.x=x,就会调用__setattr__(),属性要加到实例的__dict__中,就需要自己完成。
setattr()方法,可以拦截堆实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__。
class Base:
n = 200
class A(Base):
z = 100
d = {}
def __init__(self, x, y):
self.x = x
setattr(self, 'y', y)
self.__dict__['a'] = 5
def __getattr__(self, item):
print(item)
return self.d[item]
def __setattr__(self, key, value):
print(key, value)
self.d[key] = value
>>> a = A(4, 5)
x 4
y 5
>>> print(a.__dict__)
{'a': 5}
>>> print(A.__dict__)
A.__dict__
mappingproxy({'__module__': '__main__',
'z': 100,
'd': {'x': 4, 'y': 5},
'__init__': <function __main__.A.__init__(self, x, y)>,
'__getattr__': <function __main__.A.__getattr__(self, item)>,
'__setattr__': <function __main__.A.__setattr__(self, key, value)>,
'__doc__': None})
>>> print(a.x, a.y)
x
y
4 5
>>> print(a.a)
5
__delattr__
class Point:
z = 5
def __init__(self, x, y):
self.x = x
self.y = y
def __delattr__(self, item):
print(item)
p = Point(14, 5)
>>> p = Point(3, 4)
>>> del p.x
x
>>> p.z=15
>>> del p.z
z
>>> del p.Z
Z
>>> print(Point.__dict__)
{'__module__': '__main__', 'z': 5, '__init__': <function Point.__init__ at 0x0000019E93B01318>, '__delattr__': <function Point.__delattr__ at 0x0000019E93B013A8>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
__getattribute__
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
return item
def __getattribute__(self, item):
return item
>>> p1 = Point(4, 5)
>>> print(p1.__dict__)
__dict__
>>> print(p1.x)
x
>>> print(p1.z)
z
>>> print(p1.n)
n
>>> print(p1.t)
t
>>> print(Point.__dict__)
{'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x000001F5EB7063A8>, '__getattr__': <function Point.__getattr__ at 0x000001F5EB706558>, '__getattribute__': <function Point.__getattribute__ at 0x000001F5EB706168>, '__doc__': None}
>>> print(Point.z)
6
实例的所有的属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回值或者抛出一个AttributeError异常。
- 该方法的返回值将作为属性查找的结果。
- 如果抛出AttributeError异常,则会直接调用__getattr__方法,因为属性没有找到,__getattribute__方法中为了避免在该方法中无限递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性。
需要注意的是,除非明确知道__getattrtbute__方法用来做什么,否则不要使用。