Python中属性的查找过程分析

一. 预备知识

  1) 类中实现了__get__()__set__()__delete__()中任意一个方法,就是一个属性描述符

  2) 数据属性描述符: 至少实现__set__()__get__()方法

  3) 非数据属性描述符: 实现除__set__()以外的方法

  4) 使用属性描述符后, 可以在为属性赋值时增加一些逻辑判断

  5) 假设obj是实例化对象, 那么 obj.name 等价于 getattr(obj, "name")

二. 属性的查找过程分析

2.1 __getattribute__方法是访问属性的入口

直接说结论:

  属性查找优先级: __getattribute__方法 > 实例属性

  __getattribute__方法是所有属性调用的入口, 需要注意的是这个方法如果使用不当,会导致程序死循环

# -*- coding: utf-8 -*-


class Person(object):
    def __init__(self):
        self.name = "chinablue"
        self.age = 18

    def __getattribute__(self, item):
        print(f"调用了__getattribute__方法")
        if "age" == item:
            return 38
        return object.__getattribute__(self, item)


p = Person()

print(p.__dict__)  # 实例属性信息: {'name': 'chinablue'}

print(p.name)  # 实例中有name属性: chinablue
print(p.age)  # 实例中有age属性,但__getattribute__方法拦截并修改了age属性: 38
# print(p.job)  # 如果实例对象访问的属性不存在,也会访问__getattribute__方法

 

2.2 类属性对属性查找的影响

直接说结论:

  属性查找优先级实例属性 > 类属性

  实例没有的属性,会去类属性中查找

# -*- coding: utf-8 -*-


class Person(object):
    # age是类属性
    age = 18

    def __init__(self):
        # name是实例属性
        self.name = "chinablue"


p = Person()

print(p.__dict__)  # 实例属性信息: {'name': 'chinablue'}
print({k: v for k, v in Person.__dict__.items() if not k.startswith("__")})  # 类的属性信息: {'age': 18}

print(p.name)  # 实例中有name属性: chinablue
print(p.age)  # 实例中没有age属性,类中有age属性: 18

 

2.3 __getattr__方法对属性查找的影响

直接说结论:

  属性查找优先级实例属性 > 类属性 > __getattr__方法

  如果实例访问的属性不存在,则会抛出AttributeError异常

  如果实例中定义了__getattr__方法, 当实例访问的属性不存在时, 会去访问__getattr__方法

# -*- coding: utf-8 -*-


class Person(object):
    def __init__(self):
        # name是实例属性
        self.name = "chinablue"

    # 当实例对象找不到属性时,会来调用__getattr__方法
    def __getattr__(self, item):
        return "djtest"


p = Person()

print(p.__dict__)  # 实例属性信息: {'name': 'chinablue'}

print(p.name)  # 实例中有name属性: chinablue
print(p.job)  # 实例中没有job属性,会调用__getattr__函数: djtest

 

另外, 如果实例对象中同时存在__getattr__方法和__getattribute__方法, 代码如下:

# -*- coding: utf-8 -*-


class Person(object):
    def __init__(self):
        ...

    def __getattribute__(self, item):
        print(f"调用了__getattribute__方法")
        return object.__getattribute__(self, item)

    def __getattr__(self, item):
        print(f"调用了__getattr__方法")
        raise AttributeError(f"{item}属性不存在")


p = Person()

print(p.name)  # 实例中没有name属性,先调用__getattribute__方法, 再调用__getattr__方法
View Code

 

2.4 非数据属性描述符对属性查找的影响

直接说结论:

  属性查找优先级实例属性 > 非数据属性描述符 > __getattr__方法

  非数据属性描述符的优先级与类属性相当

# -*- coding: utf-8 -*-


class PersonName:
    def __get__(self, instance, owner):
        return "djtest"


class PersonAge:
    def __get__(self, instance, owner):
        return 28


class Person(object):
    # age和name是非数据描述符
    age = PersonAge()
    name = PersonName()

    def __init__(self):
        self.name = "chinablue"


p = Person()

print(p.__dict__)  # 实例属性信息: {'name': 'chinablue'}
print({k: v for k, v in Person.__dict__.items() if not k.startswith("__")})  # 类的属性信息: {'age': <PersonAge object>, 'name': <PersonName object>}

print(p.name)  # 实例中有name属性: chinablue
print(p.age)  # 实例中没有age属性,类中(非数据描述符中)有age属性: 28

 

2.5 数据属性描述符对属性查找的影响

 直接说结论:

  属性查找优先级: 数据属性描述符 > 实例属性

# -*- coding: utf-8 -*-


class PersonName:
    def __get__(self, instance, owner):
        return "djtest"

    def __set__(self, instance, value):
        self.name = value


class Person(object):
    # name是数据描述符
    name = PersonName()

    def __init__(self):
        self.name = "chinablue"


p = Person()

print(p.__dict__)  # 实例属性信息: {}
print({k: v for k, v in Person.__dict__.items() if not k.startswith("__")})  # 类的属性信息: {'name': <__main__.PersonName object at 0x033709B0>}

print(p.name)  # 实例中有name属性: djtest

 

三. 本章小结

对象属性的查找优先级参考如下:

    __getattribute__方法 > 数据属性描述符 > 实例属性 > 类属性 | 非数据属性描述符 > __getattr__方法

 

posted @ 2021-08-24 21:18  后来者2012  阅读(197)  评论(0编辑  收藏  举报