Python魔法方法漫游指南:属性访问
2401 views, 2023/10/17 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魔法方法漫游指南
看完文章想吐槽?欢迎留言告诉我!