Descriptor Descriptors

Creative Commons License Copyright: Attribution, allow others to create paper-based, and must distribute paper (based on the original license agreement with the same license Creative Commons )

1.1 descriptor performance

Magic use three methods: GET (), the SET (), the Delete ()
method signature as follows:
. Object GET (Self, instance, owner)
. Object the SET (Self, instance, owner)
. Object the Delete (Self, instance )
Self refers to the current example, the caller
instance is the owner instance
owner belongs to the class attributes

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)

  Method as defined __get__, is a class descriptor A, x B, or the class instance attributes of class B is read, a visiting instance of a class A, __get__ method is called.
   Note that with the description of the class is the main class attributes, but not with the instance attribute. Access to the data descriptor of the instance dictionary takes precedence over access, access to non-data descriptor is lower than the priority of access to the instance dictionary.

1.2 Definition of descriptor

  python in a class implements __get__, SET , __ any one delete__ three methods, that is descriptor. Implement some of these three methods, described to support the protocol.

  • Achieved only __get__, non-data descriptor is non-data descriptor
  • Achieved __get __, __ set__ data descriptor is the descriptor data

The descriptor 1.3python

  python process (including staticmethod () and a classmethod ()) are implemented as a non data descriptor, and thus can redefine instance covering methods. property () function is implemented as a data descriptor. Thus, examples of behavior can not be overridden 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__)

Note: non-data class descriptors may be redefined and the cover in the examples, the class data descriptor and the cover can not be redefined in the examples.

Exercise

1. To achieve StaticMethod decorator
2. To achieve ClassMethod decorator
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 pairs efficacy data instance is

Ideas:

  1. Write function, first check in __init__, if unqualified, direct Throws
  2. Decorators, using inspect complete module
  3. Descriptor
    first idea implementation code:
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: 类型错误

The second idea of ​​code to achieve:

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')  # 直接报错

The third idea of ​​code to achieve:

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}

The third process code implementation ideas, there is hard-coded, inelegant, the decorator can dynamically increase to the class method. Improved code is:

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__)

? Now you can use the function decorator class to dynamically add attributes, whether it would be changed to function decorator class decorator The answer is yes, see the detailed code:

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)

All methods are described in the python is

Guess you like

Origin blog.csdn.net/sqsltr/article/details/90598592