二、魔法方法
魔法方法是在Python的类中被双下划线前后包围的方法,如常见的 :__init__、__new__、__del__等。这些方法在类或对象进行特定的操作时会自动被调用,我们可以使用或重写这些魔法方法,给自定义的类添加各种特殊的功能来满足自己的需求。
2.1 构造类的魔法方法
__init__ 可以定义一个对象的初始操作。当实例化我们定义的类,如x = test() 的时候, __init__ 并不是第一个被调用的方法。实际上,还有一个叫做 __new__ 的方法,来实例化这个对象。然后给在开始创建时候的初始化函数来传递参数。在对象生命周期的另一端,也有一个__del__ 方法。
(1)__new__(cls, […]) 是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。它的第一个参数是这个类,其他的参数是用来直接传递给__init__ 方法。__new__ 决定是否要使用该 __init__ 方法,因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果__new__ 没有返回实例对象,则__init__ 不会被调用。
class Animal(object):
def __new__(cls, *args, **kwargs):
print("__new__()方法被调用了")
print('这个是*agrs', *args)
print('这个是kwagrs', **kwargs)
# cls表示这个类,剩余所有的参数传给__init__()方法,
# 若不返回,则__init__()不会被调用
return object.__new__(cls)
def __init__(self, name, age):
print("__init__()方法被调用了")
self.name = name
self.age = age
print(self.name, self.age)
A = Animal("花花", 5)
当我们需要继承内置类时,例如,想要继承 int、str、tuple,就无法使用__init__ 来初始化了,只能通过__new__ 来初始化数据。
实际上,在日常的编写中,__new__()和__del__()这两个魔法函数一般都不常见,保持默认就好,经常编写的是__init__()函数。
class Washer:
# 析构器,当一个实例被销毁时自动调用的方法。
def __del__(self):
"""
当删除对象时,解释器会自动调用del方法
"""
print('对象已删除!')
a = Washer()
2.2 表示类的魔法方法
类的表示相关的魔法方法,主要包括__str__()和__repr__()两种。这两个方法都是用来描述类或对象信息的,比如你直接实例化了一个对象,打印出来的是这个对象的地址。而要是重新在类中定义了这两个方法,那打印对象的结果就是方法返回的信息。
class Animal:
def __int__(self):
pass
cat = Animal()
print(cat)
重新在类中定义了__str__()和__repr__()这两个方法,那打印对象的结果就是方法返回的信息。
class Animal:
def __int__(self):
pass
def __repr__(self):
return '我是__repr__()魔法方法!'
def __str__(self):
"""
这个str的作用就是:类的说明或对象状态的说明
:return:
"""
return '我是__str__魔法方法!'
Cat = Animal()
# 不定义str方法,直接打印,结果是对象的内存地址,定义了str方法,
# 显示的就是str方法返回的内容
print(Cat)
要是同时写了这两个方法,只会调用__str__方法。都是用来描述类或对象的信息,那为啥要定义两个呢? 设计的目的是不一样的: 1. __repr__的目标是准确性,或者说,__repr__的结果是让解释器用的。 2. __str__的目标是可读性,或者说,__str__的结果是让人看的。一般的,也可以只定义__str__方法而不定义__repr__方法。
2.3 容器类的魔法方法
容器类的魔法方法,主要包括:
__setitem__(self, key, value):定义设置容器中指定元素的操作 ,相当于 self[key] = value;
__getitem__(self, key): 定义获取容器中指定元素的操作 ,相当于 self[key];
__delitem__(self, key):定义删除容器中指定元素的操作 ,相当于 del self[key];
__len__(self):定义当被 len() 调用时的操作(返回容器中元素的个数);
__iter__(self):定义迭代容器中的元素的操作;
__contains__(self, item):定义当使用成员测试运算符(in 或 not in)时的操作;
__reversed__(self):定义当被 reversed() 调用时的操作。
Python 中常见的容器类型有:
- 字典
- 元组
- 列表
- 字符串
因为它们都是「可迭代」的。可迭代是因为,它们都实现了容器协议,也就是下面要介绍到的魔法方法。下面通过自己定义类实现列表,来说明这些方法的用法:
class MyList(object):
"""自己实现一个list"""
def __init__(self, values=None):
# 初始化自定义list
self.values = values or []
self._index = 0
def __setitem__(self, idx, value):
# 添加元素
self.values[idx] = value
def __getitem__(self, idx):
# 获取元素
return self.values[idx]
def __delitem__(self, idx):
# 删除元素
del self.values[idx]
def __len__(self):
# 自定义list的元素个数
return len(self.values)
def __iter__(self):
# 可迭代
return self
def __next__(self):
# 迭代的具体细节
# 如果__iter__返回self 则必须实现此方法
if self._index >= len(self.values):
raise StopIteration()
value = self.values[self._index]
self._index += 1
return value
def __contains__(self, idx):
# 元素是否在自定义list中
return idx in self.values
def __reversed__(self):
# 反转
return list(reversed(self.values))
# 初始化自定义list
my_list = MyList([1, 2, 3, 4, 5])
print(my_list[0]) # __getitem__
my_list[2] = 20 # __setitem__
print(2 in my_list) # __contains__
print(len(my_list)) # __len__
print([i for i in my_list]) # __iter__
del my_list[3] # __del__
reversed_list = reversed(my_list) # __reversed__
print([i for i in reversed_list]) # __iter__
这个例子实现了一个 MyList 类,在这个类中,定义了很多容器类的魔法方法。这样一来,这个 MyList 类就可以像操作普通 list 一样,通过切片的方式添加、获取、删除、迭代元素了。具体魔法函数在实例中的解释如下:
__setitem__():当执行 my_list[2] = 20 时,就会调用__setitem__ 方法,这个方法主要用于向容器内添加元素。
__getitem__():当执行 my_list[0] 时,就会调用__getitem__ 方法,这个方法主要用于从容器中读取元素。
__delitem__():当执行 del my_list[3] 时,就会调用__delitem__ 方法,这个方法主要用于从容器中删除元素。
__len__():当执行 len(my_list) 时,就会调用__len__ 方法,这个方法主要用于读取容器内元素的数量。
__iter__这个方法需要重点关注,为什么我们可以执行 [i for i in my_list]?就是因为定义了__iter__。 这个方法的返回值可以有两种:
- 返回 iter(obj):代表使用 obj 对象的迭代协议,一般 obj 是内置的容器对象;
- 返回 self:代表迭代的逻辑由本类来实现,此时需要重写 next 方法,实现自定义的迭代逻辑
在这个例子中,__iter__ 返回的是 self,所以需要定义__next__ 方法,实现自己的迭代细节。__next__ 方法使用一个索引变量,用于记录当前迭代的位置,这个方法每次被调用时,都会返回一个元素,当所有元素都迭代完成后,此时 for 会停止迭代,若迭代时下标超出边界,这个方法会返回 StopIteration 异常。