• Python 面向对象(五) 描述器


    使用到了__get__,__set__,__delete__中的任何一种方法的类就是描述器 

     描述器的定义

    一个类实现了__get__,__set__,__delete__中任意一个,这个类就是描述器。

    如果只实现了__get__,就叫非数据描述器non-data descriptor

    如果同时实现了__get__,__set__,那就叫数据描述器data descriptor。

     如果一个类的类属性的值设置为了描述器,那这个类就称为owner(属主)。

      

    回顾下就会发现我们曾用过@property(property是一个类)来将一个方法转为属性,其实property就是一个描述器。

    class A:
        def __init__(self):
            self.a1 = 'a1'
    
    class B:
        x = A()
        def __init__(self):
            pass
    
    print(B.x.a1)
    ------运行结果-----
    a1
    

      看下执行流程

    class A:
        def __init__(self):
            print('A.init')
            self.a1 = 'a1'
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
            pass
    
    print(B.x.a1)
    -----运行结果———
    A.init
    a1       # B根本就没有实例化
    

      一步一步加,慢慢观察实例的初始化顺序

    class A:
        def __init__(self):
            print('A.init')
            self.a1 = 'a1'
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
            self.x = 100
    
    print(B.x.a1)
    
    b = B()
    print(B.x.a1)
    print(b.x.a1)    # 抛出下面的AttributeError异常,因为按属性的mro搜索顺序来说,b.x也就是实例的x=100,100没有a1属性
    -----运行结果-----
    A.init
    a1
    B.init
    a1
    Traceback (most recent call last):
      File "C:/python/1117/description_test1.py", line 16, in <module>
        print(b.x.a1)
    AttributeError: 'int' object has no attribute 'a1'
    

      实例中可以覆盖非数据描述器

    class A:
        def __init__(self):
            print('A.init')
            self.a1 = 'a1'
        def __get__(self, instance, owner):
            print(self,instance,owner)
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
            # self.x = 100
    
    # print(B.x.a1)
    print(B.x)  # 返回的是NOne
    
    b = B()
    print(B.x)
    print(b.x)   #描述器跟类属性有关系
    ———运行结果——————————
    A.init
    <__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'>
    None
    B.init
    <__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'>
    None
    <__main__.A object at 0x00000271CBDA1668> <__main__.B object at 0x00000271CBDA18D0> <class '__main__.B'>
    None
    

      

    class A:
        def __init__(self):
            print('A.init')
            self.a1 = 'a1'
        def __get__(self, instance, owner):
            print(self,instance,owner)
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
            # self.x = 100
            self.x= A()
    
    # print(B.x.a1)
    # print(B.x)  # 返回的是NOne
    
    b = B()
    print(B.x)
    print(b.x)   #描述器跟类属性有关系
    print(b.x.a1)
    ————运行结果—————
    A.init
    B.init
    A.init
    <__main__.A object at 0x000001A6D1031668> None <class '__main__.B'>
    None
    <__main__.A object at 0x000001A6D1031860>
    a1
    

      

    第一种情况:
    A类的类属性的值等于B类的实例,且B类实现了__get__,__set__,__delete__三种方法中任意一个时,也就是说B类是一个描述器,就会触发定义的方法(如__get__)

    第二种情况:
    如果A类的实例的值等于B类的实例,即使B类是一个描述器,也不会触发其中的三种方法

    __set__

    class A:
        def __init__(self):
            print('A.init')
            self.a1 = 'a1'
        def __get__(self, instance, owner):
            print(self,instance,owner)
            return self
        def __set__(self, instance, value):
            print(self,instance,value)
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
            self.x = 100   #实例的属性
            # self.x= A()
    
    
    b = B()
    print(B.x)
    print(b.x.a1)
    
    print(b.__dict__)
    print(B.__dict__)
    ——————运行结果——————
    A.init
    B.init
    <__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> 100
    <__main__.A object at 0x00000193299127F0> None <class '__main__.B'>
    <__main__.A object at 0x00000193299127F0>
    <__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> <class '__main__.B'>
    a1
    {}   #添加了__set__方法之后,b实例的字典没有任何属性
    {'__module__': '__main__', '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>, '__init__': <function B.__init__ at 0x0000019329902E18>, '__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x00000193299127F0>}
    

      添加了一个__set__方法后结果就发生了变化。

    结论:

    如果一个类的类属性是一个数据描述器的话,你对它的同名属性的操作,相当于操作类属性。

    官方的说明是说:

    如果一个类的类属性是一个数据描述器的话,在实例中定义了同名的属性时,类属性的查找顺序优先级优于实例属性。

    然而,我们在上例中打印的__dict__字典中可以看到,根本不是什么优先级关系,实例的字典中根本就不存在同名的属性,同名的属性只存在于类的__dict__字典中。

    所以实例的属性名与类属性名同名时,其实就是在操作类属性。

    在实例中定义一个不同名的属性举例测试下:

    class A:
        def __init__(self):
            print('A.init')
            self.a1 = 'a1'
        def __get__(self, instance, owner):
            print(self,instance,owner)
            return self
        def __set__(self, instance, value):
            print(self,instance,value)
    
    class B:
        x = A()
        def __init__(self):
            print('B.init')
            self.y = 100  #不与类属性同名的属性 
            # self.x= A()
    
    
    b = B()
    print(B.x)
    print(b.x.a1)
    print(b.y)
    
    print(b.__dict__)
    print(B.__dict__)
    ————运行结果—————
    A.init
    B.init
    <__main__.A object at 0x0000012BEE4C17F0> None <class '__main__.B'>
    <__main__.A object at 0x0000012BEE4C17F0>
    <__main__.A object at 0x0000012BEE4C17F0> <__main__.B object at 0x0000012BEE4C18D0> <class '__main__.B'>
    a1
    100
    {'y': 100}  #不与类属性同名时,不会改变属性搜索顺序
    {'__init__': <function B.__init__ at 0x0000012BEE4B2E18>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, 'x': <__main__.A object at 0x0000012BEE4C17F0>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'B' objects>}
    

      

    练习:

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

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

    from functools import partial
    
    class StaticMethod:  #防止冲突改名
        def __init__(self,fn):
            self.fn = fn
        def __get__(self, instance, owner):
            print(self,instance,owner)
            return self.fn
    
    class ClassMethod:
        def __init__(self,fn):
            print(fn)
            self.fn = fn
        def __get__(self, instance, owner):
            print(self,instance,owner)
            # return self.fn(owner)
            return partial(self.fn,owner)  #partail偏函数固定某个参数,就返回了一个newfunction新函数
    
    class A:
        @StaticMethod
        def foo(): #foo = StaticMethod(foo)
            print('foo')
        @ClassMethod
        def bar(cls): #bar=ClassMethod(bar)
            print(cls.__name__)
    
    f = A.foo
    print(f)
    f()
    print('~~~~~~~~~~~~~')
    b = A.bar
    print(b)
    b()   #需要调用必须是函数
    -----------运行结果---------
    <function A.bar at 0x000001956B9D4840>
    <__main__.StaticMethod object at 0x000001956B8728D0> None <class '__main__.A'>
    <function A.foo at 0x000001956B9D47B8>
    foo
    ~~~~~~~~~~~~~
    <__main__.ClassMethod object at 0x000001956B872940> None <class '__main__.A'>
    functools.partial(<function A.bar at 0x000001956B9D4840>, <class '__main__.A'>)
    A
    

      

    与对象绑定的叫方法
    与function绑定的就叫函数

    普通函数就用@staticmethod
                          def stcmth()
    不想传参就用静态方法


    类的方法就用@classmethod
                          def clsmtd(cls)
    类或实例的类(解释器隐含a.__class__)传入cls
    传入的是类或实例的类
    类方法即使不实例化,通过类也可以直接调用。
    实例化之后通过实例也可以调用。

    传参时对参数类型进行检查:

    第一种方法:

    传统方法,在__init__时做条件判断

    class Person:
        def __init__(self,name:str,age:int):
            if not self.checkdata(((name,str),(age,int))):
                raise TypeError()
            self.name = name
            self.age = age
        def checkdata(self,params):
            print(params)
            for dt,tp in params:
                # print(dt,tp)
                if not isinstance(dt,tp):  #第一种
                    return False
            else:
                return True
            #     if type(dt) == tp:  #第二种
            #         continue
            #     else:
            #         return False
            # else:
            #     return True
    
    
    p1 = Person('zhangsan',18)  #当传入类型不符时抛出TypeError异常,比如传入'18'字符串格式的age就会触发异常
    print(p1.__dict__)
    print(p1.name)
    print(p1.age)
    ————运行结果————————
    (('zhangsan', <class 'str'>), (18, <class 'int'>))
    {'name': 'zhangsan', 'age': 18}
    zhangsan
    18
    

      

    第二种:
    使用数据描述器

    class Typed:
        def __init__(self):
            pass
    
        def __get__(self, instance, owner):
            pass
    
        def __set__(self, instance, value):
            print('Typed.__set__:',self,instance,value)
    
    class Person:
        name = Typed()
        age = Typed()
    
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
    
    p1 = Person('jerry',18)
    ————运行结果——————
    Typed.__set__: <__main__.Typed object at 0x000002852A70FA58> <__main__.Person object at 0x000002852A7217B8> jerry
    Typed.__set__: <__main__.Typed object at 0x000002852A721710> <__main__.Person object at 0x000002852A7217B8> 18
    

      

    将类型作为参数传入Typed类,在__set__方法中做检查:

    class Typed:
        def __init__(self,type):
            self.type = type
            pass
    
        def __get__(self, instance, owner):
            pass
    
        def __set__(self, instance, value):
            print('Typed.__set__:',self,instance,value)
            if not isinstance(value,self.type):
                raise ValueError(value)
    
    class Person:
        name = Typed(str)
        age = Typed(int)
    
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
    
    p1 = Person('jerry',18)   #运行正常,说明传入的参数类型正确
    ------运行结果-------
    Typed.__set__: <__main__.Typed object at 0x00000218FDBEFA58> <__main__.Person object at 0x00000218FDC01668> jerry
    Typed.__set__: <__main__.Typed object at 0x00000218FDC01390> <__main__.Person object at 0x00000218FDC01668> 18
    

      当传入的参数不正确时就抛出ValueError异常:

    p1 = Person('jerry','18')
    ———运行结果—————
    Typed.__set__: <__main__.Typed object at 0x00000261222116A0> <__main__.Person object at 0x0000026122211710> jerry
    Traceback (most recent call last):
    Typed.__set__: <__main__.Typed object at 0x0000026122211668> <__main__.Person object at 0x0000026122211710> 18
      File "C:/python/1117/params_check_test3.py", line 23, in <module>
        p1 = Person('jerry','18')
      File "C:/python/1117/params_check_test3.py", line 21, in __init__
        self.age = age
      File "C:/python/1117/params_check_test3.py", line 13, in __set__
        raise ValueError(value)
    ValueError: 18
    

      在设置值时,Typed类__set__方法拦截到之后判断传入的value和type类型是否一致。

    第三种:

    装饰器加数据描述器

    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):
            print(self,instance,value)
            if not isinstance(value,self.type):
                raise ValueError(value)
            instance.__dict__[self.name] = value
            # return self
    
    
    import inspect
    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:
        # name = Typed(str)
        # age = Typed(int)
    
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
    
    p1 = Person('zhangsan',19)
    print(p1.__dict__)
    print(p1.name,p1.age)
    ——————运行结果————————
    OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
    name <class 'str'>
    age <class 'int'>
    <__main__.Typed object at 0x000001E818D91828> <__main__.Person object at 0x000001E818D91898> zhangsan
    <__main__.Typed object at 0x000001E818D91940> <__main__.Person object at 0x000001E818D91898> 19
    {'name': 'zhangsan', 'age': 19}
    zhangsan 19
    

      

  • 相关阅读:
    altas(ajax)控件(十七):互斥复选框控件MutuallyExclusiveCheckBox
    JAVA处理Clob大对象
    JNI简介及实例
    《JavaScript凌厉开发 Ext详解与实践》的目录
    《JavaScript凌厉开发 Ext详解与实践》的可以预订了
    Quartz入门到精通
    《JavaScript凌厉开发 Ext详解与实践》作者简介与媒体推荐
    计院生活第二章 深入虎穴(下)
    IT行业简历模板及就业秘籍
    计院生活第二章 深入虎穴(上)
  • 原文地址:https://www.cnblogs.com/i-honey/p/7862627.html
Copyright © 2020-2023  润新知