Python魔法方法漫游指南:属性访问
1771 views, 2021/07/15 updated Go to Comments
Python 提供了几种常见的访问控制的魔法方法,其作用有点类似其他语言中对属性的封装(定义私有属性,通过公有的 getter/setter 访问)。
更通俗一点,这类魔法方法可以接管“点号”运算。每当你访问类的某个属性时,就将调用这类方法。
此类方法有如下几个:
__getattr__
:在实例中找不到属性时调用。__getattribute__
:无论是否找到属性均调用。__setattr__
:对属性赋值时调用。__delattr__
:删除属性时调用。
来看个例子:
class Foo: def __init__(self, a=10): self.a = a def __getattr__(self, name): return f'Invoke getattr. name: {name}' f = Foo() a = f.a print(a) # 输出: # 10 b = f.b print(b) # 输出: # Invoke getattr. name: b
当调用一个不存在的属性时,此方法可以给出警告,或者灵活处理 AttributeError
和返回值。
再看看 __getattribute__
的行为:
class Foo: def __init__(self, a=10): self.a = a def __getattribute__(self, name): return f'Invoke getattr. name: {name}' f = Foo() a = f.a print(a) # 输出: # Invoke getattr. name: a b = f.b print(b) # 输出: # Invoke getattr. name: b
不管调用的属性存在与否,__getattribute__
均被调用。运用此方法你可以对属性的返回值做某种定制,比如单位换算、动态计算之类的骚操作。
类似的还有 __setattr__
:
class Foo: def __init__(self, a=10): self.a = a def __setattr__(self, name, value): # 注意千万不能 self.name = value + 1 赋值 # 引发无限递归错误 self.__dict__[name] = value + 1 f = Foo() f.a = 20 f.b = 30 print(f.a, f.b) # 输出: # 21 31
实现了 __setattr__
方法后,相当于所有的 self.name = value
都会变成 self.__setattr__('name', value)
。
注意方法中不能用 self.name = value
进行赋值,会引发无限递归错误,而是要用 self.__dict__[name]
直接修改命名空间中的属性。
文章末尾有对
__dict__
的解释。
最后就是删除属性的 __delattr__
了:
class Foo: def __init__(self, a=10): self.a = a def __delattr__(self, name): print(f'Invoke delattr. name: {name}') self.__dict__.pop(name) f = Foo() print(f.a) # 输出: # 10 del f.a print(f.a) # 输出: # Invoke delattr. name: a # AttributeError: 'Foo' object has no attribute 'a'
你可以想象将这些方法组合运用的强大了,强大到可以写出反直觉的花里胡哨的玩意儿出来。 Python 将灵活性交给你,而你的任务是谨慎使用这些能力,同时达到代码简洁之道。
命名空间
Python 的类具有一个特殊的字典叫 __dict__
,它被称作命名空间,说白了就是一个存放对象所有属性的字典。
对属性的引用被解释器转换为对该字典的查找,比如 a.x
相当于 a.__dict__['x']
。看下面的例子:
class Foo: def __init__(self): self.a = 10 self.b = 20 foo = Foo() print(foo.__dict__) # {'a': 10, 'b': 20} foo.__dict__['c'] = 30 print(foo.__dict__) # {'a': 10, 'b': 20, 'c': 30} print(foo.c) # 30
可以看到在程序运行期间,你可以动态的向 __dict__
中插入新的值,使得对象具有新的属性。
__setattr__
中之所以不能用 self.name = xxx
是因为对属性的赋值会嵌套调用 __setattr__
,从而导致无限递归。
而直接操作 __dict__
则不会产生这个问题,其他几个属性访问的魔法方法也是类似的道理。
本系列文章开源发布于 Github,传送门:Python魔法方法漫游指南
看完文章想吐槽?欢迎留言告诉我!