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 通过属性实例化调用描述器时,使用注意事项:
-
使用实例进行调用
最多三个方法都会被调用 -
使用类进行调用
最多会调用get方法 -
不能顺利转换场景
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
,那么以后这个值就是一个共享值,哪个实例修改后,其他实例获取到的就是被新修改的值
问题
- 解析:描述器的定义方式为类属性形式,但我们操作使用为什么都是通过对象来调用?
- 类属性是被各个对象共享的,那么有如何保证不同的对象可以操作到这个共享属性的不同值?
网友评论