21-Python类机制的深度解析: 全方位介绍Python中的魔法方法,一网打尽
楔子
下面我们来看一下Python中的魔法方法,我们知道Python将操作符都抽象成了一个魔法方法(magic method),实例对象进行操作时,实际上会调用魔法方法。也正因为如此,numpy才得以很好的实现。
那么Python中常见的魔法方法都有哪些呢?我们按照特征分成了几类,下面就来看看魔法方法都有哪些,然后再举例说明它们的用法。
魔法方法概览
我们根据不同的特征分为了以下几类:
注意:有的方法是Python2中的,但是在Python3中依然存在,但是不推荐使用了。比如:__cmp__、__coerce__等等,我们就没有画在图中。
下面我们就来介绍一下上面的那些魔法方法的实际用途。
魔法方法介绍
构建以及初始化
__new__和__init__我们之前已经见识过了,还有一个__del__是做什么 呢?我们一起来看一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Girl:
def __new__(cls, *args): print("__new__") return object.__new__(cls)
def __init__(self): print("__init__")
def __del__(self): print("__del__")
girl = Girl() print("################") """ __new__ __init__ ################ __del__ """
|
__del__被称为析构函数,当一个实例对象被销毁之后会调用该函数。如果没有销毁,那么程序结束时也会调用。
比较操作
Python的比较操作符也抽象成了魔法方法,a == b,等价于a.eq(b)
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
| class Girl:
def __init__(self, name): self.name = name
def __eq__(self, other): return "==", self.name, other.name
def __ne__(self, other): return "!=", self.name, other.name
def __le__(self, other): return "<=", self.name, other.name
def __lt__(self, other): return "<", self.name, other.name
def __ge__(self, other): return ">=", self.name, other.name
def __gt__(self, other): return ">", self.name, other.name
girl1 = Girl("girl1") girl2 = Girl("girl2")
print(girl1 == girl2) print(girl1 != girl2) print(girl1 < girl2) print(girl2 <= girl1) print(girl2 > girl1) print(girl2 >= girl1)
|
我们看到如果是a > b,那么会调用a的__gt__方法,self就是a、other就是b;如果是b > a,那么调用b的__gt__方法,self就是b、other就是a;也就是说谁在前面,就调用谁的魔法方法。
但如果a > b,并且type(a)内部没有定义__gt__呢?那么会尝试调用type(b)内部的__gt__,如果都没有定义,那么就会调用object的__gt__,显然这个时候就会报错了。
注意:如果操作符两边有一个是内置对象、或者内置对象的实例对象,那么会直接调用我们创建的实例对象的魔法方法(前提是定义了)
。比如:123 != girl1,那么直接调用girl1的__ne__,尽管整数对象也有__ne__。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Girl:
def __init__(self, name): self.name = name
def __eq__(self, other): return self.name, other
girl = Girl("matsuri")
print(girl == 123) print(123 == girl) print(object == girl) print(girl == object)
|
单目运算
下面再来看看单目运算,估计很多人都不一定能百分百说出对应魔法方法的作用。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| class Girl:
def __pos__(self): return "__pos__"
def __neg__(self): return "__neg__"
def __abs__(self): return "__abs__"
def __invert__(self): return "__invert__"
def __round__(self, n=None): return f"__round__, {n}"
def __floor__(self): return "__floor__"
def __ceil__(self): return "__ceil__"
def __trunc__(self): return "__trunc__"
girl = Girl() import numpy as np import math
print(+girl)
print(-girl) """ 注意: 不可以写成 0 + girl 和 0 - girl, 尽管我们知道在数学上这与 girl和-girl是等价的 但是在Python中不行, 因为这样会调用girl的__radd__和__rsub__, 我们后面会说 """
print(abs(girl)) print(np.abs(girl))
print(~girl)
print(round(girl)) print(round(girl, 2))
print(math.floor(girl)) print(np.floor(girl))
print(math.ceil(girl)) print(np.ceil(girl))
print(math.trunc(girl)) print(np.trunc(girl))
try: int(girl) except Exception as e: print(e) Girl.__trunc__ = lambda self: 666 print(int(Girl()))
|
以上便是单目运算的一些魔法方法,但是说实话个人觉得只有__pos__、__neg__、__invert__会用上,因为我们可能希望一些操作的调用方式尽可能简单,所以会通过重些+、-、~ 操作符对应的魔法方法,来赋予实例对象一些特殊的含义。
至于其它的简单了解一下即可,不过注意的是,有些方法numpy也是可以是使用的,但是并不推荐。
算术运算
算术运算是比较常用的了,我们来看看算数运算对应的魔法方法。
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| class Girl:
def __add__(self, other): return "__add__"
def __sub__(self, other): return "__sub__"
def __mul__(self, other): return "__mul__"
def __floordiv__(self, other): return "__floordiv__"
def __truediv__(self, other): return "__truediv__"
def __mod__(self, other): return "__mod__"
def __divmod__(self, other): return "__divmod__"
def __pow__(self, power, modulo=None): return "__pow__"
def __lshift__(self, other): return "__lshift__"
def __rshift__(self, other): return "__rshift__"
def __and__(self, other): return "__and__"
def __or__(self, other): return "__or__"
def __xor__(self, other): return "__xor__"
def __matmul__(self, other): return "__matmul__"
girl1 = Girl() girl2 = Girl()
print(girl1 + girl2) print(girl1 - girl2) print(girl1 * girl2) print(girl1 // girl2) print(girl1 / girl2) print(girl1 % girl2) print(divmod(girl1, girl2)) print(girl1 ** girl2) print(girl1 << girl2) print(girl1 >> girl2) print(girl1 & girl2) print(girl1 | girl2) print(girl1 ^ girl2)
|
常见的算术运算大概就是上面这些,还是很简单的。
反射算术运算
反射算术运算指的是什么呢?比如: a + b,我们知道会调用a的__add__,但如果type(a)中没有定义__add__,那么会尝试寻找b的__radd__。
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
| class A:
def __add__(self, other): return "class A:", type(self).__name__, type(other).__name__
class B:
def __radd__(self, other): return "class B:", type(self).__name__, type(other).__name__
a = A() b = B()
print(a + b)
del A.__add__ print(a + b)
""" 如果是一方为内置对象, 比如: a + 123: 直接调用a的__add__ 123 + a: 直接调用a的__radd__ """ print(123 + b)
try: 123 + a except Exception as e: print(e)
A.__add__ = lambda self, other: (self, other) print(a + "xxx")
|
其它操作符也是类似的,a 操作符 b
会调用a的__xxx__
,但如果a没有,会尝试搜寻b的__rxxx__
。
赋值算术运算
赋值算术运算适用于类似于+=这种形式,比如:
1 2 3 4 5 6 7 8 9 10
| class A:
def __iadd__(self, other): return type(self).__name__ + other
a = A()
a += ">>>" print(a)
|
比较简单,其它的也与此类似。
序列操作
下面我们看看序列操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class A:
def __len__(self): return 123
a = A() print(len(a))
a.__len__ = "xxx" print(a.__len__) print(len(a))
|
此外,__len__还有充当布尔值的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class A: pass
print(bool(A()))
A.__len__ = lambda self: 0 print(bool(A()))
A.__bool__ = lambda self: True print(bool(A()))
|
所以解释器具有退化功能,会优先寻找某个方法,但如果没有,那么会退化寻找替代方法。在后面,我们还会看到类似的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class A:
def __getitem__(self, item): print(item)
def __setitem__(self, key, value): print(key, value)
def __delitem__(self, key): print(key)
a = A() a["xxx"] a["xxx"] = "yyy" del a["aaa"]
a[3: 4] a["你好": "我很可爱": "请亏我全"] a["你好": "我很可爱": "请亏我全"] = "屑女仆" del a["神乐mea": "迷迭迷迭帕里桑"]
|
这里我们再着重说一下__getitem__,我们说Python的for循环本质上会调用内部的__iter__,但如果内部没有定义,那么解释器会退化寻找__getitem__。
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
| class A:
def __getitem__(self, item): return item
lst = [] for idx in A(): if idx > 5: break lst.append(idx)
print(lst)
class B:
def __init__(self): self.lst = ["古明地觉", "芙兰朵露", "雾雨魔理沙", "八意永琳", "琪露诺"] self.__len = len(self.lst)
def __getitem__(self, item): if item == self.__len: raise StopIteration return self.lst[item]
print(list(B())) (lst := []).extend(B()) print(lst)
|
怎么样,是不是很神奇呢?当然for循环肯定是优先寻找__iter__,没有的话会进行退化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class A:
def __reversed__(self): return "__reversed__"
def __contains__(self, item): return item
print(reversed(A()))
print("xx" in A()) print("" in A()) print([] in A())
|
最后一个__missing__比较特殊,它是针对于字典的,我们来看一下。
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
| class A(dict):
def __missing__(self, key): return str(key).upper()
a = A({"name": "夏色祭", "age": -1}) print(a["name"]) print(a["Name"])
class MyDict(dict):
def __getitem__(self, item): return super().__getitem__(item)
def __missing__(self, key): return f"{key!r}不存在"
d = MyDict({"name": "夏色祭", "age": -1}) print(d["age"]) print(d["AGE"])
|
类型转换
很简单的内容了,我们直接来看一下。
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
| class A:
def __int__(self): return 123
def __index__(self): return 789
print(int(A()))
del A.__int__ print(int(A()))
class B:
def __float__(self): return 3.
print(float(B()))
class C: def __complex__(self): return 1 + 3j
print(complex(C()))
|
上下文管理
这部分不说了,可以看我的这一篇博客:https://www.cnblogs.com/traditional/p/11487979.html
,通过源码分析contextlib标准库介绍with语句。
属性访问
__getattr__、__setattr__、__delattr__和我们之前说的__getitem__、__setitem__、__delitem__类似,只不过这里是通过.
的方式来访问的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class A:
def __getattr__(self, item): print(item)
def __setattr__(self, key, value): print(key, value)
def __delattr__(self, item): print(item)
a = A() a.name a.name = "夏色祭" del a.age
|
getattr、setattr、delattr这几个内置函数本质上也是调用这几个魔法方法,只不过它额外做了一些其它的工作。以getattr为例:
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
| class A:
def __init__(self): self.name = "夏色祭" self.age = -1
print(getattr(A(), "name", "不存在的属性")) print(getattr(A(), "gender", "不存在的属性"))
class B:
def __init__(self): self.name = "夏色祭" self.age = -1
def __getattr__(self, item): try: return self.__dict__[item] except KeyError: raise AttributeError
print(getattr(B(), "NAME", "不存在的属性"))
|
对象调用
这一点我们好像很早之前就说过了,一个对象能否被调用,取决于它的类对象中是否定义了__call__。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Deco:
def __init__(self, func): self.func = func
def __call__(self, *args, **kwargs): print("start") res = self.func(*args, **kwargs) print("end") return res
@Deco def foo(name, age): print(name, age)
foo("夏色祭", -1) """ start 夏色祭 -1 end """
|
小结
剩下的内容比较简单,当然描述符我们之前就说过了。最主要的是魔法方法的话,可以自己试一下就知道它们是干什么的了,没太大难度,这里就不说了。