Python描述符

1、描述符定义

先看下描述符的定义。

如果在一个新式类中至少实现了 __ get__ (),__ set __ (), __ delete __ ()中的一个,则称作这个的新式类为描述符,也称为描述符协议。

  • __ get __ ():调用一个属性时触发;
  • __ set __():一个属性赋值时触发;
  • __ delete __ ():采用del删除属性时触发;

定义一个描述符:

class Foo:  # 在python3中Foo是新式类,它实现了__get__(),__set__(),__delete__()中的一个三种方法的一个,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
        print("__get__() is called")

    def __set__(self, instance, value):
        pass

    def __delete__(self, instance):
        pass
    

2、描述符作用

使用描述符能够干什么?

描述符的作用是用来代理另外一个类的属性的,必须把描述符定义成这个类的类属性,不能定义到构造函数中

class Foo:
    def __get__(self, instance, owner):
        print("__get__() is called")

    def __set__(self, instance, value):
        print("__set__() is called")

    def __delete__(self, instance):
        print("__delete__() is called")


f = Foo()

上面的代码定义了一个描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法。

f.name = "jack"
f.name
del f.name
# 并不会触发Foo类中的任何方法

什么时候会触发这三个方法的执行呢?看下面的示例:

class Str:
    """描述符Str"""

    def __get__(self, instance, owner):
        print('Str调用')

    def __set__(self, instance, value):
        print('Str设置...')

    def __delete__(self, instance):
        print('Str删除...')


class People:
    name = Str() # 必须定义在类属性中

    def __init__(self, name):  # name被Str类代理
        self.name = name

p = People("alex") # 打印出 “Str设置...”,即调用了Str类中的__set__方法

p.name # 打印 “Str调用”
p.name = "jack" # 打印 “Str设置...”
del p.name # 打印 “Str删除...”

从上面示例可以看出,只有当描述符被定义成另一个类的类属性时,并且不能定义到另一个类中的构造方法中

3、两种描述符

  • 数据描述符

至少实现了__ get __ () 和 __ set__ ()

class Foo:
    def __set__(self, instance, value):
        print('set')

    def __get__(self, instance, owner):
        print('get')
  • 非数据描述符

没有实现 __ set __()

class Foo:
    def __get__(self, instance, owner):
        print('get')

4、描述符注意事项

  1. 描述符本身应该定义成新式类,被代理的类也应该是新式类;
  2. 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  3. 要严格遵循该优先级,优先级由高到底分别是
    • 类属性
    • 数据描述符
    • 实例属性
    • 非数据描述符
    • 找不到的属性触发 __ getattr __ ()

5、使用描述符

class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)


class People:
    name = Str('name')

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


# 疑问:如果我用类名去操作属性呢
try:
    People.name  # 报错,错误的根源在于类去操作属性时,会把None传给instance
except Exception as e:
    print(e)

输出:

get---> None <class '__main__.People'>
'NoneType' object has no attribute '__dict__'

修改代码,解决上述错误

class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)


class People:
    name = Str('name')

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


print(People.name)  # 完美,解决

输出:

get---> None <class '__main__.People'>
<__main__.Str object at 0x107a86da0>

6、自定制@property

利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

  1. property回顾
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @property
        def area(self):
            return self.width * self.length
    
    
    r1 = Room('alex', 1, 1)
    
    print(r1.area) # 调用area方法
    #输出
    1
    
  2. 自定制property
    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
            if instance is None:
                return self
            return self.func(instance)  # 此时你应该明白,到底是谁在为你做自动传递self的事情
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area) 相当于定义了一个类属性,即描述符
        def area(self): # 此时的self参数就是描述符中的instance
            return self.width * self.length
    
    
    r1 = Room('alex', 1, 1)
    
    print(r1.area)
    # 输出
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    
  3. 实现延迟计算功能
    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
            if instance is None:
                return self
            else:
                print('--->')
                value = self.func(instance)
                setattr(instance, self.func.__name__, value)  # 计算一次就缓存到实例的属性字典中
                return value
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符'
        def area(self):
            return self.width * self.length
    
    
    r1 = Room('alex', 1, 1)
    
    print(r1.area)  # 先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
    
    
    #这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    --->
    1
    
    print(r1.area)  # 先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
    1
    

    此结果的依据是因为上述描述符属于非数据描述符,而获取属性的优先级顺序是,实例属性 > 非数据描述符;

  4. 打破延时计算

    一个小小的改动,延时计算的的美梦就破碎了!

    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
            if instance is None:
                return self
            else:
                value = self.func(instance)
                instance.__dict__[self.func.__name__] = value
                return value
            # return self.func(instance) # 此时你应该明白,到底是谁在为你做自动传递self的事情
        def __set__(self, instance, value):
            print('hahahahahah')
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area) 相当于定义了一个类属性,即描述符
        def area(self):
            return self.width * self.length
    
    print(r1.area)
    print(r1.area)
    print(r1.area)
    #输出
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    

    此时再调用r1.area时,每次都会访问描述符,缓存功能失效。原因就是上述的描述符实现了 __ set __()方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了

7、描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethod,@property甚至是__slots__属性

描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件

原文出自:https://blog.csdn.net/qdPython/article/details/129203306

posted @ 2023-12-25 17:02  zchang  阅读(347)  评论(0)    收藏  举报