以下代码仅为满足好奇心而为,充满了形式主义。大多数代码都可通过普通方式实现。
我自己的C++学习小群(尽管现在只有我一个人(或许现在有两个人)):洛谷的团队向着胜利奔赴 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
什么是魔法方法
我们知道,在python的类中,我们可以定义各种各样的方法(method),而大部分方法需要手动调用,就像这样:
class Example(object):
def hello(self):
print("hello")
obj = Example()
obj.hello()
除此以外,通俗点说,不需要手动调用,就是解释器内部在某些特殊情况自动调用的方法,就是魔法方法。
在C++中,重载魔法方法就是重载运算符,比如int operator+(const int other);等
魔法方法为避免和普通方法冲突,都以“__”开头,并以其结尾。
魔法方法知多少
那有哪些不需要手动调用的方法呢?其实我们至少知道一个:
__init__
函数原型:def __init__(self) -> None
在类成员初始化的过程中,我们就用到了这个方法。
注意:__init__函数永远返回None
class Example(object):
def __init__(self):
self.name = "example"
def for_example(self):
print(f"for {self.name}")
obj = Example()
obj.for_example()
而在初始化成员的过程中,我们已经自动调用过这个方法了。那么,__init__方法在初始化过程中是不是第一个被调用的方法呢?
__new__
函数原型:def __new__(cls, *args, **kwargs)
实际上,在__init__方法执行之前,还有一个方法——__new__方法会先被执行。
需要注意的是,这虽然是一个类方法(class method),但是是唯一一个不需要使用@classmethod描述符的类方法。
记住,__new__方法的返回值是类对象(通过super调用),而参数第一个是cls(代表当前类),余下的是__init__函数的参数(除了self)。当然,如果你怕麻烦,也可以直接使用*args, **kwargs来节省时间(敲代码的时间)。
一个简单的例子:
class Example(object):
def __new__(cls, name):
print(name)
return super().__new__(cls)
def __init__(self, name):
self.name = name
print("example")
def for_example(self):
print(f"for example, it's called {self.name}")
obj = Example("jin")
obj.for_example()
结果表明,new是先于init执行的。实际上,编译器的工作原理是先从__new__方法获得对象,再把它放进__init__方法进行初始化,__new__方法是探索荒地,__init__方法是拓荒。
obj = __new__ -> __init__(obj)
__del__
函数原型:def __del__(self)
使用完对象后,我们自然要使用del关键字进行销毁。因此,我们又有了__del__魔法方法,在del obj的时候会进行调用。
class Example(object):
def __new__(cls, name):
print(name)
return super().__new__(cls)
def __init__(self, name):
self.name = name
print("example")
def __del__(self):
print("deleted")
def for_example(self):
print(f"for example, it's called {self.name}")
obj = Example("jin")
obj.for_example()
del obj
通过刚刚的代码,我相信你对del函数已经有了一个清楚的了解。
__call__
函数原型:def __call__(self, *args, **kwargs)
有的时候,如果我还希望这个对象能像函数一样工作,比如说在多态的运用中:
def run_func(func, *args, **kwargs):
return func(*args, **kwargs)
run_func(print, 1)
run_func(input)
这样就可以进行函数的调用,并把函数的调用的这一过程进行了封装。不过很明显,这里并不能传一个自定义对象
class Example(object):
pass
def run_func(func, *args, **kwargs):
func(*args, **kwargs)
obj = Example()
run_func(obj)
如果尝试运行这段代码,会报出错误:
Traceback (most recent call last):
line 10, in <module>
run_func(obj)
line 6, in run_func
func(*args, **kwargs)
TypeError: 'Example' object is not callable
错误的原因是因为对象是不可调用(not callable)的,那么如何让它变得可调用呢?不要担心,Python给我们提供了一个魔法方法来实现此功能。
__call__
比如说,下面这段代码:
class Example(object):
def __call__(self, *args, **kwargs):
print("For example")
def run_func(func, *args, **kwargs):
func(*args, **kwargs)
obj = Example()
run_func(obj)
在执行run_func中的func的过程中,func实际上就是Example类的__call__方法,如果我们直接使用obj()来调用,会得到相同的结果。
class Example(object):
def __call__(self, *args, **kwargs):
print("For example")
obj = Example()
obj()
__int__/__bool__/__complex__
函数原型:
def __int__(self) -> int
def __bool__(self) -> bool
def __complex__(self) -> complex
这三个函数的功能比较简单,__int__方法就是对此对象进行int(obj)方法返回的结果;__complex__方法就是对此对象进行complex(obj)方法返回的结果;__bool__方法就是对此对象进行bool(obj)方法返回的结果,同时,if obj和while obj中的obj的布尔值也是通过bool(obj),也就是obj.__bool__()来决定的,若__bool__未定义,bool(obj)的值永远为True。
For example:
class Example(object):
def __call__(self, *args, **kwargs):
print("For example")
def __int__(self):
return 100
def __bool__(self):
return False
obj = Example()
print(bool(obj))
print(int(obj))
__str__/__repr__
函数原型:
def __str__(self) -> str
def __repr__(self) -> str
这两个方法都是用来获得他们的字符串值的,print函数是调用了它的__str__方法,命令行窗口中直接obj会返回他的__repr__值,__str__方法对应的是str函数,__repr__方法对应的是repr函数。通俗的说str的值是给人(程序员)看的,repr的值是给system(Python解释器)看的。在__str__函数未定义的情况下,print(obj)会打印出一个莫名其妙的值。
下面给出了一个小例子:
class Example(object):
def __str__(self):
return "I'm an example."
def __repr__(self):
return "example"
obj = Example()
print(obj)
print(repr(obj))
__eq__/__ne__/__le__/__ge__/__lt__/__gt__/__cmp__
函数原型:def __func__(self, other) -> bool
下面我们要来介绍以下七个比较函数,他们的作用就是重载"==""!=""<="">=""<"">"运算符,而最后一个__cmp__魔法方法,它代表的是使用sort进行排序时的默认比较函数。由于比较简单,我就不放例子了。
注意:__cmp__魔法方法应当在self < other时返回负整数,self == other时返回0,self > other时返回正整数。
__add__/__sub__/__mul__/__truediv__/__floordiv__/__mod__/__pow__
函数原型:def __func__(self, other)
这七个方法同样比较简单,就是加、减、乘、除、地板除、模、乘方七个函数,下面放一个简单的小例子:
class Example(object):
name = ""
def __add__(self, string):
new_obj = Example()
new_obj.name = self.name + string
return new_obj
obj = Example()
print(obj.name)
obj = obj + "for"
print(obj.name)
obj = obj + " example"
print(obj.name)
__iadd__/__isub__/__imul__/__itruediv__/__ifloordiv__/__imod__/__ipow__
函数原型:def __func__(self, other)
这七个函数和前面很像,它们定义的赋值运算,如__iadd__定义的是+=操作符,这是一个小例子:
class Example(object):
name = ""
def __iadd__(self, string):
self.name += string
return self
obj = Example()
print(obj.name)
obj += "for"
print(obj.name)
obj += " example"
print(obj.name)
注意:这七个函数最后一定要返回self,因为它实际上就是__add__函数,只是解释器自动把原来的obj更新成了__iadd__函数返回的self,也就是obj += other是obj = obj.__iadd__(other);obj = obj + other是obj = obj.__add__(other)
__radd__/__rsub__/__rmul__/__rtruediv__/__rfloordiv__/__rmod__/__rpow__
函数原型:def __func__(self, other)
显然,这七个函数和前面的也很像,如
obj + other,当obj不支持__add__方法或__add__方法返回NotImplemented时被调用,也就是:
def add(a, b):
try:
value = a.__add__(b)
if value == NotImplemented:
raise TypeError
return value
except TypeError:
value = b.__radd__(a)
if value == NotImplemented:
raise TypeError
return value
下面放一个简单的小例子,其实就是把前面__add__函数改成了__radd__函数:
class Example(object):
name = ""
def __radd__(self, string):
new_obj = Example()
new_obj.name = self.name + string
return new_obj
obj = Example()
print(obj.name)
obj = "for" + obj
print(obj.name)
obj = " example" + obj
print(obj.name)
__pos__/__neg__/__abs__
函数原型:
def __pos__(self)
def __neg__(self)
def __abs__(self)
这三个函数的重载也比较简单,我就不放demo了。其中__pos__的意思是正号的结果,__neg__是负号的结果,__abs__是abs(obj)的结果,即:
+obj => obj.__pos__()
-obj => obj.__neg__()
abs(obj) => obj.__abs__()
__lshift__/__rshift__/__and__/__or__/__xor__/__invert__
函数原型:
def __lshift__(self, other)
def __rshift__(self, other)
def __and__(self, other)
def __or__(self, other)
def __xor__(self, other)
def __invert__(self)
这六个函数都是和位运算相关的,可能稍许有些难度,就不展开讲了,它们分别重载了
"<<"">>""&""|""^""~"运算符,有需要可酌情使用。
一道练习题
实现一个高精度类型(即它的每一位数字都存储在一个列表里)
E.G.
class LongInt(object):
value = []
def init(self, value):
while value:
self.value.insert(0, value % 10)
value //= 10
integer = LongInt()
integer.init(13579)
print(integer.value)
你需要重载LongInt类型的运算符,任务如下:
必做:
在无符号范围内:
- 比较运算符;
- 加减运算符;
- 对应的赋值运算符和反运算符;
- 初始化(重载__new__,可以不重载__init__);
- 转int,float类型;
- 字符串表示及repr表示;
- 销毁运算(重载del obj).
选做:
- 位运算;
- 乘除运算符;
- 乘方运算,模运算,地板除法运算;
- 带符号运算(正负号),绝对值;
- 一个Complex类:复数的四则运算;
- 自定义快读/快写函数.
如果你写好了,可以把代码发到评论区里(如果你不想被抄袭,也可以直接发给我)。
Time to 点赞
看完后,别忘了
点赞!
收藏!
Thanks……