Python-描述符

发布于 2020-05-23  602 次阅读


什么是描述符?

python描述符是一个"绑定行为"的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有__get()__、__set()__、__delete()__ 。如果这些方法中的任意一种方法被定义在对象中,那么这个对象就是一个描述符。 它是一个类,定义了另一个类的访问方式,即一个类可以将属性管理托管给另一个类 。

需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,描述符只属于类不属于实例。

描述符协议

描述符类基于3个特殊方法,这3个方法组成了描述符协议

  • __ set__(self,obj,type=none)设置属性时调用,也称为setter
  • __ get__(self,obj,value) 读取属性时调用,也称为getter
  • __ delete__(self,obj) 对象调用del时调用这一方法

描述符特点

  • 是一个类,定义了访问另外一个类属性的方式
  • 把实现了__get()__、__set()__ 方法的描述符称为数据描述符
  • 把只实现了__get()__方法的描述符称为非数据描述符
  • 在每次查找属性时,描述符协议的方法实际上是由对象的特殊方法getattrbute(不是getattr()),每次通过点号(instance.atrribute)或者getattr(instance,'attribute')函数来执行查找时,都会隐式的调用getattribute(),对象属性查找顺序如下
    • 实例属性
    • 类属性
    • 父类属性
    • __getattr__()方法

魔法方法__ get__(), __ set__(), __ delete__()的原型为

  • __ get__(self, instance, owner)
  • __ set__(self, instance, value)
  • __ del__(self, instance)

原型中self,instance,owner分别代表什么意思呢?还是用实例代码来演示:

class Desc(object):
    """
    数据描述符
    """

    def __init__(self, key, expected_type):
        """
        key:          用户传进的值
        expected_type:用户传进的值的类型
        """

        self.key = key
        self.expected_type = expected_type

    """
    描述符的三个内置属性的参数如下:
    ---------------------------------------------------
    self:     是描述符的对象,不是使用描述符类的对象,
    instance: 这才是使用描述符类的对象
    owner:    是instance的类
    value:    是instance的值
    ---------------------------------------------------
    """

    def __get__(self, instance, owner):

        print("__get__...")
        print("self : \t\t", self, id(self))
        print("instance : \t", instance, id(instance))
        print("owner : \t", owner)
        print('='*40, "\n")

        return instance.__dict__[self.key]

    def __set__(self, instance, value):
        print('__set__...')
        print("self : \t\t", self, id(self))
        print("instance : \t", instance, id(instance))
        print("value : \t", value)
        print('='*40, "\n")

        if not isinstance(value, self.expected_type):
            raise TypeError("参数%s必须为%s" % (self.key, self.expected_type))
        instance.__dict__[self.key] = value


class Phone:

        #使用描述符
        name = Desc("name", str)
        print('name-------------', id(name))
        price = Desc("price", int)
        print('price-------------', id(price))

#以下为测试代码
phone = Phone("HUAWEI", 5000)
print('phone----------------------', id(phone))
print(phone.__dict__)
phone.name = "XIAOMI"
print(phone.__dict__)
print(phone.name) # 此时调用get方法 相当于Phone.__dict__['name'].__get__(phone, Phone)

以下为################################################################输出内容
name------------- 35253048
price------------- 35253104
__set__...
self : 		 <__main__.Desc object at 0x000000000219EB38> 35253048
instance : 	 <__main__.Phone object at 0x000000000219EC18> 35253272
value : 	 HUAWEI
======================================== 

__set__...
self : 		 <__main__.Desc object at 0x000000000219EB70> 35253104
instance : 	 <__main__.Phone object at 0x000000000219EC18> 35253272
value : 	 5000
======================================== 

phone---------------------- 35253272
{'name': 'HUAWEI', 'price': 5000}
__set__...
self : 		 <__main__.Desc object at 0x000000000219EB38> 35253048
instance : 	 <__main__.Phone object at 0x000000000219EC18> 35253272
value : 	 XIAOMI
======================================== 

{'name': 'XIAOMI', 'price': 5000}
__get__...
self : 		 <__main__.Desc object at 0x000000000219EB38> 35253048
instance : 	 <__main__.Phone object at 0x000000000219EC18> 35253272
owner : 	 <class '__main__.Phone'>
======================================== 

XIAOMI

可以看到实例化类Phone后,调用对象phone设置name,price会自动调用Desc类的__set()__方法

  • self:Desc的实例对象,通过id可以看出也就是代码中的name,price
  • instance: Phone的实例对象,通过id可以看出也就是phone
  • owner :谁拥有这些东西,当然是Phone这个类,它是最高统治者

调用描述符

描述符在所有者类或者其实例中被调用,对于实例对象 object.__getattribute__() 会把 phone.name 转化为 type(Phone).__dict__['name'].__get__(phone, type(phone)) ,对于类type.__getattribute__()会把Phone.name转化为Phone.__dict__['name'].__get__(None,type(phone))

有几点需要牢记

  • 描述符被__getattribute__()方法调用
  • 重载 __getattribute__() 可能会妨碍描述器被自动调用
  • __getattribute__() 仅存在于继承自 object 的新式类(python3都是新式类)之中
  • object.__getattribute__() 和 type.__getattribute__() 对 __get__() 的调用不一样

描述符的种类及优先级

把描述符分为数据描述符、非数据描述符,把实现了get和set方法的描述符称为数据描述符、除了set方法没有实现的称为非数据描述符。之所以要区分描述符的种类,是因为在代理类的属性时有严格的优先级限制。优先级顺序高低如下

类属性>数据描述符>实例属性>非数据描述符>找不到的属性,出发getattr()

类属性>数据描述符

class RevealAccess(object):
            """A data descriptor that sets and returns values
               normally and prints a message logging their access.
            """

            def __init__(self, initval=None, name='var'):
                self.val = initval
                self.name = name

            def __get__(self, obj, objtype):
                print ('Retrieving', self.name)
                return self.val

            def __set__(self, obj, val):
                print ('Updating', self.name)
                self.val = val

class MyClass(object):
        x = RevealAccess(10, 'var "x"')
        y = 5


print(MyClass.__dict__) # 此时x属性为描述符对象
MyClass.x = 8           # 此时没有执行描述符set方法
print(MyClass.__dict__) # 此时x:8

#############################################
{'__module__': '__main__', 'x': <__main__.RevealAccess object at 0x000000000280EAC8>, 'y': 5, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
{'__module__': '__main__', 'x': 8, 'y': 5, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}

通过修改MyClass类x属性值,发现并没有触发set方法的执行,说明类属性的优先级大于数据描述符,此时类属性覆盖了数据描述符,类属性相关操作都与描述符无关。

数据描述符>实例属性

class RevealAccess(object):
            """A data descriptor that sets and returns values
               normally and prints a message logging their access.
            """

            def __init__(self, initval=None, name='var'):
                self.val = initval
                self.name = name

            def __get__(self, obj, objtype):
                print ('Retrieving', self.name)
                return self.val

            def __set__(self, obj, val):
                print ('Updating', self.name)
                self.val = val

class MyClass(object):
        x = RevealAccess(10, 'var "x"')
        y = 5


t = MyClass()
print(t.__dict__) # 没有x属性
print(MyClass.__dict__) # x属性为描述符对象
print(t.x) # 调用get方法

###############################################
{}
{'__module__': '__main__', 'x': <__main__.RevealAccess object at 0x00000000027FEAC8>, 'y': 5, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
Retrieving var "x"
10

实例属性>非数据描述符

class Descriptors:
"""
非数据描述符
"""
def __get__(self, instance, owner):
print("执行Descriptors的set")
def __delete__(self, instance):
print("执行Descriptors的delete")

class Phone:
#使用描述符
name = Descriptors()

def __init__(self, name, price):
self.name = name
self.price = price

#测试
phone = Phone("HUAWEI",6000) #报错,描述符中没有__set__()方法

如果我们的实例属性中使用了非数据描述符,就不能进行复制操作,非数据描述符应用于不需要设置属性的值或者函数(只读操作)

类属性与实例属性重名

class Descriptors:
        """
        非数据描述符
        """

        def __init__(self, name, price):
            self.name = name
            self.price = price

        def __get__(self, instance, owner):
            print("执行Descriptors的get")
            return self.name

        def __set__(self, instance, value):
            print("执行Descriptors的set")
            self.value = value

        def __delete__(self, instance):
            print("执行Descriptors的delete")

class Phone:
        #使用描述符
        name = Descriptors('XIAOMI', 3000)

        def __init__(self, name, price):
            self.name = name
            self.price = price

#测试
phone = Phone("HUAWEI",6000)   
print(phone.name)

#####################################
执行Descriptors的set
执行Descriptors的get
XIAOMI

从结果可以看到,类属性和实例属性重名时,描述符优先,会覆盖实例属性

下篇讲描述符的应用场景


一名测试工作者,专注接口测试、自动化测试、性能测试、Python技术。