python的魔法方法2
getattr
我们先看下面的代码
class A:
def __init__(self, age):
self.age = age
o = A(18)
print(o.age)
print("============")
print(o.score)
结果
18
============
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in <module>
print(o.score)
AttributeError: 'A' object has no attribute 'score'
我们发现当我们使用o.age的时候,实际上是在尝试access这个对象的一个属性,但是当这个属性在这个对象不存在的时候,就报错了,AttributeError,告诉咱们,没有这个属性啊。
那attr就是帮我们来做这个默认值的,也就是说,我们访问这个对象不存在的属性,我们不想让他报错了,想让他返回其他东西。
class A:
def __init__(self, age):
self.age = age
def __getattr__(self, name):
print(f"getting:{name}")
raise AttributeError
o = A(18)
print(o.age)
print("============")
print(o.score)
结果
18
============
getting:score
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 13, in <module>
print(o.score)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 7, in __getattr__
raise AttributeError
AttributeError
首先呢,我们定义了attr方法,使其当访问这个对象不存在的属性的时候打印了getting:不存在属性的string,然后又手动抛出了一个AttributeError。当我们使用o.score来访问一个o对象不存在的属性的时候,就会请求到attr上,然后attr会将o.score的score最为string参数传递给attr函数。
比如说,当我们希望有人访问了这个对象不存在的属性的时候不报错,而是返回一个None,当然,这并不是很合理的行为,我们只是用来做演示。
class A:
def __init__(self, age):
self.age = age
def __getattr__(self, name):
print(f"getting:{name}")
return None
o = A(18)
print(o.age)
print("============")
print(o.score)
结果
18
============
getting:score
None
getattribute
这个函数和getattr很像哈,甚至一个就是另一个的缩写,所以python在函数命名上确实有的地方不是很合理,上面的那个attr函数是只有在这个属性不存在的时候才会被调用,而getattribute这个函数,是只要你尝试去读取这个对象的属性,它都会被调用
class A:
def __init__(self, age):
self.age = age
def __getattribute__(self, name):
print(f"getting:{name}")
return name
o = A(18)
print(o.age)
print("============")
print(o.score)
结果
getting:age
age
============
getting:score
score
我们发现不管这个属性有没有,attribute函数都被调用了,当然现在这个attribute函数没什么意义对吧,那我们就写一个稍微有意义的例子
class A:
def __init__(self):
self.data = 'abc'
self.counter = 0
def __getattribute__(self, name):
if name == 'data':
self.counter += 1
return super().__getattribute__(name)
o = A()
print(o.data)
print(o.data)
print(o.counter)
结果
abc
abc
2
从结果上来看,这个data确实被访问了两遍,但是大家一定要特别注意这一行
return super().__getattribute__(name)
当你使用getattribute的时候,如果你想使用它的默认行为,一定要像上面那么些,因为这里如果不小心的话,就会产生一个无限递归,比如刚刚我们学了getattr这个函数对吧,我默认返回一个getattr不就可以了么
class A:
def __init__(self):
self.data = 'abc'
self.counter = 0
def __getattribute__(self, name):
if name == 'data':
self.counter += 1
return getattr(self, name)
o = A()
print(o.data)
print(o.data)
print(o.counter)
这样的话就会产生一个无限递归
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 13, in <module>
print(o.data)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 8, in __getattribute__
self.counter += 1
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
return getattr(self, name)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
return getattr(self, name)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
return getattr(self, name)
[Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded
因为getattr函数又会反过来执行getattribute函数,同时我们也要注意到还有一个递归的地方
self.counter += 1
你要读取self.counter对不对,当你读取他的时候,就调用了getattribute,
比如说你心血来潮的说,想要数一下这个对象里所有的属性被读取的次数
class A:
def __init__(self):
self.data = 'abc'
self.counter = 0
def __getattribute__(self, name):
self.counter += 1
return super().__getattribute__(name)
o = A()
print(o.data)
print(o.data)
print(o.counter)
结果
/Users/yinzhang/PycharmProjects/MyTest/venv/bin/python /Users/yinzhang/PycharmProjects/MyTest/test1.py
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 13, in <module>
print(o.data)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 8, in __getattribute__
self.counter += 1
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
return getattr(self, name)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
return getattr(self, name)
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
return getattr(self, name)
[Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded
Process finished with exit code 1
又无限递归了,因为你每一次+counter的时候,就要读一次counter,然后又要+counter,就一直递归下去了。
setattr
刚刚说完get了,现在就要说set了,当我们尝试去写一个属性的时候,就要用到setattr,他有两个参数,一个key一个是value,当我们做self.data="abc"的时候,data就作为了一个string传给了key,然后abc就是这个value,同样的,我们会使用
super().__setattr__(self,key,value)
来完成他的默认行为,里面和get的一样,都是pass,什么都没操作,当然你也可以用
object.__setattr__(self,key,value)
其实get的也可以
class A:
def __init__(self):
self.data = 'abc' # 这里就会调用第一次__setattr__ ,打印出了:set data
self.counter = 0 # 这里会调用第二次__setattr__,打印出了:set counter
def __setattr__(self, key, value):
print(f"set {key}")
super().__setattr__(key, value)
o = A()
print(o.data) # 打印出了abc
o.data = 'def' # 这里就会调用第三次__setattr__ ,打印出了:set data
print(o.data) # 打印出了data新的值:def
o.mess = 'xyz' # 这里就会调用第四次__setattr__ ,打印出了:set mess ,哪怕对象没有这个属性呢
print(o.mess) # 打印出了mess的值: xyz
结果
set data
set counter
abc
set data
def
set mess
xyz
delattr
新增有了,查询有了,删除这就来了,这个delattr和之前我们提到的del是不一样的,当一个object消亡的时候并不会调用delattr的,只有当尝试删除一个object的属性的时候才会被调用。
class A:
def __init__(self):
self.data = 'abc'
self.counter = 0
def __delattr__(self, item):
print(f"del {item}")
super().__delattr__(item)
o = A()
print("=============")
del o.data # 使用del关键词删除某一个对象的属性
print(o.data) # 再打印就会报错了
结果
=============
del data
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 14, in <module>
print(o.data) # 再打印就会报错了
AttributeError: 'A' object has no attribute 'data'
dir
我们知道我们有一个挺长用的内置方法,叫dir,它可以帮我们打印出这个object可以access的一些属性,打印出来
class A:
def __init__(self):
self.data = 'abc'
o = A()
print(dir(o))
结果
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'data']
除了那些内置的魔法函数,还有我们自己定义的,比如说data,我们可以在一个class里面通过定义一个dir魔法函数,来改变内置的dir的结果,python官方要求dir方法必须返回一个sequence[序列],那最常见的sequence就是list,比如我们自己定义一下
class A:
def __init__(self):
self.data = 'abc'
def __dir__(self):
return []
o = A()
print(dir(o))
打印的结果就是一个空的list:[]
那我们写一个有意义的,我们拿到正常的dir,然后我们去掉所有下划线开头的属性,只留下我们自己定义的属性
class A:
def __init__(self):
self.data = 'abc'
def __dir__(self):
lst = super().__dir__()
return [el for el in lst if not el.startswith("_")]
o = A()
print(dir(o))
结果
['data']