• 描述器用到的方法

    用到3个魔术方法: __get__()、__set__()、__delete__()    方法使用格式:        obj.__get__(self, instance, owner)        obj.__set__(self, instance, value)        obj.__delete__(self, instance)    self: 指当前类的实例本身    instance: 指owner的实例    owner: 指当前实例作为属性的所属类

代码一

以下代码执行过程:    定义B类时,执行A()赋值操作,进行A类的初始化,再打印B类调用类属性x的a1属性    紧接着执行B类的初始化,通过b实例调用类属性的x的a1属性class A:    def __init__(self):        self.a1 = 'a1'        print('A.init')class B:    x  = A()    def __init__(self):        print('B.init')print('-' * 20)print(B.x.a1)print('*' * 20)b = B()print(b.x.a1)

  • 描述器定义

Python中,一个类中实现了__get__、__set__、__delete__三个方法中的任何一个方法,那么这个类就是描述器.如果仅实现了__get__,就是非数据描述符 non-data descriptor同时实现了除__get__以外的__set__或__delete__方法,就是数据描述符 data descriptor如果一个类的类属性设置为描述器,那么它被称为此描述器的owner属主描述器方法何时被触发:    当属主类中对是描述器的类属性进行访问时(即类似b.x),__get__方法被触发    当属主类中对是描述器的实例属性通过'.'号赋值时,__set__方法被触发

代码二

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 B.x为None,None没有a1属性print('*' * 20)b = B()# print(b.x.a1)  # AttributeError B.x为None,None没有a1属性调用B类的类属性,被A类__get__方法拦截,并返回值None

代码三

class A:    def __init__(self):        self.a1 = 'a1'        print('A.init')    def __get__(self,instance,owner):        print('A.__get__ {} {} {}'.format(self,instance,owner))        return selfclass B:    x  = A()    def __init__(self):        print('B.init')print('-' * 20)print(B.x)print(B.x.a1)   print('*' * 20)b = B()print(b.x)print(b.x.a1)  解决上例中的返回值为None,将A类的实例返回,可成功调用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 selfclass B:    x  = A()    print(x)    def __init__(self):        print('B.init')#         self.x = 100    #  实例调用x属性时,直接查实例自己的__dict__        self.x = A()      # 实例调用x属性时,不进入A类的__get__方法        print(self.x)      print('-' * 20)print(B.x)    # __get__print(B.x.a1)    # __get__print('*' * 20)b = B()print(b.x)print(b.x.a1) 总结: 不论是实例还是类,只要是访问了是描述器的类属性,都会被描述器的__get__方法拦截

  • 属性的访问顺序(本质)

代码五

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    def __set__(self,instance,value):        print('A.__set__ {} {} {}'.format(self,instance,value))class B:    x  = A()    print(x)    def __init__(self):        print('B.init')        self.x = 100#         self.x = A()   # 同上面100结果类似        print(self.x)# print('-' * 20)# print(B.x)# print(B.x.a1)   # print('*' * 20)b = B()# print(b.x)# print(b.x.a1)  print(b.__dict__)print(B.__dict__)屏蔽A类的__set__方法,实例的__dict__为{'x': 100}不屏蔽A类的__set__方法,实例的__dict__为{}__set__方法本质将实例的__dict__的属性名清空,从而达到数据描述器优先于查实例字典的假象
  • Python中的描述器

描述器在Python中应用非常广泛Python的方法(包括staticmethod()和classmethod()) 都实现为非数据描述器.因此,实例可以通过'.'号进行生成属性.property()函数实现为一个数据描述器.则实例不能使用'.'号进行赋值属性.

示例

class A:    @classmethod    def foo(cls):        pass    @staticmethod    def bar():        pass    @property    def z(self):        return 5    def __init__(self): # 非数据描述器        self.foo = 100        self.bar = 200#         self.z = 300    # z属性不能使用实例覆盖a = A()print(a.__dict__)print(A.__dict__)

  • 练习

    • 实现StaticMethod装饰器,完成staticmethod装饰器的功能

  • class StaticMethod:    def __init__(self,fn):        self._fn = fn    def __get__(self,instance,owner):        print(self,instance,owner)        return self._fnclass A:    @StaticMethod      # stmd = StaticMehtod(stmd)    def stmd():        print('stmd')print(A.__dict__)A.stmd()    # 类调用stmd属性
    • 实现ClassMethod装饰器,完成classmethod装饰器的功能

  • from functools import partialclass ClassMethod:    def __init__(self,fn):        self._fn = fn    def __get__(self,instance,owner):        print(self,instance,owner)        return partial(self._fn,owner)              # 使用partial函数将类给作为默认参数class A:    @ClassMethod       # clsmd = ClassMethod(clsmd)    def clsmd(cls):        print('cls',cls.__name__)print(A.__dict__)A.clsmd()
    • 类初始化的参数检查

  • import inspectclass Typed:    def __init__(self,tp):        self._tp = tp    def __get__(self,instance,owner):        pass    def __set__(self,instance,value):        if not isinstance(value,self._tp):            raise ValueError(value)        setattr(instance.__class__,self._name,value)def pcheck(cls):    def wrapper(*args):        sig = inspect.signature(cls)        params = sig.parameters        for i,(name,param) in enumerate(params.items()):            if param.empty != param.annotation:#                 if not isinstance(args[i],param.annotation):#                     raise ValueError(args[i])                setattr(cls,name,Typed(param.annotation))        return cls(*args)    return wrapper@pcheckclass A:#     a = Typed(str)#     b = Typed(int)    def __init__(self,a:str,b:int):        self.a = a        self.b = bA('1',2)

    描述器结合装饰实现

    import inspectclass Typed:    def __init__(self,name,tp):        self._name = name        self._tp = tp    def __get__(self,instance,owner):        print('get',self,instance,owner)        return instance.__dict__[self._name]    def __set__(self,instance,value):        print('set',self,instance,value)        if not isinstance(value,self._tp):            raise ValueError(value)        instance.__dict__[self._name] = valueclass A:    a = Typed('a',str)    b = Typed('b',int)    def __init__(self,a:str,b:int):        self.a = a        self.b = ba = A('1',2)print(a.__dict__)# print(type(a.a),type(a.b))print(a.a)

    描述器实现

    import inspectdef pcheck(cls):    def wrapper(*args):        sig = inspect.signature(cls)        params = sig.parameters        for i,(_,param) in enumerate(params.items()):            if param.empty != param.annotation:                if not isinstance(args[i],param.annotation):                    raise ValueError(args[i])        return cls(*args)    return wrapper@pcheck    # A = pcheck(A)class A:    def __init__(self,a:str,b:int):        self.a = a        self.b = bA('1','2')

    装饰器版本

    class A:

        def __init__(self,a:str,b:int):
            if not (isinstance(a,str) and isinstance(b,int)):
                raise ValueError(a,b)
            else:
                self.a = a
                self.b = b
    A('1',2)

    直接参数检查

    思路:

        实现参数检查的本质是判断传入的参数是否符合形参定义的类型,也就是用isinstance进行判断.
        因此参数检查的不同实现的区别在于在哪些地方拦截传入的参数,来进行检查.
        上述实现的拦截地方:
            在类初始化时,在对实例属性赋值之前拦截
            使用装饰器,和inspect模块,在实例化之前进行参数检查
            使用描述器,在初始化时对实例属性设置时,触发描述器的__set__方法,在__set__方法中进行参数检查,再对其实例的类添加类属性
                (如果添加在实例上,则会递归调用回到__set__方法)
            使用装饰器获取参数注解,给类添加有描述器的类属性,再通过描述器的方式进行参数检查