美文网首页
Python基础27-面向对象(系统内置方法7-描述器)

Python基础27-面向对象(系统内置方法7-描述器)

作者: Jacob_LJ | 来源:发表于2018-05-23 23:00 被阅读7次

    Python基础-面向对象(方法)

    7 描述器

    1 概念

    • 用于描述一个属性对应操作的对象。
    • 属性对应操作一般为:增/改、删、查

    2 作用

    • 可以代为管理一个类属性的读写删操作, 在相关方法中, 对数据进行验证处理, 过滤处理等等
    • 如果一个类属性被定义为描述器,那么以后对这个类属性的操作(读写删), 都将由这个描述器代理

    3 定义

    3.1 通过 property创建属性的描述器

    其实就是前面提到的 property 的使用

    • 一般我们定义类都会将属性定义为私有属性,这样外界就不能随便访问或赋值
    class Person:
        def __init__(self, value):
            self.__age = value
    
    • 让实例能够通过.age方式进行访问或过滤性修改
    • 通过 property 定义后返回的 age 就是一个描述器,描述器就是一个对属性操作进行描述(即过滤或保护等)的对象
    
    class Person:
        def __init__(self, value):
            self.__age = value
    
        def get_age(self):
            print("get age")
            return self.__age
    
        def set_age(self, value):
            print("set age")
            if value < 0:
                value = 0
            self.__age = value
    
        def del_age(self):
            print("del age")
            del self.__age
    
        # 此时返回的 age 就是一个描述器,描述器就是一个对属性操作进行描述(即过滤或保护等)的对象
        age = property(get_age, set_age, del_age)
    
    p = Person()
    # 访问
    print(p.age)
    # 赋值
    p.age = 19
    # 删除
    del p.age
    
    
    • 查看上面代码中通过property 定义的 age 描述器与 name 属性分区对别
      age 是被分配到 Data descriptors defined here: 区
    class Person:
        # 与 age 对比用
        name = "fkm"
    
        def __init__(self, value):
            self.__age = value
    
        def get_age(self):
            print("get age")
            return self.__age
    
        def set_age(self, value):
            print("set age")
            if value < 0:
                value = 0
            self.__age = value
    
        def del_age(self):
            print("del age")
            del self.__age
    
        age = property(get_age, set_age, del_age)
    
    p = Person(10)
    print(Person.__dict__)
    print(p.__dict__)
    
    print("-" * 20)
    
    help(Person)
    
    
    
    >>>> 打印结果
    
    {
    '__module__': '__main__', 
    'name': 'fkm', 
    '__init__': <function Person.__init__ at 0x10c86ebf8>, 
    'get_age': <function Person.get_age at 0x10c86eb70>, 
    'set_age': <function Person.set_age at 0x10c86ec80>, 
    'del_age': <function Person.del_age at 0x10c86ed08>, 
    'age': <property object at 0x10c6bea98>, 
    '__dict__': <attribute '__dict__' of 'Person' objects>, 
    '__weakref__': <attribute '__weakref__' of 'Person' objects>, 
    '__doc__': None
    }
    
    {'_Person__age': 10}
    --------------------
    Help on class Person in module __main__:
    
    class Person(builtins.object)
     |  Methods defined here:
     |  
     |  __init__(self, value)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  del_age(self)
     |  
     |  get_age(self)
     |  
     |  set_age(self, value)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  age
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  name = 'fkm'
    
    
    Process finished with exit code 0
    
    
    • 使用 property 内置方式,同样可以通过属性描述器访问属性
    class Person:
        def __init__(self):
            self.__age = 10
    
        @property
        def age(self):
            return self.__age
    
        @age.setter
        def age(self, value):
            if value < 0:
                value = 0
            self.__age = value
    
        @age.deleter
        def age(self):
            print("del age")
            del self.__age
    
    # p = Person()
    # # 访问
    # print(p.age)
    # # 赋值
    # p.age = 19
    # # 删除
    # del p.age
    
    help(Perosn)
    # age 同样在 Data descriptors defined here: 区
    
    >>>>> 打印结果
    
    Help on class Person in module __main__:
    
    class Person(builtins.object)
     |  Methods defined here:
     |  
     |  __init__(self)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  age
    
    
    Process finished with exit code 0
    
    

    3.2 通过属性实例化方式 - 创建属性描述器

    3.2.1 该实例的类必须要实现以下三个方法

    __get__
    __set__
    __delete__
    
    • 优点
    • 上述3.1创建属性描述器方式,具有导致该类臃肿的弊端,试想:实现一个 age 属性描述器已经在 Person 类里面写了3个方法了,如果再多些属性,则会出现3*x 个方法。
    • 而通过属性实例化方式,则可以将对该属性进行操作描述的3个方法抽离到该实例类里面,更加面向对象了。
    • 这样的属性实例化方式也体现 python 语言一切皆对象的特性
    # 属性描述器对象
    class Age:
        def __get__(self, instance, owner):
            print("get")
    
        def __set__(self, instance, value):
            print("set")
    
        def __delete__(self, instance):
            print("delete")
    
    
    # 类
    class Person:
        age = Age() # 属性描述器实例化
    
    # 此时 age 也是一个类属性,但之能通过类执行 get 方法,其他方法不会被转传到描述器中
    
    # 属性操作
    p = Person()
    p.age = 10
    print(p.age)
    # del p.age
    
    >>> 打印结果
    
    set
    get
    None
    

    3.2.2 通过属性实例化调用描述器时,使用注意事项:

    1. 使用实例进行调用
      最多三个方法都会被调用

    2. 使用类进行调用
      最多会调用get方法

    3. 不能顺利转换场景
      3.1 新式类和经典类
      描述器仅在新式类(继承自 object 的)中生效,且类及描述器类都是新式类

      3.2 方法拦截
      * 一个实例属性的正常访问顺序

      1 实例对象自身的__dict__字典
      2 对应类对象的__dict__字典
      3 如果有父类, 会再往上层的__dict__字典中检测
      4 如果没找到, 又定义了__getattr__方法, 就会调用这个方法
      
      • 而在上述的整个过程当中, 是如何将描述器的__get__方法给嵌入到查找机制当中?

      • 就是通过这个方法进行实现:__getattribute__

      • 内部实现模拟
        如果实现了描述器方法get就会直接调用
        如果没有, 则按照上面的机制去查找

    4 描述器-和实例属性同名时, 操作优先级

    • 资料描述器:描述器类同时实现了 get set 方法

    • 非资料描述器:仅仅实现了 get 方法

    • 资料描述器 > 实例属性 > 非资料描述器

    • 1 资料描述器 > 实例属性 测试

    class Age(object):
        def __get__(self, instance, owner):
            print("get")
    
        def __set__(self, instance, value):
            print("set")
    
        def __delete__(self, instance):
            print("delete")
    
    
    class Person(object):
        age = Age()
        def __init__(self):
            self.age = 10
    
    p = Person()
    
    p.age = 10
    print(p.age)
    
    print(p.__dict__)
    
    >>>> 打印结果
    set
    set
    get
    None
    {}
    
    
    • 实例属性 > 非资料描述器 测试
    class Age(object):
        def __get__(self, instance, owner):
            print("get")
    
    
    class Person(object):
        age = Age()
        def __init__(self):
            self.age = 10
    
    p = Person()
    
    p.age = 10
    print(p.age)
    
    
    print(p.__dict__)
    
    >>>> 打印结果
    
    10
    {'age': 10}
    

    5 描述器-值的存储问题

    • 描述器Age是共享的
    class Age:
        def __get__(self, instance, owner):
            print("get", self, instance, owner)
            if "v" in instance.__dict__:
                return instance.v
    
        def __set__(self, instance, value):
            print("set", self, instance, value)
            instance.v = value
    
        def __delete__(self, instance):
            print("delete", self, instance)
            del instance.v
    
    
    class Person:
        age = Age()
    
    
    p1 = Person()
    print(p1.age)
    
    p2 = Person()
    print(p2.age)
    
    
    >>>> 打印结果
    
    get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a20> <class '__main__.Person'>
    get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a58> <class '__main__.Person'>
    
    # 只有 instance 的值是对应 Person 实例,而 Age 描述器则是同一个
    
    • 所以应该把值存放到对应的类实例中
    class Age:
        def __get__(self, instance, owner):
            print("get", self, instance, owner)
            if "v" in instance.__dict__:
                return instance.v
    
        def __set__(self, instance, value):
            print("set", self, instance, value)
            instance.v = value
    
        def __delete__(self, instance):
            print("delete", self, instance)
            del instance.v
    
    
    class Person:
        age = Age()
    
    
    p1 = Person()
    p1.age = 10
    print(p1.age)
    
    p2 = Person()
    p2.age = 19
    print(p2.age)
    
    >>>> 打印结果
    
    set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> 10
    get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> <class '__main__.Person'>
    10
    set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> 19
    get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> <class '__main__.Person'>
    19
    
    
    • 有些场景也可以共享 描述器哦,如你想保存最新被修改的值时,就应该将值绑定到共享的描述器中 self.v = value,那么以后这个值就是一个共享值,哪个实例修改后,其他实例获取到的就是被新修改的值

    问题

    1. 解析:描述器的定义方式为类属性形式,但我们操作使用为什么都是通过对象来调用?
    2. 类属性是被各个对象共享的,那么有如何保证不同的对象可以操作到这个共享属性的不同值?

    相关文章

      网友评论

          本文标题:Python基础27-面向对象(系统内置方法7-描述器)

          本文链接:https://www.haomeiwen.com/subject/ycxljftx.html