__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__方法。

总结

如果bookLanguageBook的实例,那么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