1. 主页
  2. Python基础到高级
  3. 描述器

描述器

描述器的定义

描述器的其实就是一个类,实现了__get__,__set__和__delete__中的任何一个方法的类,这样的类就是描述器

描述器是一种代理机制,当一个类变量被定义为描述器的时候,对这个类变量的操作,将有此描述器代理

  • 访问这个类变量会调用__get__方法
  • 对这个类变量赋值会调用__set__方法
  • 删除这个类变量的时候会调用__delete__方法

定义一个描述器

In [12]: class Int:
    ...:     def __init__(self, val):
    ...:         self.val = val
    ...:     def __get__(self, instance, cls):
    ...:         print(self.val)
    ...:     def __set__(self, instance, cls):
    ...:         self.val = 'test set'
    ...:         

In [13]: class A: 
    ...:     val = Int('val')   # 设置一个类变量,为上面类,也就是描述器
    ...:     def __init__(self):
    ...:         self.val = 3   # 在当前作用域中定义val为3
    ...:         

In [14]: a = A()    # 实例化 A

In [15]: a.val   # 获取实例a的val,本来应该是3的,但是因为在实例的类中定义了一个类变量val为一个描述器,则类变量val的访问及设置的顺序就发生了改变
                 # 访问的时候就是在访问描述器的__get__方法,设置的时候就是调用了描述器的__set__方法
test set

描述器会提升优先级到高于dict之前,但是前提是描述器中存在set或者delete方法,如果描述器中不存在set或者delete方法的话,则低于dict的优先级

In [17]: a.__dict__
Out[17]: {}

In [18]: a.__dict__['val'] = 3   # 直接给实例添加了一个val的类变量,理论上来说,dict里面的类变量是高于类变量的

In [20]: a.__dict__
Out[20]: {'val': 3}

In [21]: a.val   # 但是当我们访问的时候,还是得到了描述器的__get__的返回值
test set

In [22]: class Int:   # 定义一个没有set方法的描述器
    ...:     def __get__(self, instance, cls):
    ...:         return 3
    ...:       

In [25]: class A():
    ...:     val = Int()
    ...:     def __init__(self):
    ...:         self.__dict__['val'] = 5   # 直接在dict中定义一个类变量
    ...:         

In [26]: a = A()

In [27]: a.val   # 访问实例a的类变量的时候,没有调用描述器的get方法
Out[27]: 5

__get__、__set__和__delete__的原始表达形态

  • __get__(self, instance, cls) # instance:类实例, cls:类本身, 使用类访问的时候,instance为None
  • __set__(self, instance, value) # instance:类实例, value:为值,只有实例才会调用__set__方法
  • __delete__(self, instance) # instance:类实例

__get__

In [31]: class Desc:
    ...:     def __get__(self, instance, cls):   # 分别将instance和cls打印,看是什么东西
    ...:         print(instance)  
    ...:         print(cls)
    ...:         return 'x'
    ...:     

In [32]: class A:
    ...:     x = Desc()
    ...:     

In [33]: A().x
<__main__.A object at 0x7f30cca44358>   # 类的实例
<class '__main__.A'>   # 类的本身
Out[33]: 'x'

In [34]: A.x    # 通过类名来访问,无法获取到实例结果
None
<class '__main__.A'>
Out[34]: 'x'

__set__

In [35]: class Desc:
    ...:     def __set__(self, instance, value):
    ...:         print(instance)
    ...:         print(value)
    ...:         

In [36]: class A:
    ...:     x = Desc()
    ...:     

In [37]: A().x = 5   # 仅仅只能是实例才会调用
<__main__.A object at 0x7f30cca71cc0>
5

In [38]: A.x = 5

__delete__

In [39]: class Desc:
    ...:     def __delete__(self, instance):
    ...:         print(instance) 
    ...:            

In [40]: class A:
    ...:     x = Desc()
    ...:  

In [42]: del A().x  # 删除类变量
<__main__.A object at 0x7f30ccaf6cf8>

描述器的应用场景

描述器是用于接管实例变量的操作

使用描述器来实现classmethod装饰器

from functools import partial   # 固定方法所在位置

class Classmethod:
    def __init__(self, fn):
        self.fn = fn
    def __get__(self, instance, cls):
        return partial(self.fn, cls)

使用描述器来实现staticmethod装饰器

from functools import partial
from functools import wraps

class Staticmethod:
    def __init__(self, fn):
        self.fn = fn
    def __get__(self, instance, cls):
        return wraps(self.fn)(partial(self.fn, cls))

我们要如何帮助您?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注