什么是描述符?
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从结果可以看到,类属性和实例属性重名时,描述符优先,会覆盖实例属性
下篇讲描述符的应用场景






 
 


Comments | NOTHING