Time will tell.
前面的Python文章中讲到过,我们可以用@property
装饰器将方法包装成属性,这样的属性,相比于其他属性有一个优点就是可以在对属性赋值时,进行变量检查,举例:
class A:
def __init__(self, name, score):
self.name = name # 普通属性
self._score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
print('setting score here')
if isinstance(value, int):
self._score = value
else:
print('please input an int')
a = A('Bob',90)
a.name # 'Bob'
a.score # 90
a.name = 1
a.name # 1 ,名字本身不应该允许赋值为数字,但是这里无法控制其赋值
a.score = 83
a.score # 83,当赋值为数值型的时候,可以顺利运行
a.score = 'bob' # please input an int
a.score # 83,赋值为字符串时,score没有被改变
当我们有很多这样的属性时,如果每一个都去使用@property,代码就会过于冗余,如下:
class A:
def __init__(self, name, score, age):
self.name = name # 普通属性
self._score = score
self._age = age
@property
def score(self):
return self._score
@score.setter
def score(self, value):
print('setting score here')
if isinstance(value, int):
self._score = value
else:
print('please input an int')
@property
def age(self):
return self._age
@age.setter
def age(self, value):
print('setting age here')
if isinstance(value, int):
self._age = value
else:
print('please input an int')
a = A('Bob', 90, 20)
因为每次检验的方法都是一样的,所以最好有方法可以批量实现这件事,只写一次 if isinstance 。描述器就可以用来实现这件事。
为了能够更清楚地理解描述器如何实现,我们先跳开这个话题,先讲讲描述器的基本理论。
描述器功能强大,应用广泛,它可以控制我们访问属性、方法的行为,是@property、super、静态方法、类方法、甚至属性、实例背后的实现机制,是一种比较底层的设计,因此理解起来也会有一些困难。
定义:从描述器的创建来说,一个类中定义了__get__
、__set__
、__delete__
中的一个或几个,这个类的实例就可以叫做一个描述器。
为了能更真切地体会描述器是什么,我们先看一个最简单的例子,这个例子不实现什么功能,只是使用了描述器。
# 创建一个描述器的类,它的实例就是一个描述器
# 这个类要有__get__ __set__ 这样的方法
# 这种类是当做工具使用的,不单独使用
class M:
def __init__(self, x=1):
self.x = x
def __get__(self, instance, owner):
return self.x
def __set__(self, instance, value):
self.x = value
# 调用描述器的类
class AA:
m = M() # m就是一个描述器
aa = AA()
aa.m # 1
aa.m = 2
aa.m # 2
分析上面这个例子:
创建aa实例和普通类没什么区别,我们从aa.m开始看。
aa.m是aa实例调用了m这个类属性,然而这个类属性不是普通的值,而是一个描述器,所以我们从访问这个类属性变成了访问这个描述器。
如果调用时得到的是一个描述器,python内部就会自动触发一套使用机制
访问的话自动触发描述器的__get__
方法。
修改设置的话就自动触发描述器的__set__
方法。
这里就是aa.m触发了__get__
方法,得到的是 self.x 的值,在前面__init__
中定义的为1。
aa.m = 2 则触发了__set__
方法,赋的值 2 传到 value 参数之中,改变了self.x的值,所以下一次aa.m
调用的值也改变了。
进一步思考:当访问一个属性时,我们可以不直接给一个值,而是接一个描述器,让访问和修改设置时自动调用__get__
方法和__set__
方法。再在__get__
方法和__set__
方法中进行某种处理,就可以实现更改操作属性行为的目的。这就是描述器做的事情。
相信有的读者已经想到了,开头引言部分的例子,就是用描述器这样实现的。在讲具体如何实现之前,我们要先了解更多关于描述器的调用机制。
aa.m
命令其实是查找m属性的过程,程序会先到哪里找,没有的话再到哪里找,这是有一个顺序的,说明访问顺序时需要用到__dict__
方法。
先看下面的代码了解一下__dict__
方法:
class C:
x = 1
def __init__(self, y):
self.y = y
def fun(self):
print(self.y)
c = C(2)
# 实例有哪些属性
print(c.__dict__) # {'y': 2}
# 类有什么属性
print(C.__dict__) # 里面有 x fun
print(type(c).