zoukankan      html  css  js  c++  java
  • python的魔术方法(描述器)

    一、描述器 Descriptors

    • 描述器的表现:用到3个魔术方法:__get__(),__set__(),__delete__()
    格式:object.__get__(self,instance,owner)
          object.__set__(self,instance,value)
          object.__delete__(self,instance)
    self指当前实例,调用者;instance是owner实例;owner是属性的所属的类
    
        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)
    可以看出上面代码执行过程:类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,先打印A.init
    然后执行到打印B.x.a1,然后实例化并初始化B的实例b,打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值


    1、下面代码加入__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  #解决返回值None的问题
    
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
    
    print('-'*20)
    print(B.x)
    #print(B.x.a1)    #抛出异常AttributeError
    
    print('='*20)
    b = B()
    print(b.x)
    # print(b.x.a1)  #抛出异常AttributeError
    
    执行输出:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x00000000049CB518>,None,<class '__main__.B'>
    None
    ====================
    B.init
    A.__get__<__main__.A object at 0x00000000049CB518>,<__main__.B object at 0x00000000049CB630>,<class '__main__.B'>
    None
    
    因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取称为对类A的实例的访问,就会调用__get__方法
    
    self都是A的实例,owner都是B的类,instance说明
    #None表示没有B类的实例,对应调用B.x


    二、描述器的定义

    • python中,一个类实现了__get__,__set__,__delete__三个中的任何一个方法就是描述器
    • 如果仅实现了__get__,就是非数据描述器
    • 同时实现了__get__,__set__就是数据描述器
    • 如果一个类的类属性设置为描述器,那么它被称为owner属主

    1、属性的访问顺序

    • 实例的__dict__优先于非数据描述器,数据描述器优先于实例的__dict__
    class A:
        def __init__(self):
            print(111111111111111111)
            self.a1 = 'a1'
            print('A.init')
        
        def __get__(self,instance,owner):
            print(22222222222222222222)
            print("A.__get__{},{},{}".format(self,instance,owner))
            return self  #解决返回值None的问题
        
        def __set__(self,instance,value):
            print(33333333333333333333333)
            print('A.__set__ {},{},{}'.format(self,instance,value))
            self.data = value
    
    class B:
        x = A()
        def __init__(self):
            print(4444444444444444444444)
            print('B.init')
            self.x = 'b.x' #增加实例属性x
    
    # print('-'*20)
    # print(B.x)
    # print(B.x.a1)    #抛出异常AttributeError
    
    print('='*20)
    b = B()
    print(b.x)
    print(b.x.a1)  #抛出异常AttributeError
    
    
    使用set和不使用set看出,原来不是什么数据描述器优先级高,而是把实例的属性从__dict__中去除了,
    造成该属性如果是数据描述器优先访问的假象,说到底,属性访问顺序从来没变过


    2、python中的描述器

    • 描述器在python中应用非常广泛,python的方法都实现为非数据描述器;因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一个类的其他实例不同的行为
    • 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,bar都可以在实例中覆盖,但是z不可以    
        
        
    #实现StaticMethod装饰器,实现staticmethod装饰器的功能
    
    #类staticmethod装饰器
    
    class StaticMethod:
        def __init__(self,fn):
            self._fn = fn
        
        def __get__(self,instance,owner):
            return self._fn
    
    class A:
        @StaticMethod
        def stmtd():
            print('static method')
    
    A.stmtd()
    A().stmtd()    
        
    #A.clsmtd()的意思就是None(),一定报错,怎么修改
    
    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  # ClassMethod(clsmtd)
        def clsmtd(cls):
            print(cls.__name__)
    
    print(A.__dict__)
    A.clsmtd
    A.clsmtd()
        


    3、对实例的数据进行校验

    •  思路:写函数,在__init__中先检查,如果不合格,直接抛异常;使用装饰器,使用inspect模块完成
    # class Person:
    #     def __init__(self, name:str, age:int):
    #         params = ((name,str),(age,int))
    #         print(params)
    #         if not self.checkdata(params):
    #             raise TypeEror()
    #         self.name = name 
    #         self.age = age
        
    #     def checkdata(self, params):
            
    #         for p, t in params:
    #             print(p,t)
    #             if not isinstance(p,t):
    #                 return False
                
    #         return True
        
    # p = Person('tom', 20)       
        
    
    #描述器方式 ,写入实例属性的时候做检查
    
    class Typed:
        def __init__(self, name, type):
            self.name = name
            self.type = type
        
        def __get__(self, instance, owner):
            if instance is not None:
                return instance.__dict__[self.name]
            return self
        
        def __set__(self, instance, value):
            if not isinstance(value, self.type):
                raise TypeError(value)
            instance.__dict__[self.name] = value
    
    class Person:
        name = Typed('name', str)
        age = Typed('age', int)
        
        def __init__(self, name:str, age:int):
            self.name = name
            self.age = age
    
    p = Person('tom',20)
        
    #代码看似不错,但是有硬编码,能否直接获取形参类型,使用inspect模块
    #测试如下:
            import inspect
            params = inspect.signature(Person).parameters
            print(params)
        
        
     
    #使用inspect模块完成    
    import inspect
    
    class Typed:
        def __init__(self, name, age):
            self.name = name
            self.type = type
        
        def __get__(self, instance, owner):
            if instance is not None:
                return instance.__dict__[self.name]
            
            return self
        
        def __set__(self, instance, value):
            if not isinstance(value, self.type):
                raise TypeError(value)
            
            instance.__dict__[self.name] = value
    
    def typeassert(cls):
        params = inspect.signature(cls).parameters
        print(params)
        for name,param in params.items():
            print(param.name, param.annotation)  # 注入类属性
            if param.annotation != param.empty:
                setattr(cls, name, Typed(name, param.annotation))
        
        return cls
    
    @typeassert
    class Person:
        def __init__(self, name:str, age:int):
            self.name = name
            self.age = age
            
        def __repr__(self):
            return "{} is {}".format(self.name, self.age)
    
    #p = Person('tom','20')
    p = Person('tom', 20)
    print(p)  
  • 相关阅读:
    How much training data do you need?
    什么样的论文容易接收发表?​
    How to Write and Publish a Scientific Paper: 7th Edition(科技论文写作与发表教程)(11.04更新)
    如何起草你的第一篇科研论文——应该做&避免做
    Writing the first draft of your science paper — some dos and don’ts
    matlab学习笔记 bsxfun函数
    乘法器的Verilog HDL实现
    重构:改善既有代码的设计
    【Leetcode】Length of Last Word
    Saving HDU hdu
  • 原文地址:https://www.cnblogs.com/jiangzuofenghua/p/11448848.html
Copyright © 2011-2022 走看看