Python进阶 -- 魔法方法

魔法方法及其作用

在Python中的内置类型都支持一些统一的函数接口,比如len, print, bool等,这些函数是如何实现的呢?当我们建立自己的数据类型时,如何表现的很pythonic,也就是让我们自己的数据类型也能支持这些操作呢?

在Python中有一些以双下划线开头和结尾的方法,叫做魔法方法,这就是实现这些统一接口的关键。

例如我们想生成一个Classmate类来保存一个班级的名单,想让len()能够取到里面保存的人数,想让print()能够打印出名单,想让bool()返回名单是否为空,那么如何组织我们的类,让我们可以像使用python内置类型一样使用它呢?

_no_value = object()


class Classmate(object):
    def __init__(self, name_lst=_no_value):
        if name_lst == _no_value:
            self._name_lst = list()
        else:
            self._name_lst = list(name_lst)  # 建立拷贝,防止改动传入数据

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

    def __repr__(self):
        return str(self._name_lst)

    def __bool__(self):
        return len(self._name_lst) != 0  # 其实可以不写,python会自动调用__len__


if __name__ == '__main__':
    c = Classmate(['Xiao Zhang', 'Lao Wang', 'Xiao Zhao'])
    print(c)  # ['Xiao Zhang', 'Lao Wang', 'Xiao Zhao']
    print(bool(c))  # True
    print(len(c))  # 3

在上面的例子中,我们可以看到,尽管Classmate并非python中内置的类,我们仍然可以通过实现魔法方法,将它的使用方式和内置类的使用方式统一起来。

常用魔法方法总结

下面总结一下python中常用的魔法方法,并给出使用的例子

二元操作符

+   object.__add__(self, other)
-   object.__sub__(self, other)
*   object.__mul__(self, other)
//  object.__floordiv__(self, other)
/   object.__div__(self, other)
%   object.__mod__(self, other)
**  object.__pow__(self, other[, modulo])
<<  object.__lshift__(self, other)
>>  object.__rshift__(self, other)
&   object.__and__(self, other)
^   object.__xor__(self, other)
|   object.__or__(self, other)

我们用一个二维向量的例子来演示其中几个方法的作用:

class Vector(object):
    def __init__(self, vx, vy):
        self._vx = vx
        self._vy = vy

    def __add__(self, other):
        return Vector(self._vx + other.get_x(), self._vy + other.get_y())

    def __sub__(self, other):
        return Vector(self._vx - other.get_x(), self._vy - other.get_y())

    def __pow__(self, power, modulo=None):
        return Vector(pow(self._vx, power), pow(self._vy, power))

    def __repr__(self):
        return f"Vector({self._vx}, {self._vy})"

    def get_x(self):
        return self._vx

    def get_y(self):
        return self._vy


if __name__ == '__main__':
    v1 = Vector(3.0, 5.0)
    v2 = Vector(-1.0, 2.0)
    print(v1 + v2)  # Vector(2.0, 7.0)
    print(v1 - v2)  # Vector(2.0, 7.0)
    print(v1 ** 2)  # Vector(9.0, 25.0)

可以看到在定义了__add__, __sub__, __pow__之后,我们自定义的Vector类也可以像python中内置的int, float类型一样,进行相加、相减、幂次操作了。需要注意的是,在完成二元操作符对应的魔法方法时,我们需要返回一个相同类型的对象,这是因为我们需要考虑使用者进行连续加、连续减等操作的可能性。

扩展二元操作符

+=  object.__iadd__(self, other)
-=  object.__isub__(self, other)
*=  object.__imul__(self, other)
/=  object.__idiv__(self, other)
//= object.__ifloordiv__(self, other)
%=  object.__imod__(self, other)
**= object.__ipow__(self, other[, modulo])
<<= object.__ilshift__(self, other)
>>= object.__irshift__(self, other)
&=  object.__iand__(self, other)
^=  object.__ixor__(self, other)
|=  object.__ior__(self, other)

扩展二元操作符的使用方法和二元操作符基本一致:

class Vector(object):
    def __init__(self, vx, vy):
        self._vx = vx
        self._vy = vy

    def __add__(self, other):
        return Vector(self._vx + other.get_x(), self._vy + other.get_y())

    def __iadd__(self, other):
        return Vector(self._vx + other.get_x(), self._vy + other.get_y())

    def __sub__(self, other):
        return Vector(self._vx - other.get_x(), self._vy - other.get_y())

    def __pow__(self, power, modulo=None):
        return Vector(pow(self._vx, power), pow(self._vy, power))

    def __repr__(self):
        return f"Vector({self._vx}, {self._vy})"

    def get_x(self):
        return self._vx

    def get_y(self):
        return self._vy


if __name__ == '__main__':
    v1 = Vector(3.0, 5.0)
    v2 = Vector(-1.0, 2.0)
    v1 += v2
    print(v1)  # Vector(2.0, 7.0)

在定义了__iadd__方法之后,+=也可以被用于我们的类了

一元操作符

-   object.__neg__(self)
+   object.__pos__(self)
abs()   object.__abs__(self)
~   object.__invert__(self)
complex()   object.__complex__(self)
int()   object.__int__(self)
long()  object.__long__(self)
float() object.__float__(self)
oct()   object.__oct__(self)
hex()   object.__hex__(self)
round() object.__round__(self, n)
floor() object__floor__(self)
ceil()  object.__ceil__(self)
trunc() object.__trunc__(self)

比较函数

<   object.__lt__(self, other)
<=  object.__le__(self, other)
==  object.__eq__(self, other)
!=  object.__ne__(self, other)
>=  object.__ge__(self, other)
>   object.__gt__(self, other)

类的表示与输出

str()   object.__str__(self) 
repr()  object.__repr__(self)
len()   object.__len__(self)
hash()  object.__hash__(self) 
bool()  object.__nonzero__(self) 
dir()   object.__dir__(self)
sys.getsizeof() object.__sizeof__(self)

__str__方法与__repr__方法

在类的表示中__str____repr__方法值得一提,这两个方法在一定程度上是有重合的:

  • 他们都提供了将对象转化为某种字符串的方式
  • 当对象没有实现__str__方法时,会用__repr__方法替代

对于他们的使用方式,可以简单概括为:

  • 如果只想要实现其中之一,那么实现__repr__
  • 如果想要输出的结果可读性更强,那么可以选择去实现__str__

我们可以看一下下面的例子:

class Vector(object):
    def __init__(self, vx, vy):
        self._vx = vx
        self._vy = vy

    def __repr__(self):
        return f"Repr Vector({self._vx}, {self._vy})"

    def __str__(self):
        return f"Str Vector({self._vx}, {self._vy})"


if __name__ == '__main__':
    v = Vector(3.0, 5.0)
    print(v)  # Vector({self._vx}, {self._vy})
    print("%s" % v)  # Str Vector(3.0, 5.0)
    print("%r" % v)  # Vector({self._vx}, {self._vy})
    print(str(v))  # Vector({self._vx}, {self._vy})

如果我们注释掉__repr__方法,得到的结果为:

Str Vector(3.0, 5.0)
Str Vector(3.0, 5.0)
<__main__.Vector object at 0x10c4e0eb8>
Str Vector(3.0, 5.0)

如果我们注释掉__str__方法,得到的结果为:

Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)

对比第二行的结果,说明了当没有__str__方法时,会调用__repr__的结果,但是当没有实现__repr__方法时,则会用默认的__repr__输出类似return "%s(%r)" % (self.__class__, self.__dict__)的结果。

类容器实现

类容器的实现方法告诉编译器我们的类将执行迭代、调用、索引等行为:

len()   object.__len__(self)
self[key]   object.__getitem__(self, key)
self[key] = value   object.__setitem__(self, key, value)
从对象取切片 object.__getslice__(self, start, end)
为切片设置值 object.__setslice__(self, start, end, sequence)
删除切片 object.__delslice__(self, start, end)
del[key] object.__delitem__(self, key)
iter()  object.__iter__(self)
reversed()  object.__reversed__(self)
in操作    object.__contains__(self, item)
字典key不存在时   object.__missing__(self, key)

__getitem__、__setitem__和__delitem__

这三个方法用于从类容器中用键取值,设置了这三个函数之后,就可以对类容器直接用键来取得、修改和删除里面的键值对。例如:

class Classmate(object):
    """A demo class which stores some name-age pairs"""

    def __init__(self, **kwargs):
        self._info = kwargs

    def __str__(self):
        return str(self._info)

    def __getitem__(self, item):
        print("__getitem__")
        if item in self._info:
            return self._info[item]
        else:
            print("Name not found")

    def __setitem__(self, key, value):
        print("__setitem__")
        self._info[key] = value

    def __delitem__(self, key):
        print("__delitem__")
        if key in self._info:
            del self._info[key]


c = Classmate(laowang=50, xiaohuang=18, xiaoli=17, laojin=66)
print(c["laowang"])  # 调用__getitem__
c["xinren"] = 20  # 调用__setitem__
del c["xiaohuang"]  # 调用__delitem__
print(c)

同时,在python3中,将python2对容器进行切片操作的魔法方法__getslice__、__setslice__和__delslice__也整合到了这三个方法当中。如下例:

class someClass(object):
    """ This is description for the class"""

    def __init__(self, startVal, endVal):
        if endVal > startVal:
            self._lst = list(range(startVal, endVal))
        else:
            self._lst = list(range(0, 10))

    def __getitem__(self, index):
        print("__getitem__")
        if isinstance(index, slice):
            print(self._lst[index.start:index.stop:index.step])

    def __setitem__(self, index, value):
        print("__setitem__")
        if isinstance(index, slice):
            self._lst[index.start:index.stop:index.step] = value

    def __delitem__(self, index):
        print("__delitem__")
        if isinstance(index, slice):
            del self._lst[index.start:index.stop:index.step]

    def __str__(self):
        return str(self._lst)


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

推荐阅读更多精彩内容