__getattr__
在Python中,当我们试图访问一个不存在的属性的时候,会报出一个AttributeError
。但是如何才能避免这一点呢?于是__getattr__
便闪亮登场了
当访问一个不存在的属性的时候,会触发__getattr__方法
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
def __getattr__(self, item):
return ">>>" + item
a = A()
print(a.name) # satori
print(a.这个属性不存在) # >>>这个属性不存在
"""
可以看到当我们访问存在的属性的时候,是可以直接访问的
如果视图访问一个不存在的属性,那么会触发__getattr__方法
里面的参数item就是我们访问的不存在的属性名
"""
因此有了__getattr__
,我们便可以进行更安全的属性访问了
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
def __getattr__(self, item):
try:
return self.__dict__[item]
# 从属性字典里面返回,至于这里可不可以通过self.item返回呢?答案显然是不行的
# 因为self.item这种方式表示的就是获取self里面的item属性,虽然我们后面传的是name
# 但是.item就表示获取名称为item的属性,相当于self.__dict__["item"]
# 因此如果是self.item,发现不存在item属性,于是触发__getattr__,然后又执行self.item
# 于是又触发__getattr__,于是会引发无限递归,从而最终栈溢出。
except KeyError:
return f"不存在{item}属性"
a = A()
print(a.name) # satori
print(a.age) # 不存在age属性
__getattribute__
__getattribute__
是做什么的呢?其实它和__getattr__
比较类似,只不过__getattr__
是在访问一个不存在的属性才会触发,而__getattribute__
则是在访问任何属性的时候都会触发。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
def __getattr__(self, item):
try:
return self.__dict__[item]
except KeyError:
return f"不存在{item}属性"
def __getattribute__(self, item):
return item
a = A()
print(a.name) # name
print(a.不存在的属性) # 不存在的属性
"""
可以看到不管属性存不存在,获取的时候都会触发__getattribute__方法
__getattribute__里面的item参数,也是我们传入的属性名。
这个__getattribute__也叫做属性拦截器,就是不管什么属性都会触发该方法
"""
如果我们不希望外部访问某些变量,除了使用私有变量之外(然而也是可以访问的),还可以使用__getattribute__
,我们看到把所有属性都拦截下来了。但是问题又来了,既然所有属性都拦截下来了,那我怎么访问想访问的属性呢?
答案是raise一个AttributeError,一旦当__getattribute__
引发了AttributeError,那么便会触发__getattr__
方法。可以把__getattr__
看成是__getattribute__
的小弟,什么事都要经过老大,如果老大抛异常了,那么小弟要来兜着。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
self.age = 16
def __getattr__(self, item):
return f"{item} is satori"
def __getattribute__(self, item):
if item == "age":
return "女人的芳龄不可泄露"
else:
print("其他的属性就无所谓喽")
raise AttributeError
a = A()
print(a.age) # 女人的芳龄不可泄露
"""
当访问age的时候,直接给拦截下来了,不让访问该属性
"""
print(a.name)
"""
其他的属性就无所谓喽
name is satori
"""
当__getattribute__
引发异常,交给__getattr__
执行的时候,那么也会把item传递给__getattr__
。不过可能会有人好奇,__getattr__
里面不是self.dict[item]吗?怎么改了?这正是我想说的,这个__getattribute__
真的一不小心就会引发无限递归。所以这里写self.dict[item]是不合适的,可如果通过属性字典都不行的话,那我们怎么获取属性?别急,后面还有一个解决办法,不过在此之前,我们来看一下,为什么写成self.dict[item]会引发无限递归。
首先我们要明白两件事,尽管已经说过了,但是再重复一遍。
如果__getattribute__方法raise了一个AttributeError,那么会执行__getattr__
不管什么时候,只要是访问实例对象(self)的属性,也就是通过self.xxx访问实例对象属性的时候,不管属性是否存在,都会触发__getattribute__方法。
那我们要怎么做呢?通过super的方式,让父类去调用
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
self.age = 16
def __getattr__(self, item):
try:
return self.__dict__[item]
except KeyError:
return f"不存在{item}属性"
def __getattribute__(self, item):
print(">>>item:", item)
if item == "age":
return "女人的芳龄不可泄露"
return super().__getattribute__(item)
a = A()
print(a.age)
"""
>>>item: age
女人的芳龄不可泄露
"""
# 访问age,直接返回,这是肯定的
print(a.name)
"""
>>>item: name
satori
"""
# 如果执行__getattribute__方法之后,还return 父类调用__getattribute__方法
# 那么父类会直接返回给我们,如果这个属性存在的话。
print(a.xxx)
"""
>>>item: xxx
>>>item: __dict__
不存在xxx属性
"""
# 现在我访问了一个不存在的属性xxx
# 那么肯定调用__getattribute__,这是肯定的,先打印xxx
# 然后调用父类的__getattribute__,然后父类去帮我们获取self的对应属性。
# 但是找不到,那么会默认执行__getattr__。如果我们没有重写__getattr__,那么默认执行object的__getattr__,显然会报错
# 但是我们自己定义了__getattr__,因此会执行我们自己的__getattr__
# 于是执行self.__dict__[item],但是__dict__是self的一个属性,于是在这一步又会触发__getattribute__
# 此时的item就是字符串形式的__dict__,然后打印
# 由于"__dict__" != "age", 因此会再调用父类的__getattribute__,帮我们获取,显然self是有__dict__这个属性字典的,于是直接返回
# 此时的self.__dict__就返回了属性字典,然后获取对应的属性,但是发现没有,于是引发KeyError
# 捕获、打印不存在该属性
因此可以看到,这个__getattribute__
功能很强大,但是搞不好会走火入魔,最保险的方法就是最后一定要让父类去调用,不建议使用raise AttributeError的方式。而且至于这个方法本身,没事的话也不建议使用,除非你能很清晰地理解这个方法,并且很明白自己就需要这个方法,那么是可以使用的。如果你都不确定要不要用这个方法,那么十有八九是用不到的,就跟元类一样,需要你有清晰的认识和较深的理解。
getattr
还记得getattr吗?获取对象的某个属性,通过指定一个默认值,如果找不到,就返回默认值。那么这是如何实现的呢?
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
self.age = 16
def __getattr__(self, item):
try:
return self.__dict__[item]
except KeyError:
return f"不存在{item}属性"
a = A()
print(getattr(a, "name", None)) # satori
print(getattr(a, "age", None)) # 16
"""
获取已存在的属性,那么显然结果是正确的
"""
print(getattr(a, "xxx", None)) # 不存在xxx属性
"""
明明不存在xxx这个属性,但是却没有返回我们自己定义的值,这是为什么?
其实从返回的结果也能看出来,getattr(obj, "attr"),本质上就是调用obj对应的类的__getattr__方法,获取其返回值
因此可以理解,我们上面即便获取不到属性的时候,还是会返回一个正常的值。
getattr(obj, "attr")本质上就是obj对应的类.__getattr__(obj, "attr")
只不过使用getattr()这种方式会多帮我们做一层处理,就是找不到属性返回默认值。
而我们定义了__getattr__,而且不管属性存不存在都是正常返回的,那么getattr肯定就获取不到默认值了。
"""
那么怎么办呢?我们可以看看如果在__getattr__
只返回属性的值,属性不存在就不管了,这样使用getattr是不是就能返回默认值了呢?我们来试一下
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
self.age = 16
def __getattr__(self, item):
return self.__dict__[item]
a = A()
print(getattr(a, "xxx", None))
"""
Traceback (most recent call last):
File "C:/Users/satori/Desktop/satori/task.py", line 15, in <module>
print(getattr(a, "xxx", None))
File "C:/Users/satori/Desktop/satori/task.py", line 11, in __getattr__
return self.__dict__[item]
KeyError: 'xxx'
"""
但是解释器报错了,这是为什么呢?我们注意到异常是KeyError。我们再来试试不定义__getattr__
的情况
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
self.age = 16
a = A()
print(getattr(a, "xxx", None)) # None
"""
我们发现自己不定义__getattr__,而是走父类的__getattr__是可以在访问不存在的属性的时候,获取到默认值。
可这是为什么?
"""
# 如果我们直接访问
print(a.xxx)
"""
Traceback (most recent call last):
File "C:/Users/satori/Desktop/satori/task.py", line 17, in <module>
print(a.xxx)
AttributeError: 'A' object has no attribute 'xxx'
"""
我们注意到,当我们在不定义__getattr__
的时候,会使用父类的__getattr__
,此时getattr就没问题。但是我们来注意一下,首先我们自己定义的__getattr__
在找不到属性的时候,抛出的是一个KeyError,但是父类的__getattr__
抛出的确是一个AttributeError。这不是意味着我们如果抛出的也是一个AttributeError就可以了呢?
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "satori"
self.age = 16
def __getattr__(self, item):
try:
return self.__dict__[item]
except KeyError:
raise AttributeError
a = A()
print(getattr(a, "xxx", None)) # None
print(getattr(a, "哈哈哈", 666)) # 666
咦,居然成功了。所以我们需要raise一个AttributeError
这里还想提一点,我们说getattr(obj, "attr")本质上就是调用obj对应的类.__getattr__(obj, "attr")
。具有这样特点方法,还有很多,比如说len。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __len__(self):
return 123
a = A()
print(len(a)) # 123
print(A.__len__(a)) # 123
"""
但是可能有人有疑问,这等不等于a.__len__()呢?可以一半等于吧
"""
a.__len__ = lambda : 234
print(a.__len__()) # 234
print(len(a)) # 123
"""
可以看到,当我手动给a设置了__len__属性的时候,再使用len(a)走的还是类的方法
"""
# 因此如果类里面没有__len__方法的话,即使实例有,那么使用len()这种方式也是会报错的
# 会报出TypeError: object of type 'A' has no len()
# 因为这种方法的本质上就是,类.__len__(实例),而不是实例.__len__()
# 但是如果不给类设置__len__方法,那么a.__len__()是可以的,因为实例没有__len__,就会去执行类的__len__,并自动把本身作为第一个参数传进去
# 所以本质走的还是类的方法,因此我们说一半等于
hasattr
提到getattr,就不提hasattr,这是判断一个对象里面是否有某个属性,其实hasattr就是在getattr的基础上实现的。我们知道getattr获取属性是可以指定默认值的,属性找不到就会获取默认值。但是如果不指定,在获取不到的属性时候同样会抛出异常(而不是返回None,这一点切记),什么异常呢?对,AttributeError。如果抛出了这个异常,那么说明没有该属性,返回False,否则返回True
我们完全可以手动实现一个hasattr
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "mashiro"
def hasattr_(obj, attr):
try:
getattr(obj, attr)
return True
except AttributeError:
return False
a = A()
print(hasattr_(a, "name")) # True
print(hasattr_(a, "age")) # False
setattr(__setattr__)
同理还有setattr,这个底层则是调用类的__setattr__
方法。
class A:
def __init__(self):
self.name = "mashiro"
a = A()
setattr(a, "age", 16) # 传入对象、属性名、值
print(a.age) # 16
我们同样可以改写
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "mashiro"
def __setattr__(self, key, value):
print(key, value)
print("设置完之后,不要别访问啊。我只是打印了一下,没有设置到你的属性字典里面去,你访问不到的")
a = A()
setattr(a, "age", 16)
"""
name mashiro
设置完之后,不要别访问啊。我只是打印了一下,没有设置到你的属性字典里面去,你访问不到的
age 16
设置完之后,不要别访问啊。我只是打印了一下,没有设置到你的属性字典里面去,你访问不到的
"""
# 我们可以看到,__setattr__里面的key和value就是属性和值
# 但是我们看到__init__里面的self.name居然也走了__setattr__
# 说明self.xxx走的是__getattr__,self.xxx = xxx走的是__setattr__
print(hasattr(a, "name")) # False
print(hasattr(a, "age")) # False
# 可以看到都没有设置到属性字典里面去。
# 那我们如何设置呢?
# 肯定不能通过a.__setattr__来设置,因为这走的就是A的__setattr__
# 首先__setattr__的本质就是给一个 obj 赋上 指定name 的 value
# 既然不能用A的__setattr__,那我用其他人的不就可以了吗?
int.__setattr__(a, "name", "satori")
object.__setattr__(a, "age", 16)
dict.__setattr__(a, "gender", "f")
set.__setattr__(a, "place", "东方地灵殿")
print(hasattr(a, "name"), a.name)
print(hasattr(a, "age"), a.age)
print(hasattr(a, "gender"), a.gender)
print(hasattr(a, "place"), a.place)
"""
True satori
True 16
True f
True 东方地灵殿
"""
delattr(__delattr__)
这个就更简单了,就是删除对象的某个属性
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/23 9:27
class A:
def __init__(self):
self.name = "xxx"
a = A()
delattr(a, "name")
try:
a.name
except AttributeError as e:
print(e) # 'A' object has no attribute 'name'
# 本质上调用了__delattr__
__getitem__
__getattr__
,是支持我们通过 . 的方式来访一个不存在的属性。而__getitem__
则是支持我们可以像字典和列表一样使用[]的形式访问。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "mashiro"
def __getitem__(self, item):
print(">>>:" + item)
return self.__dict__.get(item, "不存在哦")
a = A()
print(a["name"])
"""
>>>:name
mashiro
"""
print(a["gogogo"])
"""
>>>:gogogo
不存在哦
"""
# 可以看到这里的item也是我们传入的属性名
可以看到__getitem__
是支持我们使用[]这种方式获取属性的,会把[]里面内容传给item。但从获取属性这一点上来说,这和__getattr__
貌似没啥区别啊,只不过一个是通过self.xxx、一个是通过self["xxx"]的方式。但是__getitem__
除了支持我们使用[]访问属性之外,还有一个更强大的功能,就是它可以充当迭代器。总所周知,如果我们自己定义一个类,想能够被for循环,那么这个类要实现__iter__
和__next__
方法,如果没有实现这两个方法,而是实现了__getitem__
方法也是可以的。对于一个类,如果对其进行for循环,首先会去找__iter__
和__next__
方法,如果没有,那么会退而求其次去找__getitem__
方法。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.name = "mashiro"
def __getitem__(self, item):
return item
for i in A():
if i > 10:
break
print(i)
"""
0
1
2
3
4
5
6
7
8
9
10
"""
# 可以看到,如果没有__iter__和__next__,但如果有__getitem__
# 那么会去执行__getitem__,并且每一次迭代,都会自动将值传递给item
# 我们虽然没有在任何地方指定item,但是对于迭代来说,会自动传递0 1 2 3 4......
我们看一个栗子
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.l = ['a', 'b', 'c', 'd']
def __getitem__(self, item):
return self.l[item]
for i in A():
print(i)
"""
a
b
c
d
"""
# item显然是从0开始,依次遍历获取self.l里面的元素
# 一旦当索引越界,for循环迭代不到元素,那么便迭代终止
# 除此之外,__getitem__还可以做很多事情
a = A()
l = [123]
l += a
print(l) # [123, 'a', 'b', 'c', 'd']
# 为什么会有这个结果,首先对于列表来说,+= 就相当于extend
# 同样会依次打开,将里面的元素迭代出来,会自动捕获异常
不仅如此,我们看看__getitem__
更让人惊叹的特性。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __init__(self):
self.l = ['a', 'b', 'c', 'd']
def __getitem__(self, item):
print(">>>item =", item)
return self.l[item]
a = A()
print(a[1: 3])
"""
>>>item = slice(1, 3, None)
['b', 'c']
"""
print(a[0:: 2])
"""
>>>item = slice(0, None, 2)
['a', 'c']
"""
# 还可以传入一个切片
所以__getitem__
除了支持我们使用[]来取值之外,还可以充当迭代器的作用,功能还是很强大的。当然这只是__iter__
和__next__
不存在的时候,如果定义了,肯定还是会先走__iter__
和__next__
。__getitem__
是在找不到这两个方法时,解释器才会非常智能地退化去找__getitem__
方法。
__setitem__和__delitem__
既然提到了__getitem__
,就必须提__setitem__
和__delitem__
。从__setattr__
和__delattr__
应该也知道这个__setitem__
和__delitem__
是干什么的。
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class A:
def __getitem__(self, item):
return self.__dict__[item]
def __setitem__(self, key, value):
print(key, value)
a = A()
a["name"] = "satori"
"""
name satori
"""
# 通过字典的形式赋值的时候会触发这个方法
我们就来使用__getitem__
模拟字典,其实字典之所以能够使用[]这种方式取值,也是因为其内部实现了__getitem
和__setitem__
和__delitem__
这几个魔法函数
# -*- coding:utf-8 -*-
# @Author: WanMingZhu
# @Date: 2019/10/22 10:31
class Dict:
def __init__(self, items):
for k, v in items:
self.__dict__[k] = v
def __getitem__(self, item):
return self.__dict__[item]
def __setitem__(self, key, value):
self.__dict__[key] = value
def __delitem__(self, key):
del self.__dict__[key]
def __str__(self):
return str(self.__dict__)
d = Dict([("a", 1), ("b", 2)])
print(d) # {'a': 1, 'b': 2}
d['c'] = 3
print(d) # {'a': 1, 'b': 2, 'c': 3}
print(d['b']) # 2
del d['a'], d['b']
print(d) # {'c': 3}
以上就是全部内容啦。
おしまい