Python基础27-面向对象(系统内置方法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__': , 
'get_age': , 
'set_age': , 
'del_age': , 
'age': , 
'__dict__': , 
'__weakref__': , 
'__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> 
get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a58> 

# 只有 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> 
10
set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> 19
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> 
19

  • 有些场景也可以共享 描述器哦,如你想保存最新被修改的值时,就应该将值绑定到共享的描述器中 self.v = value,那么以后这个值就是一个共享值,哪个实例修改后,其他实例获取到的就是被新修改的值

问题

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

你可能感兴趣的:(Python基础27-面向对象(系统内置方法7-描述器))