其他见此:

全面学习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
    11
    def __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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AccessCounter(object):
'''A class that contains a value and implements an
access counter . The counter increments each time the
value is changed . '''
def __init__(self, val):
super(AccessCounter, self).__setattr__('counter', 0)
super(AccessCounter, self).__setattr__('value', val)
def __setattr__(self, name, value):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)

# Make this unconditional .
# If you want to prevent other attributes to be set,
# raise AttributeError(name )
super(AccessCounter, self).__setattr__(name, value )
def __delattr__(self, name):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
super(AccessCounter, self).__delattr__(name)]

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__定义使用innot in 测试成员资格。当没有定义__contains__时,如果遇到正在寻找的项目, Python只是迭代序列并返回True

  • __missing__(self,key)

    __missing__用于dict的子类。它定义了在字典中不存在访问密钥时的行为。

An example

一个示例,说明如何实现自己的序列。 当然,自定义序列有更多有用的应用,但其中相当一部分已在标准库中实现,如CounterOrderedDictNamedTuple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class FunctionalList :
'''A class wrapping a list with some extra functional
magic, like head, tail, init, last, drop, and take.'''

def __init__(self, values = None):
if values is None :
self.values = []
else :
self.values = values

def __len__(self):
return len(self.values)

def __getitem__(self, key):
# if key is of invalid type or value, the list values
# will raise the error
return self.values[key]

def __setitem__(self, key, value):
self.values[key] = value

def __delitem__(self, key):
del self.values[key]

def __iter__(self):
return iter(self.values)

def __reversed__(self):
return reversed(self.values)

def append(self, value):
self.values.append(value)

def head(self):
# get the first element
return self.values[0]

def tail(self):
# get all elements after the first
return self.values[1:]

def init(self):
# get elements up to the last
return self.values[:-1]

def last(self):
# get last element
return self.values[-1]

def drop(self, n):
# get all elements except first n
return self.values[n:]

def take(self, n):
# get first n elements
return self.values[:n]

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
2
3
4
5
6
7
8
9
10
class Entity :
'''Class to represent an entity . Callable to update
the entity ’s position .'''
def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size
def __call__(self, x, y):
'''Change the position of the entity .'''
self.x, self.y = x, y
# snip ...

Context Managers

在Python 2.5中,Python中引入了一个新关键字以及一种新的代码重用方法,即with语句。 上下文管理器的概念在Python中并不是新的(它之前是作为库的一部分实现的),但直到PEP 343被接受它才能实现作为第一类语言构造的状态。

1
2
with open ('foo. txt') as bar:
# perform some action with bar

上下文管理器允许在创建对象用with语句包装时,对对象进行设置和清理操作。上下文管理器的行为由两种魔法方法决定:

  • __enter__(self)

    定义上下文管理器在with语句创建的块的开头应该执行的操作。请注意,__enter__的返回值绑定到with语句的目标,或者as之后的名称。

  • __exit__(self,exception_type,exception_value,traceback)

    定义上下文管理器在块执行(或终止)后应该执行的操作。它可以用于处理异常,执行清理,或者在块中的操作之后立即执行某些操作。如果块成功执行,则 exception_typeexception_valuetraceback将为None。否则,您可以选择处理异常或让用户处理它; 如果你想处理它,请确保__exit__在完成所有操作后返回True。如果您不希望上下文管理器处理异常,那就让它发生即可。

__enter____exit__对于具有明确定义,并且有常见的设置和清理行为的特定类非常有用。您还可以使用这些方法创建包装其他对象的通用上下文管理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Closer :
'''A context manager to automatically close an object with a
close () method in a with statement.'''

def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj # bound to target
def __exit__(self, exception_type, exception_val, trace):
try:
self.obj.close()
except AttributeError: # obj isn ’t closable
print('Not closable.')
return True # exception handled successfully

以下是Closer的实例,使用FTP连接来演示它(一个可关闭的套接字):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn :
... conn.dir()
...
# output omitted for brevity
>>> conn.dir()
# long AttributeError message , can't use a connection that's closed
>>> with Closer(int(5)) as i:
... i += 1
...
Not closable.
>>> i
6

Python标准库包含一个模块contextlib,它包含一个上下文管理器contextlib.closing(),它执行大致相同的操作(不需要处理对象没有close()方法的情况)。

Building Descriptor Objects

描述符是一种类类,当通过获取,设置或删除访问时,还可以更改其他对象。 描述符并不意味着独立; 相反,它们应该由一个所有者类持有(an owner class)。在构建面向对象的数据库或具有值相互依赖的属性的类时,描述符非常有用。当在几个不同的测量单位中表示属性或表示计算的属性(例如,从类中的原点到表示网格上的点的距离)时,描述符也特别有用。

要成为描述符,类必须至少实现__get____set____delete__中之一。

  • __get__(self,instance,owner)

    定义描述符的值被检索时的行为。instanceowner对象的实例。ownerowner class本身。

  • __set__(self,instance,value)

    定义描述符的值被更改时的行为。instanceowner类的实例,value是设置描述符的值。

  • __delete__(self,instance)

    定义删除描述符值时的行为。 instanceowner对象的实例。

一个使用描述符的例子:单位转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Meter(object):
'''Descriptor for a meter.''''

def __init__(self, value=0.0):
self.value = float(value)

def __get__(self, instance, owner):
return self.value

def __set__(self, instance, value):
self.value = float(value)

class Foot(object):
"""Descriptor for a foot."""

def __get__(self, instance, owner):
return instance.meter * 3.2808

def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
'''Class to represent distance holding two descriptors for feet and
meters.'''
meter = Meter()
foot = Foot()