魔数方法(Magic Method)
简介
所谓魔法函数(Magic Methods),是Python的一种高级语法,允许你在类中自定义函数(函数名格式一般为 __xx__),并绑定到类的特殊方法中。比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用 __str__() 函数,并返回相应的结果。在我们平时的使用中,可能经常使用 __init__ 函数和 __del__ 函数,其实这也是魔法函数的一种。
使用 Python 魔法函数最大的优势在于他们提供了一种简单的方法让对象可以表现的像内置类型一样。那意味着可以避免相同的操作对不同类型的对象需要使用不同的函数库:
例如:
Java 中比较对象是否相同:
if (instance.equals(other_instance)){
# do something
}
而 python 中,只需要类定义 __eq__ 方法,便可以直接比较:
if instance == other_instance:
//do something
构造和初始化
每个人都知道一个最基本的魔术方法, __init__ 。通过此方法我们可以定义一个对象的初始操作。然而,当我调用 x = SomeClass() 的时候,__init__ 并不是第一个被调用的方法。实际上,还有一个叫做__new__ 的方法,来构造这个实例。然后给在开始创建时候的初始化函数来传递参数。在对象生命周期的另一端,也有一个 __del__ 方法。我们现在来近距离的看一看这三个方法:
__new__(cls, *args, **kwargs)
此方法为类的初始化方法。当构造函数被调用的时候的任何参数都将会传给它。(比如如果我们调用 x = SomeClass(10, 'foo')),那么 __new__ 将会得到两个参数10和foo。
__init__(self)
对象实例化时,调用的第一个方法,它的第一个参数时这个类,其他参数是用来直接传递给 __init__ 方法
__del__(self)
如果 __new__ 和 __init__ 是对象的构造器的话,那么 __del__ 就是析构器。它不实现语句 del x (以上代码将不会翻译为 x.__del__())。它定义的是当一个对象进行垃圾回收时候的行为。当一个对象在删除的时需要更多的清洁工作的时候此方法会很有用,比如套接字对象或者是文件对象。注意,如果解释器退出的时候对象还存存在,就不能保证 __del__ 能够被执行
控制属性访问
__getattr__(self, name)
定义了当用户试图获取一个不存在的属性时的行为
__setattr__(self, name, value)
无论属性是否存在,都允许你定义对属性的赋值行为,以及可以对属性的值进个性定制。(小心防止无限递归现象发生)
__delattr__(self, name)
删除一个属性,相当于调用了 del self.name
在进行属性访问控制定义的时候你可能会很容易的引起一个错误。考虑下面的例子。
def __setattr__(self, name, value):
self.name = value
每当属性被赋值的时候, __setattr__()
会被调用,这样就造成了递归调用。这意味这会调用 self.__setattr__('name', value)
,每次方法会调用自己。这样会造成程序崩溃。
def __setattr__(self, name, value):
self.__dict__[name] = value #给类中的属性名分配值
#定制特有属性
Python的魔术方法非常强大,然而随之而来的则是责任。了解正确的方法去使用非常重要。
所以我们对于定制属性访问权限了解了多少呢。它不应该被轻易的使用。实际上,它非常强大。但是它存在的原因是:Python 不会试图将一些不好的东西变得不可能,而是让它们难以实现。自由是至高无上的,所以你可以做任何你想做的。以下是一个特别的属性控制的例子(使用 super 因为不是所有的类都有 dict 属性):
class AccessCounter:
'''一个包含计数器的控制权限的类每当值被改变时计数器会加一'''
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)
#如果你不想让其他属性被访问的话,那么可以抛出 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)]
会话管理
with open('foo.txt') as bar:
//do something
会话控制器通过包装一个 with 语句来设置和清理行为。它的行为通过 2 个魔术方法来定义:
__enter__
定义使用当使用 with 语句块的时候,会话管理器应该在<b>「初始块被创建的时候」</b>(也就是对象初始化的时候)的行为,该方法的返回值将被作为 with 语句的目标或 as 后的名字绑定
__exit__
定义当代码块执行完成或中途被终止之后,会话管理器应该做什么。通常可以用来处理异常,或者一些日志记录和清除工作。
class Sample:
def __enter__(self):
print("exec __enter__")
return "abc"
def __exit__(self, exception_type, exception_value, traceback):
print("exec __exit__")
def getSample():
return Sample()
with getSample() as sample:
print("Sample", sample)
执行结果:
exec __enter__
Sample abc
exec __exit__
运行流程:
- __enter__ 方法被执行
- __enter__ 的返回值赋值给 sample
- 执行代码块:print("Sample", sample)
- __exit__ 方法被执行
注意:
如果代码块执行成功,__exit__ 方法中的参数 exception_type , exception_value , 和 traceback 将会是 None 。否则的话你可以选择处理这个异常或者是直接交给用户处理。如果你想处理这个异常的话,确认 __exit__ 在所有结束之后会返回 True
附录:如何调用魔术方法
一些魔术方法直接和内建函数相对,在这种情况下,调用他们的方法很简单,但是,如果是另外一种不是特别明显的调用方法,这个附录介绍了很多并不是很明显的魔术方法的调用形式。
魔术方法 | 调用方式 | 解释 |
---|---|---|
init(self [,...]) | instance = MyClass(arg1, arg2) | init 在创建实例的时候被调用 |
cmp(self, other) | self == other, self > other, 等。 | 在比较的时候调用 |
pos(self) | +self | 一元加运算符 |
neg(self) | -self | 一元减运算符 |
invert(self) | ~self | 取反运算符 |
index(self) | x[self] | 对象被作为索引使用的时候 |
nonzero(self) | bool(self) | 对象的布尔值 |
getattr(self, name) | self.name | # name 不存在 访问一个不存在的属性时 |
setattr(self, name, val) | self.name = val | 对一个属性赋值时 |
delattr(self, name) | del self.name | 删除一个属性时 |
__getattribute(self, name) | self.name | 访问任何属性时 |
getitem(self, key) | self[key] | 使用索引访问元素时 |
setitem(self, key, val) | self[key] = val | 对某个索引值赋值时 |
delitem(self, key) | del self[key] | 删除某个索引值时 |
iter(self) | for x in self | 迭代时 |
contains(self, value) | value in self, value not in self | 使用 in 操作测试关系时 |
concat(self, value) | self + other | 连接两个对象时 |
call(self [,...]) | self(args) | “调用”对象时 |
enter(self) | with self as x: with | 语句环境管理 |
exit(self, exc, val, trace) | with self as x: with | 语句环境管理 |
getstate(self) | pickle.dump(pkl_file, self) | 序列化 |
setstate(self) | data = pickle.load(pkl_file) | 序列化 |