python高级之描述器

发布时间:2019-07-22 09:57:32编辑:auto阅读(1236)

    • 描述器用到的方法

        用到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 self
    
    class 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 self
    
    class 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._fn
      
      class A:
          @StaticMethod      # stmd = StaticMehtod(stmd)
          def stmd():
              print('stmd')
      
      print(A.__dict__)
      A.stmd()    # 类调用stmd属性

      • 实现ClassMethod装饰器,完成classmethod装饰器的功能

    • from functools import partial
      
      
      class 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 inspect
      
      class 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
      
      @pcheck
      class A:
      #     a = Typed(str)
      #     b = Typed(int)
          def __init__(self,a:str,b:int):
              self.a = a
              self.b = b
      
      A('1',2)

      描述器结合装饰实现

      import inspect
      
      
      class 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] = value
      
      class A:
          a = Typed('a',str)
          b = Typed('b',int)
          def __init__(self,a:str,b:int):
              self.a = a
              self.b = b
      
      a = A('1',2)
      print(a.__dict__)
      # print(type(a.a),type(a.b))
      print(a.a)

      描述器实现

      import inspect
      
      def 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 = b
      
      A('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__方法)
              使用装饰器获取参数注解,给类添加有描述器的类属性,再通过描述器的方式进行参数检查


关键字