全面学习Python魔法方法(magic methods) II
目录
其他见此:
全面学习Python魔法方法(magic methods) I
全面学习Python魔法方法(magic methods) II
全面学习Python魔法方法(magic methods) III
Controlling Attribute Access
Python通过“魔法方法”完成了许多的封装,如下所示:
-
__getattr__(self,name)
可以定义用户何时尝试访问不存在的属性(或者根本不存在)的行为。这对于捕获和重定向常见的拼写错误非常有用,可以提供有关使用弃用属性的警告(如果愿意,仍然可以选择计算并返回该属性),或者巧妙地处理
AttributeError
。它只在访问不存在的属性时被调用,但是它不是真正的封装解决方案。 -
__setattr__(self,name,value)
与
__getattr__
不同,__setattr__
是一个封装解决方案。它允许定义分配给属性的行为,无论该属性是否存在,这意味着可以为属性值的任何更改定义自定义规则。注意不要出现无限递归,如下所示。1
2
3
4
5
6
7
8
9
10
11def __setattr__(self, name, value):
self.name = value
# since every time an attribute is assigned , __setattr__()
# is called , this is recursion . So this really means
# self.__setattr__ ('name', value). Since the method keeps
# calling itself , the recursion goes on forever causing a crash
def __setattr__ (self , name , value ):
# assigning to the dict of names in the class
self.__dict__[name] = value
# define custom behavior here -
__delattr__(self,name)
这与
__setattr__
完全相同,但是用于删除属性而不是设置它们。为了防止无限递归,需要采取与__setattr__
相同的预防措施(在__delattr__
的实现中调用del self.name
会导致无限递归)。 -
__getattribute__(self,name)
__getattribute__
非常适合配合__setattr__
和__delattr__
一起使用,但是只能用于新式类。__getattribute__
为访问属性值时,定义规则,即__getattribute__
是属性访问拦截器,当这个类的属性被访问时,会自动调用类的__getattribute__
方法。同时它还主要消除了
__getattr__
的需要,当__getattribute__
被实现时,只有在显式调用或引发AttributeError
时才会被调用。但是不推荐这样使用,因为非常容易出现bug,导致无限递归。
那么,我们应该在Python中学习自定义属性访问的哪些内容? 不应被轻易使用。
事实上,它往往是过于有力并且违反直觉。Python 不会试图让坏事变得不可能,而只是为了让它们变得困难。自由是至关重要的,所以你可以真正做任何你想做的事。
以下是一些特殊属性访问方法的示例(使用super,是因为并非所有类都具有属性__dict__
):
1 | class AccessCounter(object): |
Making Custom Sequences
有许多方法可以使 Python 类像内置序列(dict,tuple,list,string等)一样运行。
Requirements
既然正在谈论在Python中创建自己的序列,那么是时候讨论协议了。
协议有点类似于其他语言中的接口,因为它们为您提供了一组必须定义的方法。但是,在Python中,协议是完全非正式的,不需要显式声明来实现。相反,它们更像是一种指导。
我们为什么现在谈论协议?因为在Python中实现自定义容器类型涉及使用其中一些协议。
首先,有用于定义不可变容器的协议:要创建一个不可变容器,只需要定义__len__
和__getitem__
(后面会详细介绍)。可变容器协议则需要不可变容器所需的所有内容以及__setitem__
和__delitem__
。最后,如果您希望对象可迭代,则必须定义__iter__
,它返回一个迭代器。该迭代器必须符合迭代器协议,要求迭代器具有__iter__
(返回自身)和next
方法。
The magic behind containers
-
__len__(self)
返回容器的长度。不可变容器和可变容器的协议的一部分。
-
__getitem__(self,key)
使用符号
self[key]
定义访问项目时的行为。这也是可变和不可变容器协议的一部分。它还应该引发适当的异常:如果key
的类型错误,则为TypeError; 如果key
没有对应的值,则为KeyError。 -
__setitem__(self,key,value)
使用符号
self[key]=value
定义项目分配时的行为。这是可变容器协议的一部分。同样,应该在适当的时候引发KeyError和TypeError。 -
__delitem__(self,key)
定义删除项目时的行为(例如
del self[key]
)。这只是可变容器协议的一部分。使用无效key
时,必须引发相应的异常。 -
__iter__(self)
应返回容器的迭代器。迭代器在许多上下文中返回,最明显的是内置函数的
iter()
以及容器在容器中使用 x 的表单循环时:迭代器是它们自己的对象,它们还必须定义一个返回self
的__iter__
方法。 -
__reversed__(self)
调用以实现
reverse()
内置函数的行为, 应返回序列的反转版本。只有在序列类被排序时才实现它,例如list或tuple。 -
__contains__(self,item)
__contains__
定义使用in
和not in
测试成员资格。当没有定义__contains__
时,如果遇到正在寻找的项目, Python只是迭代序列并返回True
。 -
__missing__(self,key)
__missing__
用于dict的子类。它定义了在字典中不存在访问密钥时的行为。
An example
一个示例,说明如何实现自己的序列。 当然,自定义序列有更多有用的应用,但其中相当一部分已在标准库中实现,如Counter
,OrderedDict
和 NamedTuple
。
1 | class FunctionalList : |
Reflection
还可以使用内置函数 isinstance
控制反射的工作方式,并通过定义魔法方法来控制 issubclass()
的行为。
-
__instancecheck__(self,instance)
检查实例是否是定义的类的实例 (e.g. isinstance(instance, class)
-
__subclasscheck__(self,subclass)
检查一个类是否是定义的类的子类 (e.g. issubclass(subclass, class))
这些魔法方法的用处可能看起来很少,但它们反映了Python和Python中面向对象编程的一些重要内容。
Abstract Base Classes
参见 abc
— Abstract Base Classes
Callable Objects
在Python中,函数是第一类对象(first-class objects)。这意味着它们可以传递给函数和方法,就像它们是任何其他类型的对象一样,这是一个非常强大的功能。
Python中一种特殊的魔法方法允许类的实例表现得就像它们是函数一样,这样你就可以“调用”它们,将它们传递给以函数作为参数的函数,依此类推。
-
__call__(self,[args ...])
允许将类的实例作为函数调用。从本质上讲,这意味着
x()
与x.__call__()
相同。__call__
采用可变数量的参数, 这意味着您可以像定义任何其他函数一样定义__call__
__call__
在具有需要经常更改状态的实例的类中特别有用。“调用”实例可以是一种直观而优雅的方式来更改对象的状态。
1 | class Entity : |
Context Managers
在Python 2.5中,Python中引入了一个新关键字以及一种新的代码重用方法,即with语句。 上下文管理器的概念在Python中并不是新的(它之前是作为库的一部分实现的),但直到PEP 343被接受它才能实现作为第一类语言构造的状态。
1 | with open ('foo. txt') as bar: |
上下文管理器允许在创建对象用with语句包装时,对对象进行设置和清理操作。上下文管理器的行为由两种魔法方法决定:
-
__enter__(self)
定义上下文管理器在with语句创建的块的开头应该执行的操作。请注意,
__enter__
的返回值绑定到with语句的目标,或者as之后的名称。 -
__exit__(self,exception_type,exception_value,traceback)
定义上下文管理器在块执行(或终止)后应该执行的操作。它可以用于处理异常,执行清理,或者在块中的操作之后立即执行某些操作。如果块成功执行,则
exception_type
,exception_value
和traceback
将为None
。否则,您可以选择处理异常或让用户处理它; 如果你想处理它,请确保__exit__
在完成所有操作后返回True
。如果您不希望上下文管理器处理异常,那就让它发生即可。
__enter__
和__exit__
对于具有明确定义,并且有常见的设置和清理行为的特定类非常有用。您还可以使用这些方法创建包装其他对象的通用上下文管理器:
1 | class Closer : |
以下是Closer的实例,使用FTP连接来演示它(一个可关闭的套接字):
1 | from magicmethods import Closer |
Python标准库包含一个模块contextlib,它包含一个上下文管理器contextlib.closing()
,它执行大致相同的操作(不需要处理对象没有close()方法的情况)。
Building Descriptor Objects
描述符是一种类类,当通过获取,设置或删除访问时,还可以更改其他对象。 描述符并不意味着独立; 相反,它们应该由一个所有者类持有(an owner class)。在构建面向对象的数据库或具有值相互依赖的属性的类时,描述符非常有用。当在几个不同的测量单位中表示属性或表示计算的属性(例如,从类中的原点到表示网格上的点的距离)时,描述符也特别有用。
要成为描述符,类必须至少实现__get__
,__set__
和__delete__
中之一。
-
__get__(self,instance,owner)
定义描述符的值被检索时的行为。
instance
是owner
对象的实例。owner
是owner class
本身。 -
__set__(self,instance,value)
定义描述符的值被更改时的行为。
instance
是owner
类的实例,value
是设置描述符的值。 -
__delete__(self,instance)
定义删除描述符值时的行为。
instance
是owner
对象的实例。
一个使用描述符的例子:单位转换。
1 | class Meter(object): |