描述器Descriptors

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

1.1 描述器的表现

用到三个魔术方法:get()、set()、delete()
方法的签名如下:
object.get(self, instance, owner)
object.set(self, instance, owner)
object.delete(self, instance)
self指代当前实例,调用者
instance是owner的实例
owner是属性所属的类

class A:  # 数据描述器的访问优先级要高于实例__dict__的访问;非数据描述器的访问,优先级要低于实例字典的访问
    def __init__(self):
        self.a1 = 'a1'
        print('A init')

    def __get__(self, instance, owner):  # instance为owner的实例
        print('get~~~~~~~~', self, instance, owner)
        # print(self.__dict__)
        return self

    def __set__(self, instance, value):  # 可以禁止修改实例的属性;主要看方法里的内容
        print('set~~~~~~~~~~~', value, instance)
        # self.data = value  # 保存在A的实例里了

        # setattr(instance, 'x', value)  # 无限递归
        # instance.data = value
        # instance.__dict__['x'] = value
        if instance:
            raise Exception('不许改')

    def __set_name__(self, owner, name):  # python3.6新增的
        print(owner, name)
        self.name = name


class B:  # 属主
    x = A()  # 类属性可以,描述器和属主类的类属性有关;解释器执行到这一句时,会调用__set_name__方法
    z = 5

    def __init__(self):
        # self.y = A()  # 实例属性不会,描述器与属主类的实例属性无关
        self.x = 'b.x'  # 动态增加属性
        print('B init')


print('~~~~~~~~~~~~~~~~~')
print(B.x)  # 会调用A实例的__get__方法;instance为None,B.x或B().x才会调用描述器
print(B.x.a1)  # instance为None;注意是B.x调用了描述器
print('++++++++++++++++++')
b = B()
print(b.x)  # instance为<__main__.B object at 0x0000000001E89668>
print(b.x.a1)
print('~~~~~~~~~~~~~~~')
b = B()
print(b.x)  # <__main__.A object at 0x0000000000789630>
print(b.__dict__)  # {'x': 'b.x'}
print('~~~~~~~~~~~~~~~~~~~~~~')
b.x = 500  # 会调用描述器的__set__方法
print(b.x)  # <__main__.A object at 0x00000000027C9668>
print(b.__dict__)  # {'x': 500}
print("++++++++++++++++")
B.x = 600  # 如果类的类属性x是描述器,那么不要使用这样的赋值语句
print(b.x)  # 500
print(B.x)  # 600

print('~~~~~~~~~~~~~~~')
b = B()
b.x = 100
print(b.x)

  因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A实例的访问,就会调用__get__方法。
   注意描述器与属主类的类属性有关,与实例的属性无关。数据描述器的访问优先级要高于实例字典的访问,非数据描述器的访问优先级要低于实例字典的访问。

1.2 描述器的定义

  python中,一个类实现了__get__、set、__delete__三个方法中的任意一个,就是描述器。实现这三个中的某些方法,就支持了描述器协议。

  • 仅实现了__get__,就是非数据描述器 non-data descriptor
  • 实现了__get__、__set__就是数据描述器 data descriptor

1.3python中的描述器

  python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此实例可以重新定义和覆盖方法。property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。

class A:
    @classmethod
    def foo(cls):  # 非数据描述符——> foo = classmethod(foo)
        pass

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

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

    def get_foo(self):  # 非数据描述符
        return self.foo

    def __init__(self):  # 非数据描述符
        self.foo = 100
        self.bar = 200
        # self.z = 300  # 不能覆盖,会报错,因为z为数据描述符


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

注意:类的非数据描述符在实例中可以被重新定义和覆盖,类的数据描述符在实例中不能被重新定义和覆盖。

练习

1. 实现StaticMethod装饰器
2. 实现ClassMethod装饰器
from functools import partial


class StaticMethod:
    def __init__(self, fn):
        self.fn = fn

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


class ClassMethod:
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, instance, owner):
        return partial(self.fn, owner)  # 固定owner即属主类


class D:
    @StaticMethod  # stmd = StaticMethod(stmd) 非数据描述器
    def stmd(x, y):
        print('static method', x, y)

    @ClassMethod  # foo = ClassMethod(foo)
    def foo(cls, x, y):
        print(cls.__name__, x, y)


d = D()
d.stmd(4, 5)  # static method 4 5

d2 = D()
d2.foo(5, 6)  # D 5 6

1.4 对实例的数据进行效验

思路:

  1. 写函数,在__init__中先检查,如果不合格,直接抛异常
  2. 装饰器,使用inspect模块完成
  3. 描述器
    第一种思路的实现代码:
class Person:
    def __init__(self, name: str, age: int):
        params = ((name, str), (age, int))
        if not self.check_data(params):
            raise TypeError('类型错误')
        self.name = name
        self.age = age

    def check_data(params):
        for p, typ in params:
            if not isinstance(p, typ):
                return False
        return True


# p1 = Person('tom', '20')  # TypeError: 类型错误

第二种思路的代码实现:

from functools import wraps
import inspect


def type_check(cls):
	@wraps(cls)
    def wrapper(*args, **kwargs):
        sig = inspect.signature(cls)
        params = sig.parameters  # OrderedDict
        # print(params)
        # values = list(params.values())
        # keys = list(params.keys())
        # for i, p in enumerate(args):
        #     if values[i].annotation != inspect._empty and  not isinstance(p, values[i].annotation):
        #         raise TypeError('Wrong param={} {}'.format(keys[i], p))

        for p, (k, v) in zip(args, params.items()):
            if v.annotation is not v.empty and not isinstance(p, v.annotation):
                raise TypeError('Wrong param= {} {}'.format(k, p))

        for k, v in kwargs.items():
            if params[k].annotation is not v.empty:  # inspect._empty
                if not isinstance(v, params[k].annotation):
                    raise TypeError('Wrong param={} {}'.format(k, v))
        return cls(*args, **kwargs)
    return wrapper


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


p1 = Person('tony', 20)
p2 = Person('jacky', '18')  # 直接报错

第三种思路的代码实现:

class TypeCheck:  # 描述器
    def __init__(self, typ):
        self.type = typ

    def __get__(self, instance, owner):
        # print('get~~~~~~~~~~~~~~')
        if instance:
            return instance.__dict__[self.name]
        else:
            raise Exception  # 或者return self,总之不正常

    def __set__(self, instance, value):
        # print('set~~~~~~~~~~~~~')
        if instance:
            if not isinstance(value, self.type):
                raise TypeError(self.name, '+++++++++++')
            else:
                instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样

    def __set_name__(self, owner, name):  # python3.6新增的方法
        print(name)
        self.name = name


class Person:
    name = TypeCheck(str)  # 硬编码,不优雅
    age = TypeCheck(int)

    def __init__(self, name: str, age: int):
        self.name = name  # 会调用TypeCheck实例的__set__方法(描述器)
        self.age = age


p3 = Person('curry', 31)
p4 = Person('durant', 29)  # 直接抛出异常
print(p3.__dict__)  # {'name': 'curry', 'age': 31}
print(p4.__dict__)  # {'name': 'durant', 'age': 29}

第三种思路代码实现的过程中,存在硬编码,不优雅,可以使用装饰器动态的给类增加方法。改进后的代码为:

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]
        else:
            raise Exception  # 或者return self,总之不正常

    def __set__(self, instance, value):
        # print('set~~~~~~~~~~~~~')
        if instance:
            if not isinstance(value, self.type):
                raise TypeError(self.name, '+++++++++++')
            else:
                instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样

    # def __set_name__(self, owner, name):  # python3.6新增的方法
    #     print(name)
    #     self.name = name


def type_check(cls):
    sig = inspect.signature(cls)
    params = sig.parameters
    # print(params)  # 有序字典
    for name, param in params.items():
        if param.annotation is not param.empty:
            setattr(cls, name, TypeCheck(name, param.annotation))
    return cls


# 注意动态给类增加属性(方法)时,__set_name__方法并没有调用,即此方法无效
@type_check  # Person = type_check(Person)
class Person:
    # name = TypeCheck(str)  # 硬编码,不优雅
    # age = TypeCheck(int)

    def __init__(self, name: str, age: int):
        self.name = name  # 会调用TypeCheck实例的__set__方法(描述器)
        self.age = age


p3 = Person('curry', 31)
p4 = Person('durant', 29)  # 直接抛出异常
print(p3.__dict__)  # {'name': 'curry', 'age': 31}
print(p4.__dict__)  # {'name': 'durant', 'age': 29}
print(Person.__dict__)

既然可以使用函数装饰器给类动态增加属性,那么可否将函数装饰器改为类装饰器呢?答案是肯定的,请见详细代码:

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]
        else:
            raise Exception  # 或者return self,总之不正常

    def __set__(self, instance, value):
        # print('set~~~~~~~~~~~~~')
        if instance:
            if not isinstance(value, self.type):
                raise TypeError(self.name, '+++++++++++')
            else:
                instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样

    # def __set_name__(self, owner, name):  # python3.6新增的方法
    #     print(name)
    #     self.name = name


class TypeInject:
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwarg):
        sig = inspect.signature(self.cls)
        params = sig.parameters
        # print(params)  # OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
        for name, param in params.items():
            # print(name, param.annotation)
            if param.annotation != param.empty:  # inspect._empty
                setattr(self.cls, name, TypeCheck(name, param.annotation))

        return self.cls(*args, **kwarg)


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


p5 = Person('Green', 28)

python中所有方法都是描述器

猜你喜欢

转载自blog.csdn.net/sqsltr/article/details/90598592