魔数方法(Magic Method)

image.png

简介

所谓魔法函数(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__

运行流程:

  1. __enter__ 方法被执行
  2. __enter__ 的返回值赋值给 sample
  3. 执行代码块:print("Sample", sample)
  4. __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) 序列化
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容