Python Magic Function

前言

Python的魔法函数是指Python的类中,一系列函数名由双下划线包裹的函数。

笔者最初接触到魔法函数的使用是在Pytorch中,在Pytorch中的Dataset类中有这样的用法:

image-20210904114042487

除了常见的__init__构造函数外,还有__getitem__和__len__函数。在之后的代码中,笔者并没有看到__getitem__和__len__函数的显示调用。那么这样的声明与定义有什么意义?

首先定义一个空类,并用dir方法获取类中的所有方法(一个空类真的是空空如也吗?)

1
2
3
4
class Foo:
    pass

print(dir(Foo))

结果:

1
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

我们可以看到许多默认的方法。比如每一个类都有一个默认的__init__方法作为其构造函数。而如果想定义自己的构造函数,就需要显式地对构造函数进行定义。

下面结合实例来说明一些魔法函数的用处。

基本

__new__(cls[,args...])

一个对象实例化的时候所调用的第一个方法。

__init__(self[, args...])

默认的构造函数,用于类的初始化。

注:__init__和__new__的区别:

在一般情况下,我们在构造类时调用的是__init__,但实际上第一个被调用的方法是__new__。以下代码为证:

1
2
3
4
5
6
7
8
9
class Bar:
 def __new__(cls, num):
     print("use __new__ method")
     return super(Bar, cls).__new__(cls)

 def __init__(self, num):
     print("use __init__ method")
     self.num = num
bar = Bar(1)

结果为:

1
2
use __new__ method
use __init__ method

一般情况下,我们放心使用__init__方法进行对象的构造即可,只需大致理解__new__和__init__的区别即可:

__new__:创建对象时调用,会返回当前对象的一个实例

__init__:创建完对象后调用,对当前对象的一些实例初始化,无返回值

__str__(self)

定义使用print()时的行为。

__repr__(self)

与str方法类似,但更面向开发者。如果一个类同时定义str()和repr(),那么会优先使用str()。

注:str和repr的返回值必须为str

__call__(self[, args...])

像函数一样调用类的实例。可以接参数。

__get__(self)

定义用对象为其他成员赋值的行为。

__set__(self[,args...])

定义为其他数据类型为对象赋值时的行为。

__del__(self)

默认的析构函数。

代码实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Foo:
    def __init__(self,num):
        self.num = num
        print("constructor")
    def __del__(self):
        print("destructor")
    def __repr__(self):
        return "the repr"
    def __call__(self, a):
        print("__call__ is used, the value is {}".format(a))
    def __get__(self):
        return self.num
    def __set__(self,num):
        self.num = num
        
foo = Foo(0) #  __init__
print(foo) # __str__或__repr__
foo(1) # __call__
foo = 2 # __set__
num = foo # __get__
# 析构
print(num)

运算符重载

Python的类中定义了大量一元运算符、二元运算符、类型转换运算符等。此处不再一一赘述。

函数 功能
__lt__(self, other) 定义小于号的行为:x < y 调用 x.lt(y)
__le__(self, other) 定义小于等于号的行为:x <= y 调用 x.le(y)
__eq__(self, other) 定义等于号的行为:x ` y 调用 x.eq(y)
__ne__(self, other) 定义不等号的行为:x != y 调用 x.ne(y)
__gt__(self, other) 定义大于号的行为:x > y 调用 x.gt(y)
__ge__(self, other) 定义大于等于号的行为:x >= y 调用 x.ge(y)
__add__(self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
__truediv__(self, other) 定义真除法的行为:/
__floordiv__(self, other) 定义整数除法的行为://
__mod__(self, other) 定义取模算法的行为:%
__divmod__(self, other) 定义当被 divmod() 调用时的行为
__pow__(self, other[, modulo]) 定义当被 power() 调用或 ` 运算时的行为
__lshift__(self, other) 定义按位左移位的行为:«
__rshift__(self, other) 定义按位右移位的行为:»
__and__(self, other) 定义按位与操作的行为:&
__xor__(self, other) 定义按位异或操作的行为:^
__or__(self, other) 定义按位或操作的行为:|
__radd__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rsub__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rmul__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rtruediv__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rfloordiv__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rmod__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rdivmod__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rpow__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rlshift__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rrshift__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rand__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__rxor__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__ror__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
__iadd__(self, other) 定义赋值加法的行为:+=
__isub__(self, other) 定义赋值减法的行为:-=
__imul__(self, other) 定义赋值乘法的行为:*=
__itruediv__(self, other) 定义赋值真除法的行为:/=
__ifloordiv__(self, other) 定义赋值整数除法的行为://=
__imod__(self, other) 定义赋值取模算法的行为:%=
__ipow__(self, other[, modulo]) 定义赋值幂运算的行为:`=
__ilshift__(self, other) 定义赋值按位左移位的行为:«=
__irshift__(self, other) 定义赋值按位右移位的行为:»=
__iand__(self, other) 定义赋值按位与操作的行为:&=
__ixor__(self, other) 定义赋值按位异或操作的行为:^=
__ior__(self, other) 定义赋值按位或操作的行为:|=
__pos__(self) 定义正号的行为:+x
__neg__(self) 定义负号的行为:-x
__abs__(self) 定义当被 abs() 调用时的行为
__invert__(self) 定义按位求反的行为:~x
__complex__(self) 定义当被 complex() 调用时的行为(需要返回恰当的值)
__int__(self) 定义当被 int() 调用时的行为(需要返回恰当的值)
__float__(self) 定义当被 float() 调用时的行为(需要返回恰当的值)
__round__(self[, n]) 定义当被 round() 调用时的行为(需要返回恰当的值)

容器

假设对象名为foo:

__len__(self)

定义调用len()时的返回值。(必须为int)

__getitem__(self,index)

定义调用foo[index]的返回值。

__setitem__(self,index,value)

定义为foo[index]赋值为value的行为。

__delitem__(self, key)

删除指定索引对应的元素。

__contains__(self, item)

判断序列是否包含指定元素。

迭代

__iter__(self)

生成迭代对象时调用,返回值必须是对象自己。

__next__(self)

每一次for循环都调用该方法。

如果一个类实现了iter()和next()方法,那么就认为它是一个可迭代对象。

以下用一个简单的实例(简单的dataloader)说明上述函数的用法:

 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
class Dataloader:
    def __init__(self,picList):
        self.picList = picList
        self.index = -1
    def __len__(self):
        return len(self.picList)
    def __getitem__(self,index):
        return self.picList[index]
    def __setitem__(self,index,value):
        self.picList[index] = value
    def __delitem__(self,index):
        print("{} will be deleted".format(self.picList[index]))
        del(self.picList[index])
    def __contains__(self,name):
        return self.picList.__contains__(name)
    def __iter__(self):
        return self
    def __next__(self):
        self.index+=1
        if(self.index<len(self)):
            return self.picList[self.index]
        else:
            raise StopIteration

if __name__ ` '__main__':
    picList = ['cat.png','dog.png','sheep.png','pig.png']
    dataloader = Dataloader(picList)
    # __len__
    print(len(dataloader))
    # __getitem__
    pic1 = dataloader[2]
    print(pic1)
    # __setitem__
    dataloader[2] = 'horse.png'
    # __contains__
    print('horse.png' in dataloader)
    # __delitem__
    del(dataloader[2])
    print(len(dataloader))
    # __iter__和__next__
    for pic in dataloader:
        print(pic)

回头再看开头的那个pytorch程序,是不是就没那么难了呢?

后记

实际上Python的魔法函数远不止这些。感兴趣的读者可以参阅https://rszalski.github.io/magicmethods/

Built with Hugo
Theme Stack designed by Jimmy
visitors: total visits: time(s) reads: time(s)