当前位置:   article > 正文

python中的类实例的属性查找过程_python 类数组 根据类的属性查找

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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    通过代码的输出我们可以看到:
    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__
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在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__
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    若在__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__
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在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)。
  • 1
  • 2
  • 3

属性描述符类需要被实例化为对象,且对象作为另一个类的类属性时__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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

通过代码输出我们可以看到:
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__
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

上面的代码中,类和实例中都定义了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异常。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/540999
推荐阅读
相关标签
  

闽ICP备14008679号