python中的类实例的属性查找过程
__getattr__
、__getattribute__
对属性查找的影响
-
没有__getxxx__的例子
class Book: press = "人民邮电出版社" class LanguageBook(Book): name = "" price = 100 def __init__(self): self.name = "python" book = LanguageBook() print(book.name) print(book.price) print(book.press) print(book.__dict__) print(Book.__dict__) print("-" * 40) print(book.author) 输出: python 100 人民邮电出版社 {'name': 'python'} {'__module__': '__main__', 'name': '', 'price': 100, '__init__': <function LanguageBook.__init__ at 0x00000000037A50D0>, '__doc__': None} {'__module__': '__main__', 'press': '人民邮电出版社', '__dict__': <attribute '__dict__' of 'Book' objects>, '__weakref__': <attribute '__weakref__' of 'Book' objects>, '__doc__': None} ---------------------------------------- Traceback (most recent call last): File "F:/code/python/test.py", line 51, in <module> print(book.author) AttributeError: 'Book' object has no attribute 'author'
通过代码的输出我们可以看到:
1、name属性同时存在于实例的__dict__和类的__dict__中,当访问book.name时得到的是实例book的__dict__中的值。
2、price属性存在于类的__dict__中,当访问book.price时得到的是类LanguageBook的__dict__中的值。
3、press属性存在于类的基类的__dict__中,当访问book.press时得到的是基类Book的__dict__中的值。
4、author属性即不存在于类(或基类)的__dict__中,也不存在于实例的__dict__中,当访问book.author时抛出属性不存在的异常。所以我们得出结论:
1、访问类实例的属性时,首先查找实例本身的__dict__中是否存在,存在直接返回,
2、不存在时,查找类(或基类)的__dict__中是否存在,存在直接返回,
3、不存在则抛出属性不存在的异常。 -
含义__getattr__的例子
class Book: press = "人民邮电出版社" class LanguageBook(Book): name = "" price = 100 def __init__(self): self.name = "python" def __getattr__(self, item): return "__getattr__" book = LanguageBook() print(book.name) print(book.price) print(book.press) print("-" * 40) print(book.author) 输出: python 100 人民邮电出版社 ---------------------------------------- __getattr__
在LanguageBook类中增加__getattr__方法后,通过代码的输出我们可以看到:
1、当访问的属性在实例、类和基类任意一个的__dict__中时,实例属性访问顺序和没有__getattr__时相同。
2、当访问的属性不在实例、类和基类任意一个的__dict__中时,访问属性时会自动调用__getattr__方法。所以我们得出结论:
1、访问类实例的属性时,首先查找实例本身的__dict__中是否存在,存在直接返回,
2、不存在时,查找类(或基类)的__dict__中是否存在,存在直接返回,
3、不存在则判断类(或基类)中是否包含__getattr__方法,存在调用__getattr__方法,
4、不存在则抛出属性不存在异常。 -
含义__getattr__和__getattribute__的例子
class Book: press = "人民邮电出版社" class LanguageBook(Book): name = "" price = 100 def __init__(self): self.name = "python" def __getattr__(self, item): return "__getattr__" def __getattribute__(self, item): return "__getattribute__" book = LanguageBook() print(book.name) print(book.price) print(book.press) print("-" * 40) print(book.author) 输出: __getattribute__ __getattribute__ __getattribute__ ---------------------------------------- __getattribute__
若在__getattribute__中抛出AttributeError异常
class Book: press = "人民邮电出版社" class LanguageBook(Book): name = "" price = 100 def __init__(self): self.name = "python" def __getattr__(self, item): return "__getattr__" def __getattribute__(self, item): if item == 'author': raise AttributeError return "__getattribute__" book = LanguageBook() print(book.name) print(book.price) print(book.press) print("-" * 40) print(book.author) 输出: __getattribute__ __getattribute__ __getattribute__ ---------------------------------------- __getattr__
在LanguageBook类中增加__getattribute__方法后,通过代码的输出我们可以看到:
1、不管属性是否存在于实例、类和基类任意一个的__dict__中,访问属性时都自动调用__getattribute__方法;
2、当访问book.author时,__getattribute__方法中抛出了AttributeError异常,转而调用__getattr__方法。所以我们得出结论:
1、访问类实例的属性时,当类(或基类)中包含__getattribute__方法,首先调用__getattribute__方法,若__getattribute__方法中抛出AttributeError异常,会转而调用__getattr__方法,
2、不存在__getattribute__方法则查找实例本身的__dict__中是否存在,存在直接返回,
3、不存在时,查找类(或基类)的__dict__中是否存在,存在直接返回,
4、不存在则判断类(或基类)中是否包含__getattr__方法,存在调用__getattr__方法,
4、不存在则抛出属性不存在异常。补充:实际上,最顶层基类object中就实现了__getattribute__方法,里面处理了后续的属性查找逻辑,若查找不到,则抛出异常转而调用__getattr__方法。我们一般在自定义类中不重写__getattribute__方法,因为此方法对属性访问影响大,处理不好很容易出错。
__get__
、__set__
、__delete__
对属性查找的影响
这里需要涉及一个新的概念:属性描述符(Descriptor)。
实现了__get__,__set__,__delete__中任意一个方法的类,就可以叫它属性描述符类,属性描述符,分为数据属性描述符和非数据属性描述符。
1、如果类只实现了__get__方法,就为非数据属性描述器(non-data descriptor)。
2、如果类实现了__get__方法,同时实现了__set__,__delete__中的一个或两个,就为数据属性描述符(data descriptor)。
属性描述符类需要被实例化为对象,且对象作为另一个类的类属性时__get__
,__set__
,__delete__
方法才能生效。
class Dec:
def __get__(self, instance, owner):
print(instance, owner)
return 'Des:__get__'
class LanguageBook:
name = Dec()
def __init__(self):
self.price = Dec()
book = LanguageBook()
print(book.price)
print("-" * 40)
print(book.name)
print(LanguageBook.name)
输出:
<__main__.Dec object at 0x0000000002330DA0>
----------------------------------------
<__main__.LanguageBook object at 0x0000000002940BE0> <class '__main__.LanguageBook'>
Des:__get__
None <class '__main__.LanguageBook'>
Des:__get__
通过代码输出我们可以看到:
1、当属性描述符类实例化的对象为LanguageBook类对象的属性时,会被当做普通的类实例处理;
2、当属性描述符类实例化的对象为LanguageBook类的属性时,通过LanguageBook类或book实例调用对应属性时,都会调用描述符的__get__方法;
3、__get__方法接收两个参数,第一个参数为所属类的实例,第二个为所属的类本身,所以调用LanguageBook.name时,__get__方法的第一个参数为None
数据属性描述符和非数据属性描述符,对实例属性查找的影响有所不同。
# 当类属性为非数据属性描述符时
class Dec:
def __get__(self, instance, owner):
return 'Des:__get__'
class LanguageBook:
name = Dec()
def __init__(self):
self.name = 'python'
book = LanguageBook()
print(book.name)
print(book.__dict__)
print(LanguageBook.__dict__)
输出:
python
{'name': 'python'}
{'__module__': '__main__', 'name': <__main__.Dec object at 0x0000000002940B70>, '__init__': <function LanguageBook.__init__ at 0x00000000029477B8>, '__dict__': <attribute '__dict__' of 'LanguageBook' objects>, '__weakref__': <attribute '__weakref__' of 'LanguageBook' objects>, '__doc__': None}
--------------------分割线----------------------------
# 当类属性为数据属性描述符时
class Dec:
def __get__(self, instance, owner):
return 'Des:__get__'
def __set__(self, instance, value):
pass
class LanguageBook:
name = Dec()
def __init__(self):
self.name = 'python'
book = LanguageBook()
print(book.name)
print(book.__dict__)
print(LanguageBook.__dict__)
print("-" * 40)
book.__dict__['name'] = 'java'
print(book.__dict__)
print(book.name)
输出:
Des:__get__
{}
{'__module__': '__main__', 'name': <__main__.Dec object at 0x0000000002940B70>, '__init__': <function LanguageBook.__init__ at 0x00000000029477B8>, '__dict__': <attribute '__dict__' of 'LanguageBook' objects>, '__weakref__': <attribute '__weakref__' of 'LanguageBook' objects>, '__doc__': None}
----------------------------------------
{'name': 'java'}
Des:__get__
上面的代码中,类和实例中都定义了name属性,其中类的name属性为属性描述符,通过代码的输出我们可以看到:
1、当类的name属性为非数据属性描述符时,实例中定义的name属性会被写入到实例的__dict__字典中,调用book.name会获取实例__dict__中的值;
2、当类的name属性为数据属性描述符时,实例中定义的name属性不会被写入到实例的__dict__字典中,调用book.name会调用描述符符类中的__get__方法;
3、当类的name属性为数据属性描述符时,即使实例的__dict__存在name属性,调用book.name也会调用属性描述符类中的__get__方法
所以我们得出结论:
1、当访问实例属性时,如果属性出现在其类(或基类)的__dict__中,且为数据属性描述符(data descriptor),不管实例的__dict__中是否包含对应属性,都调用属性描述符的__get__方法,
2、当访问实例属性时,如果属性出现在其类(或基类)的__dict__中,且为非数据属性描述符(data descriptor),那么判断属性是否出现在实例的__dict__中,若存在则返回实例__dict__字典中的字,若不存在则调用属性描述符的__get__方法。
总结
如果book
是LanguageBook
的实例,那么book.name
(或getattr(book,'name')
)的查找顺序如下:
1、首先调用__getattribute__
方法,若在__getattribute__
中找到对应属性就直接返回,找不到就抛出AttributeError
异常。
2、如果类(或其基类)中定义了__getattr__
方法,在抛出AttributeError
异常后就转而调用__getattr__
方法,否则继续抛出AttributeError
异常。
属性的查找逻辑主要在__getattribute__
方法中,在不重写object类中的__getattribute__
方法的情况下属性查找顺序如下:
1、如果name
出现在LanguageBook
(或其基类)的__dict__
中,且name
是数据属性描述符,那么调用调用其__get__
方法。
2、如果name
出现在book
的__dict__
中,那么返回book
的__dict__
中的值。
3、如果name
出现在LanguageBook
(或其基类)的__dict__
中,且name
是非数据属性描述符,那么调用调用其__get__
方法。
4、如果name
出现在LanguageBook
(或其基类)的__dict__
中,那么返回LanguageBook
(或其基类)的__dict__
中的值。
5、__getattribute__
方法中的属性查找结束,未找到属性,抛出AttributeError
异常。
6、如果在LanguageBook
(或其基类)中实现了__getattr__
方法,则调用其__getattr__
方法。
7、抛出AttributeError
异常。
本文地址:https://blog.csdn.net/u014294083/article/details/109823700
上一篇: SSM框架整合核心内容