zoukankan      html  css  js  c++  java
  • 描述器

    定义:

    Python中如果一个类实现了 __get__、__set__、__delete__ 三个方法中的任意一个,那么这个类就是一个描述器。

    如果仅实现了 __get__就是非数据描述器non data descriptor;

    同时实现了 __get__、__set__ 就是数据描述器 data descriptor;

    如果一个类的类属性设置为描述器,那么它被称为owner属主。method也是类的属性

    __get__方法的触发时机:

    如果一个类的类属性是一个实例,并且这个实例是一个描述器,当访问这个类属性时,就会触发描述器中的__get__方法。ps:通过实例属性访问,不会触发__get__方法

    # 定义一个非数据描述器

    class A:

        def __init__(self):

            print('A init')

        def __get__(self, instance, owner):

            print('__get__', self, instance, owner)

            return self

     

    class B:

        # 类加载时就已经创建,

        x = A()  # 类属性是一个描述器

        def __init__(self):

            print('B init')

            self.x = A()

     

    print(B.x)  # 访问类属性,触发__get__方法

    print(B().x) # 访问实例属性,不会触发__get__方法,即使实例属性名和类属性相同,非数据描述器不会触发

     

    输出:

    A init

    __get__ <__main__.A object at 0x0000018F48169C50> None <class '__main__.B'>

    <__main__.A object at 0x0000018F48169C50>

     

    B init

    A init

    <__main__.A object at 0x0000018F48379C50>

    __get__方法参数说明:

             instance:属主类B的实例

             owner:属主类B

    返回值:类属性x的值

    __set__方法的触发时机:

             如何一个类的类属性是一个数据描述器(同时有__get__ 和 __set__ 方法),且这个类的实例属性名称和这个类属性具有相同的名字,当访问这个类和类属性同名的类实例时,实际访问的是这个类的类属性,会同时触发__get__ 和 __set__ 方法

    # 定义一个数据描述器

    class A:

        def __init__(self):

            print('A init')

        def __get__(self, instance, owner):

            print('__get__', self, instance, owner)

            return self

        def __set__(self, instance, value):

            print('__set__', self, instance, value)

     

     

    class B:

        # 类加载时就已经创建,

        x = A()  # 类属性是一个数据描述器

     

        def __init__(self):

            print('B init')

            self.x = 100 # 实例名字和类属性名字相同

     

     

    # 访问实例属性,实际访问的类属性,会先触发__set__方法在触发__get__方法

    print(B().x)

    输出:

    A init

    B init

    __set__ <__main__.A object at 0x000001A66DD19C50> <__main__.B object at 0x000001A66DF29DD8> 100

    __get__ <__main__.A object at 0x000001A66DD19C50> <__main__.B object at 0x000001A66DF29DD8> <class '__main__.B'>

    <__main__.A object at 0x000001A66DD19C50>

    B().x = 1 在外部操作时,同样会触发__set__方法

    官方解释:

             当类属性是一个数据描述器,且实例属性和类属性名称相同,访问类实例属性时,属性查找顺序,类字典的优先级高于实例字典优先级,所以因先在类字典进行搜索

    本质:

             通过打印 B.__dict__ 和 B().__dict__ 发现,B().__dict__中没有定义实例属性,所以在访问时发现实例字典中没有,会在类中进行搜索,最终访问的是类属性

    print(B().__dict__)

    print(B.__dict__)

    {}

    {'__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x000001D23EA29C88>, '__dict__': <attribute '__dict__' of 'B' objects>, '__module__': '__main__', '__doc__': None, '__init__': <function B.__init__ at 0x000001D23EA69E18>}

     

    描述器在Python中应用非常广泛

    Python中的方法(包括staticmethod 和 classmethod )都实现了非数据描述器,因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一个的其他实例不同的行为。

    Property类实现了一个数据描述器,因此,实例不能覆盖属性的行为。

     

    应用实例:

    ①   自定义类中的静态方法staticmethod

             分析过程:

    1、  自定义一个类 StaticMethod;

    2、  用于装饰另外一个A类中的foo方法,相当于foo = StaticMethod(foo);

    3、  所以在StaticMethod的初始化方法中需要接受foo函数,__init__(self, fn);

    4、  foo方法本身为A类中类属性,值为StaticMethod类的实例,如果StaticMethod为非数据描述时,访问foo时,会触发StaticMethod类中的__get__方法;

    5、  __get__返回值为foo方法本身,这样在执行foo()时,才能指向foo本身

    代码如下:

    class StaticMthod:

        def __init__(self, fn):

            self._fn = fn

        def __get__(self, instance, owner):

            print('__get__: ', self, instance, owner)

            return self._fn # 返回fn本身

     

    class A:

        @StaticMthod # 相当于 foo = StaticMthod(foo)

        def foo():

            print('static')

    # foo是A的类属性,值为StaticMthod实例,且StaticMthod是一个描述器

    # 访问A.foo时,会触发__get__方法,返回值为A.foo的值

    a = A.foo # 此时就会触发__get__方法

    a() # 执行

    输出:

    __get__:  <__main__.StaticMthod object at 0x000001FE6A189CC0> None <class '__main__.A'>

    static

    ②   自定义类中的类方法classmethod

             分析过程和静态方法类似,classmethod执行是多一个cls参数,如果__get__ 返回值还是self._fn的话,就会报错,缺少一个参数,所以引入偏函数将cls进行固化,返回一个新的可调用对象

    示例代码:

    from functools import partial

    class ClassMethod:

        def __init__(self, fn):

            self._fn = fn

     

        def __get__(self, instance, owner):

            print("__get__ : ", self, instance, owner)

            return partial(self._fn, owner)

     

     

    class A:

        @StaticMthod # 相当于 foo = StaticMthod(foo)

        def foo():

            print('static')

     

        @ClassMethod # 相当于 bar = StaticMthod(bar)

        def bar(cls):

            print(cls.__name__)

     

    # foo是A的类属性,ClassMethod,ClassMethod是一个描述器

    # 访问A.bar时,会触发__get__方法,返回值为A.bar的值

    a = A.bar # 此时就会触发__get__方法

    a() # 执行

    输出:

    __get__ :  <__main__.ClassMethod object at 0x000001C7DE6C9DD8> None <class '__main__.A'>

    A

     

    ③  对实例的数据进行检查

    class Person:

        def __init__(self, name: str, age: int):

            self.name = name

            self.age = age

     

    思路:

        写函数,在__init__中先检查,如果不合格,直接跑异常

        装饰器,使用inpect模块完成

        描述器

    使用描述器实现:

        分析:实例数据进行检查,即需要触发__set__ 方法,所以将类实例属性的名称和类属性的名称定义为相同。

    第一版代码如下:

    class Typed:

        def __init__(self, name, _type):

            self.name = name

            self.type = _type

     

        def __get__(self, instance, owner):

            print('__get__:', self, instance, owner)

            if instance is not None:

                return instance.__dict__[self.name]

            return self

     

        def __set__(self, instance, value):

            print('__set__:', self, instance, value)

            if not isinstance(value, (self.type,)):

                raise ValueError(value)

            instance.__dict__[self.name] = value

     

     

    class Person:

        # 类属性名称和类实例属性定义为一样

        name = Typed('name', str)

        age = Typed('age', int)

     

        def __init__(self, name: str, age: int):

            # 初始化实例属性时会触发__set__方法

            self.name = name

            self.age = age

     

    p = Person('lisi',18)

    # 访问实例属性时会触发__get__方法

    print(p.name)

    print(p.age)

     

    上面代码在类中需要定义和实例属性相同的名称,显然不够灵活

    改进如下:

        使用inspect模块动态获取初始化方法签名

        使用装饰器的方式动态给类添加类属性

     

    # 定义描述器

    class Typed:

        def __init__(self, name, _type):

            self.name = name

            self.type = _type

     

        def __get__(self, instance, owner):

            # print('__get__:', self, instance, owner)

            if instance is not None:

                # 访问属性时触发__get__,访问__set__访问的值

                return instance.__dict__[self.name]

            return self

     

        def __set__(self, instance, value):

            # print('__set__:', self, instance, value)

            if not isinstance(value, (self.type,)):

                raise ValueError(value)

            # 将value保存在__dict__中

            instance.__dict__[self.name] = value

     

    # 装饰器类的装饰器

    def typeassert(cls):

        import inspect

        params = inspect.signature(cls).parameters

        for name, param in params.items():

            # print(name, param.annotation)

            if param.annotation != param.empty(): # 只检查注解不为空的参数

                # 动态方式给类添加类属性,类属性值为描述器实例

                # 将装饰器和描述器联系起来

                setattr(cls, name, Typed(name, param.annotation))

        return cls

     

     

    @typeassert  # Person = TypeAssert(Pseron), Person()

    class Person:

        def __init__(self, name: str, age: int):

            self.name = name

            self.age = age

     

    上面的装饰器还可以定义为类装饰器

    class TypeAssert:

        def __init__(self, cls):

            self.cls = cls

     

        def __call__(self, *args, **kwargs):

            import inspect

            params = inspect.signature(self.cls).parameters

            for name, param in params.items():

                # print(name, param.annotation)

                if param.annotation != param.empty():

                    setattr(self.cls, name, Typed(name, param.annotation))

            return self.cls(*args, **kwargs) # 返回类实例

     
  • 相关阅读:
    使用 libevent 和 libev 提高网络应用性能
    An existing connection was forcibly closed by the remote host
    各种浏览器的兼容css
    vs输出窗口,显示build的时间
    sass
    网站设置404错误页
    List of content management systems
    css footer not displaying at the bottom of the page
    强制刷新css
    sp_executesql invalid object name
  • 原文地址:https://www.cnblogs.com/yaoqingzhuan/p/11878906.html
Copyright © 2011-2022 走看看