Python--描述器 Descriptors

描述器 Descriptors

  • 描述器表现
    • `__get__`
    • self、instance、owner`三个参数的意思
  • 描述器定义
  • 属性的访问顺序
    • `__set__`
    • 属性查找顺序
  • Python中的描述器
  • 新增方法
  • 练习
    • 实现StaticMethod装饰器
    • 实现ClassMethod装饰器
    • 对实例的数据进行校验

描述器表现

  • 用到的魔术方法:__get__()__set__()__delete()__
  • 方法签名如下
    object.__get__(self, instance, owner)
    object.__set__(self, instance, owner)
    object.__delete__(self, instance)
  • self值当前实例,调用者
  • instance是owner的实例
  • owner是属性的所属的类

__get__

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}{}{}".format(self, instance, owner))

class B:
    x = A()
    def __init__(self):
        print('B.init')

print('-' * 20)
print(B.x)
# print(B.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1'

print('=' * 20)
b = B()
print(b.x)
# print(b.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1'

# 运行结果
A.init
--------------------
A.__get__<__main__.A object at 0x000001A4C0198630>None<class '__main__.B'>
None
====================
B.init
A.__get__<__main__.A object at 0x000001A4C0198630><__main__.B object at 0x000001A4C0198668><class '__main__.B'>
None

因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对A的实例的访问,就会调用__get__方法

self、instance、owner`三个参数的意思

 __get__(self, instance, owner)方法的签名,会传入3个参数
 B.x调用返回<__main__.A object at 0x000001A4C0198630>None<class '__main__.B'>
 b.x调用返回<__main__.A object at 0x000001A4C0198630><__main__.B object at 0x000001A4C0198668><class '__main__.B'>
  • self 对应都是A的实例
  • owner 对应都是B类
  • instance 说明
    None表示不是B类的实例
    <__main__.B object at 0x000001A4C0198668> 表示是B的实例,对应调用B().x

使用返回值解决。返回self,就是A的实例,该实例有a1属性,返回正常

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}{}{}".format(self, instance, owner))
        return self # 解决返回None的问题

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.b = A() # 实行属性也指向一个A的实例

print('-' * 20)
print(B.x)
print(B.x.a1)

print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)

print(b.b) # 并没有触发__get__

# 执行结果
A.init
--------------------
A.__get__<__main__.A object at 0x0000024595758668>None<class '__main__.B'>
<__main__.A object at 0x0000024595758668>
A.__get__<__main__.A object at 0x0000024595758668>None<class '__main__.B'>
a1
====================
B.init
A.init
A.__get__<__main__.A object at 0x0000024595758668><__main__.B object at 0x00000245957586A0><class '__main__.B'>
<__main__.A object at 0x0000024595758668>
A.__get__<__main__.A object at 0x0000024595758668><__main__.B object at 0x00000245957586A0><class '__main__.B'>
a1
<__main__.A object at 0x00000245957586D8>

从运行结果可以看出,只要类属性类的实例才行

描述器定义

Python中,一个类实现了__get____set____delete__三个方法中的任何一个方法,就是描述器。实现三个中的某些方法,就支持了描述器协议

  • 仅实现__get__,就是非数据描述符 non-data descriptor
  • 实现了__get__和剩下两个任意一种,就是数据描述符 data descriptor

如果一个类的类属性设置为描述器实例,name它被称为owner属主
当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法

属性的访问顺序

为上例中的类B增加实例属性x

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}{}{}".format(self, instance, owner))
        return self # 解决返回None的问题

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = 'b.x' # 增加实例属性x

print('-' * 20)
print(B.x)
print(B.x.a1)

print('=' * 20)
b = B()
print(b.x)
# print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'

# 执行结果
A.init
--------------------
A.__get__<__main__.A object at 0x0000021C903C86A0>None<class '__main__.B'>
<__main__.A object at 0x0000021C903C86A0>
A.__get__<__main__.A object at 0x0000021C903C86A0>None<class '__main__.B'>
a1
====================
B.init
b.x

类A只实现了__get__()方法,b.x访问到了实例的属性,而不是描述器

__set__

为类A增加__set__方法```

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print("A.__get__{}{}{}".format(self, instance, owner))
        return self # 解决返回None的问题

    def __set__(self, instance, value):
        print("A.__set__{}{}{}".format(self, instance, value))
        self.data = value

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = 'b.x' # 增加实例属性x

print('-' * 20)
print(B.x)
print(B.x.a1)

print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
print(b.x.data)

# 执行结果
A.init
--------------------
A.__get__<__main__.A object at 0x0000021C2C9486A0>None<class '__main__.B'>
<__main__.A object at 0x0000021C2C9486A0>
A.__get__<__main__.A object at 0x0000021C2C9486A0>None<class '__main__.B'>
a1
====================
B.init
A.__set__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8>b.x
A.__get__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8><class '__main__.B'>
<__main__.A object at 0x0000021C2C9486A0>
A.__get__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8><class '__main__.B'>
a1
A.__get__<__main__.A object at 0x0000021C2C9486A0><__main__.B object at 0x0000021C2C9486D8><class '__main__.B'>
b.x

所有的b.x就会访问描述器的__get__()方法,代码中返回的self就是描述器实例,它的实例字典中就保存着a1data属性,可以打印b.x.__dict__看这些属性

属性查找顺序

实例的__dict__优先于非数据描述器
数据描述器优先于实例的__dict__
__delete__方法有同样的效果,有了这个方法,也就是数据描述器

Python中的描述器

  • 描述器在Python中应用非常广泛
  • Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为

property()函数实现为一个数据描述器。因此,实例不能覆盖属性和行为

class A:
    @classmethod
    def foo(cls): # 非数据描述器
        pass

    @staticmethod
    def bar(): # 非数据描述器
        pass

    @property
    def z(self): # 数据描述器
        return 5

    def getfoo(self): # 非数据描述器
        return self.foo

    def __init__(self): # 非数据描述器
        self.foo = 100
        self.bar = 200
        # self.z = 300

a = A()
print(a.__dict__) # {'foo': 100, 'bar': 200}
print(A.__dict__)

foo、bar都可以在事例中覆盖,但是z不可以

新增方法

3.6新增描述器方法__set_name__,它在属主类构建的时候就会调用

class A:
    def __init__(self):
        print('A init')

    def __get__(self, instance, owner):
        print(1, self, instance, owner)
        return self

    def __set_name__(self, owner, name):
        print(2, self, owner, name)
        self.name = name

class B:
    x = A() # 类属性创建时调用描述器的__set_name__方法

print('-' * 30)
print(B().x)

# 执行结果
A init
2 <__main__.A object at 0x0000024F08AB47B8> <class '__main__.B'> x
------------------------------
1 <__main__.A object at 0x0000024F08AB47B8> <__main__.B object at 0x0000024F08AB8400> <class '__main__.B'>
<__main__.A object at 0x0000024F08AB47B8>

提供这个方法,就是可以知道属主类和属主类的类属性名

练习

实现StaticMethod装饰器

  • 实现StaticMethod装饰器,完成staticmethod装饰器的功能
class StaticMethod:
    def __init__(self, fn):
        self._fn = fn

    def __get__(self, instance, owner):
        return self._fn

class A:
    @StaticMethod
    # stmtd = StaticMethod(stmtd)
    def stmtd():
        print('static method')

A.stmtd()
A().stmtd()

# 执行结果
static method
static method

实现ClassMethod装饰器

  • 实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial

# 类classmethod装饰器
class ClassMethod:
    def __init__(self, fn):
        self._fn = fn

    def __get__(self, instance, cls):
        ret = partial(self._fn, cls)
        return ret

class A:
    @ClassMethod
    # clsmtd = ClassMethod(clsmtd)
    # 调用a.clsmtd() 或者 A().clsmtd()
    def clsmtd(cls):
        print(cls.__name__)

print(A.__dict__)
print(A.clsmtd)
A.clsmtd()
A().clsmtd()

对实例的数据进行校验

class Person:
	def __init__(self, name:str, age:int):
		self.name = name
		self.age = age

对上面的类的实例的属性name、age进行数据校验

import inspect

class TypeCheck:
    def __init__(self, name, typ):
        self.name = name
        self.type = typ

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

    def __set__(self, instance, value):
        print('set~~~~')
        if not isinstance(value, self.type):
            raise TypeError(value)
        instance.__dict__[self.name] = value

def typeinject(cls):
    sig = inspect.signature(cls)
    params = sig.parameters
    for name,param in params.items():
        print(name, param)
        if param.annotation != param.empty:
            setattr(cls, name, TypeCheck(name, param.annotation))
    return cls

@typeinject
class Person:
    # 类属性,由装饰器注入
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age

print(Person.__dict__)

p1 = Person('tom', 20)
p2 = Person('jerry', '18')

Python--描述器 Descriptors_第1张图片

  • 因为p2的age传参没有按照int类型传参,所以会抛出异常TypeError: 18
    将上例函数装饰器改为类装饰器
import inspect

class TypeCheck:
    def __init__(self, name, typ):
        self.name = name
        self.type = typ

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

    def __set__(self, instance, value):
        print('set~~~~')
        if not isinstance(value, self.type):
            raise TypeError(value)
        instance.__dict__[self.name] = value

class TypeInject:
    def __init__(self, cls):
        self.cls = cls
        sig = inspect.signature(cls)
        params = sig.parameters
        for name,param in params.items():
            print(name, param)
            if param.annotation != param.empty: # 注入类属性
                setattr(cls, name, TypeCheck(name, param.annotation))
    def __call__(self, *args, **kwargs):
        return self.cls(*args, **kwargs) # 新构建一个新的Person对象
@TypeInject # Person = TypeInject(Person)
class Person:
    # 类属性,由装饰器注入
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age

print(Person.__dict__)

p1 = Person('tom', 20)
p2 = Person('tom', 19)

你可能感兴趣的:(Python--描述器 Descriptors)