欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python中的类实例的属性查找过程

程序员文章站 2022-03-18 16:37:26
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...

__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

相关标签: python