Descriptor¶
Design Pattern¶
strategy pattern
描述符协议¶
- 描述符协议由三个魔术方法组成:
- __get__(self, instance, owner=None)
定义当描述器的值被取得的时候的行为, instance 是“拥有者对象”的一个实例。 owner 是拥有者类本身
- __set__(self, instance, value)
定义当描述器值被改变时候的行为。 instance 是“拥有者类”的一个实例, value 是要设置的值
- __delete__(self, instance)
- 和其他魔术方法的参数列表比较,上述三个魔术方法的传入参数中instance是很特别的,需要格外注意。
- 描述符被分配给一个类,而不是实例。
创建描述符¶
有三种创建描述符的方法:
- 三种方法逐渐简化
- 不同的创建方法对客户端代码并无影响
- 关注客户端代码使用什么语句才能触发调用魔术方法。
创建描述符类¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Descriptor(object):
def __init__(self):
self._name = ''
def __get__(self, instance, owner):
print "Getting: %s" % self._name
return self._name
def __set__(self, instance, name):
print "Setting: %s" % name
self._name = name.title()
def __delete__(self, instance):
print "Deleting: %s" %self._name
del self._name
class Person(object):
"""
Attributes:
name: 一个类属性同时也是一个“描述符对象”
"""
name = Descriptor()
|
在Anaconda prompt中使用这段代码:
1 2 3 4 5 6 7 8 9 10 | >>> from descriptor import *
>>> user1 = Person()
>>> user2 = Person()
#__set__(self, user1, 'yiwen')被调用
>>> user1.name = 'yiwen'
Setting: yiwen
#__get__(self, user2)被调用
>>> user2.name
Getting: Yiwen
'Yiwen'
|
上面的代码中,
- 触发描述符魔术方法调用的是拥有者对象对描述符对象的使用,描述符对象使用自己的属性时反而不会触发描述符魔术方法,这一点和其他魔术方法的调用有很大的区别。
- class Person和class Descriptor是“组合关系”,
- class Person的对象作为参数传入class Descriptor的魔术方法__get__, __set__,于是,这些class Descriptor方法就能操作class Person的属性,像极了“策略模式”
- 从上面的客户端代码可以看出,user1和user2两个对象并没有真正拥有自己的_name属性,对class Descriptor稍作修改,就可以实现,见下述代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Descriptor(object):
def __init__(self):
# self._name = ''
pass
def __get__(self, instance, owner):
print("Getting: %s" % instance._name)
# return self._name
return instance._name
def __set__(self, instance, name):
print("Setting: %s" % name)
# self._name = name.title()
instance._name = name.title()
def __delete__(self, instance):
print("Deleting: %s" % self._name)
# del self._name
del instance._name
|
使用property()¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Person(object):
def __init__(self):
self._name = ''
def fget(self):
print "Getting: %s" % self._name
return self._name
def fset(self, value):
print "Setting: %s" % value
self._name = value.title()
def fdel(self):
print "Deleting: %s" %self._name
del self._name
name = property(fget, fset, fdel, "I'm the property.")
|
这种方法无需手动创建“描述符类”,只需在拥有者类中定义下列三个方法中的一个或几个,然后调用property()。
- fget:属性获取方法
- fset:属性设置方法
- fdel:属性删除方法
注意:
- 这三个方法不是魔术方法,方法名可以随便取,只需要传入property()的顺序不错就行。
- fget、fset 和 fdel 方法是可选的,但是如果没有指定这些方法,那么将在尝试各个操作时出现一个 AttributeError 异常。例如,声明 name 属性时,fset 被设置为 None,然后开发人员尝试向 name 属性分配值。这时将出现一个 AttributeError 异常。
1 2 | #这种方法可以用于定义系统中的只读属性。
name = property(fget, None, fdel, "I'm the property")
|
使用@property(属性修饰符)¶
上面小节的代码中,类属性name = property()的定义形式就是Decorator。
在Decorator中引入了“修饰符”的概念@decorator, 用以更改函数的功能。
当用@decorator修饰一个函数时,到底发生了什么,可以参见Decorator的相关章节。
用@property来替代property(),可以对上小节的代码进一步简化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Person(object):
def __init__(self):
self._name = ''
#name = property(name)
@property
def name(self):
print "Getting: %s" % self._name
return self._name
#name = name.setter(name)
@name.setter
def name(self, value):
print "Setting: %s" % value
self._name = value.title()
#name = name.deleter(name)
@name.deleter
def name(self):
print ">Deleting: %s" % self._name
del self._name
|
其中:
- property(), name.setter(), name.deleter()都是decorator function
- name原本是方法,被@property改成了类属性,一个property object
- setter(), deleter()是property object所有的方法
- 客户端代码仍然使用user1.name对_name进行读写
- 如果要对_name属性的读、写和删除都要进行控制,那么必须有三个同名的decorated function
- 三个被修饰的name()实际上形成了一个 decorator chain