这期我们接着聊聊拥有模拟
功能的魔术方法。
在 Python 中,每个类都是可调用的对象(callable),这意味着你可以像调用函数一样调用一个类来创建新的实例。__call__
是一个特殊的方法,用于使 Python 类的实例能够像函数一样被调用。
当你调用一个实例时,Python 会查找该实例所属类的 __call__
方法。如果该方法存在,则 Python 会调用该方法,将实例和任何参数传递给该方法,然后返回方法的结果。
下面是一个示例,演示了如何使用 __call__
方法:
class Adder:
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x + y
add = Adder(10)
print(add(5)) # Output: 15
在这个示例中,我们定义了一个 Adder
类,它有一个构造函数和一个 __call__
方法。__call__
方法接受一个参数 y
,并返回 self.x + y
的结果。我们实例化一个 Adder
对象,将 x
设置为 10,然后使用括号运算符调用该实例,并将参数 5
传递给它。该调用返回 15
,这是 10 + 5
的结果。
注意,当你调用一个实例时,Python 实际上是在查找类的 __call__
方法,而不是实例的 __call__
方法。如果类没有定义 __call__
方法,Python 会抛出 TypeError
异常。
__len__
是Python中的一个魔术方法,用于定义类的实例对象的长度。该方法用于返回一个对象的长度,通常是容器类型的对象,如字符串、列表、元组和字典等。
以下是一个使用__len__
魔术方法的例子:
class MyList:
def __init__(self, lst):
self.lst = lst
def __len__(self):
return len(self.lst)
lst = MyList([1, 2, 3, 4, 5])
print(len(lst)) # 输出: 5
在上面的例子中,MyList
类中定义了__len__
方法,该方法返回了MyList
实例对象的长度。当我们调用len()
函数时,它将会自动调用该对象的__len__
方法来获取其长度。
注意,只有实现了__len__
方法的对象才能够使用len()
函数获取其长度。如果对象没有实现该方法,则会引发TypeError
异常。
__length_hint__
是 Python 3.4 新增的一个魔术方法,用于返回一个对象的估计长度。该方法通常被迭代器实现,可以让一些 Python 函数(如 len()
)更加高效地工作。
当我们对一个对象调用 len()
函数时,如果该对象没有实现 __len__
方法,那么 Python 解释器就会尝试使用迭代器来估计对象的长度。而在这个过程中,就会调用对象的 __length_hint__
方法来获取对象的估计长度。
需要注意的是,__length_hint__
方法并不要求返回准确的对象长度,只需要返回一个大致的估计即可。如果对象的长度无法估计,也可以返回 NotImplemented
表示无法处理。
以下是一个简单的示例,演示了如何使用 __length_hint__
方法实现自定义迭代器对象:
import operator
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
def __length_hint__(self):
return len(self.data) - self.index
def __len__(self):
return len(self.data)
my_list = [1, 2, 3, 4, 5]
my_iterator = MyIterator(my_list)
print(len(my_list)) # 输出 5
print(len(my_iterator)) # 输出 5
print(operator.length_hint(my_iterator)) # 输出 5
print(list(my_iterator)) # 输出 [1, 2, 3, 4, 5]
这段代码在实现__length_hint__
的同时,也实现了标准的__len__
魔术方法。__len__
的作用是返回迭代器所包含的元素数量,可以直接使用内置的len
函数来获取。
__length_hint__
是一个可选的魔术方法,主要用于优化迭代器在长度上的性能。这个方法会返回剩余未迭代的元素数量的估计值,如果无法准确估计,可以返回一个较大的数值。在Python 3.4之后,标准库中新增了一个operator.length_hint
函数,用于获取一个迭代器的长度估计值。
__getitem__
是Python中的一个魔术方法,用于实现索引操作。当我们对一个对象使用索引操作时,Python解释器会自动调用该对象的__getitem__
方法,并将对应的索引作为参数传入。该方法需要返回与索引对应的值,或者抛出IndexError
异常(当索引无效时)。
以下是一个简单的示例,展示了如何在自定义类中实现__getitem__
方法:
class MyList:
def __init__(self, values):
self.values = values
def __getitem__(self, index):
return self.values[index]
在上述示例中,我们自定义了一个MyList
类,并实现了__getitem__
方法。该方法接收一个索引作为参数,然后返回与该索引对应的元素。我们可以像使用普通列表一样使用MyList
实例,例如:
mylist = MyList([1, 2, 3, 4, 5])
print(mylist[0]) # 输出 1
print(mylist[3]) # 输出 4
通过实现__getitem__
方法,我们使得MyList
实例支持了索引操作。
__setitem__
是 Python 中的一个魔术方法,用于设置对象的某个元素的值。该方法在使用 obj[key] = value
语法时被调用。
下面是一个简单的例子,展示了如何在类中实现 __setitem__
方法来设置对象的元素值:
class MyList:
def __init__(self, lst):
self._data = lst
def __setitem__(self, index, value):
self._data[index] = value
def __getitem__(self, index):
return self._data[index]
my_list = MyList([1, 2, 3])
my_list[1] = 4
print(my_list[1]) # 输出 4
在上面的例子中,MyList
类定义了 __setitem__
和 __getitem__
方法,分别用于设置和获取对象的元素值。当我们使用 my_list[1] = 4
语法来设置列表中第二个元素的值时,__setitem__
方法被调用,将 value
赋值给 _data
中的第 index
个元素。
需要注意的是,__setitem__
方法需要在类中实现 __getitem__
方法,因为在对对象元素进行赋值操作时,需要先获取该元素的值,然后再对其进行赋值操作。如果没有实现 __getitem__
方法,则会抛出 TypeError
异常。
__delitem__
是Python中的一个特殊方法,用于删除序列对象中的元素。该方法可以在用户定义的类中进行实现,以允许使用者通过类似于索引的方式删除序列中的元素。
下面是一个使用__delitem__
方法删除列表元素的例子:
class MyList:
def __init__(self, lst):
self._lst = lst
def __delitem__(self, key):
del self._lst[key]
def __repr__(self):
return str(self._lst)
my_list = MyList([1, 2, 3, 4, 5])
print(my_list) # [1, 2, 3, 4, 5]
del my_list[2]
print(my_list) # [1, 2, 4, 5]
在上面的例子中,我们实现了一个名为MyList
的类,它包含一个列表对象。__delitem__
方法被实现为在列表对象中删除指定的元素。在主程序中,我们创建了一个MyList
实例,并使用del
关键字删除了第三个元素。该方法会将列表对象中的第三个元素(即数字3)删除,并输出修改后的列表。
__reversed__
是一个魔术方法,用于定义一个反向迭代器。当对一个对象使用 reversed()
函数时,会自动调用该对象的 __reversed__
方法返回一个反向迭代器对象,从而实现对该对象的反向迭代。
该方法需要返回一个迭代器对象,迭代器对象可以通过实现 __next__
方法来遍历该对象的元素。如果 __reversed__
方法未定义,会默认调用 __len__
和 __getitem__
方法来实现反向迭代。
下面是一个示例:
class MyList:
def __init__(self, data):
self.data = data
def __getitem__(self, i):
return self.data[i]
def __len__(self):
return len(self.data)
def __reversed__(self):
return reversed(self.data)
my_list = MyList([1, 2, 3, 4, 5])
for i in reversed(my_list):
print(i)
输出结果:
5
4
3
2
1
__contains__
是 Python 中的一个魔术方法,用于在对象中检查某个元素是否存在。它会在使用 in
关键字时被调用,用于检查一个对象是否包含某个值。当一个对象需要支持成员检查时,可以实现这个方法。
下面是一个简单的例子,展示了如何实现 __contains__
方法:
class MyList:
def __init__(self, items):
self.items = items
def __contains__(self, item):
return item in self.items
在这个例子中,我们定义了一个名为 MyList
的类,它接收一个列表作为构造函数的参数。__contains__
方法返回一个布尔值,指示传入的值是否在列表中。
现在,我们可以创建一个 MyList 对象,然后检查其中是否包含某个值:
>>> l = MyList([1, 2, 3, 4, 5])
>>> 3 in l
True
>>> 10 in l
False
__iter__
和 __next__
魔术方法通常一起使用,用于自定义可迭代对象。其中 __iter__
方法返回一个迭代器对象,而 __next__
方法用于定义迭代器的行为。
__iter__
是一个Python的魔术方法(magic method),用于定义对象的迭代行为。当使用for...in
循环或者内置的iter()
函数迭代一个对象时,Python会自动调用该对象的__iter__
方法,获取一个迭代器(iterator)对象。迭代器是一个具有__next__
方法的对象,该方法每次返回一个值,直到所有的值都被迭代完成,然后抛出一个StopIteration
异常,通知调用者迭代已经完成。
下面是一个简单的例子,展示了如何定义一个可迭代的对象:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
self.index = 0
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
在这个例子中,我们定义了一个名为MyIterable
的类,它接收一个列表作为初始化参数。当我们调用iter()
函数或者使用for...in
循环迭代一个MyIterable
对象时,Python会自动调用它的__iter__
方法,该方法返回一个迭代器对象self
。迭代器对象中保存了一个当前的索引self.index
,并且在__next__
方法中每次返回一个值,同时更新索引。
我们可以使用这个MyIterable
类来迭代一个列表:
my_list = [1, 2, 3, 4, 5]
my_iterable = MyIterable(my_list)
for item in my_iterable:
print(item)
这个代码将会打印出:
1
2
3
4
5
我们也可以使用内置的iter()
函数获取迭代器,然后使用next()
方法迭代:
my_iterable = MyIterable(my_list)
my_iterator = iter(my_iterable)
print(next(my_iterator)) # 1
print(next(my_iterator)) # 2
print(next(my_iterator)) # 3
print(next(my_iterator)) # 4
print(next(my_iterator)) # 5
print(next(my_iterator)) # 抛出StopIteration异常
在这个例子中,我们使用了iter()
函数获取一个迭代器,然后使用next()
方法每次迭代一个值,直到所有的值都被迭代完成,然后抛出StopIteration
异常。
__missing__
是一个用于字典的魔术方法,用于在字典中查询不存在的键时返回一个默认值。如果在字典中查询的键不存在,则会自动调用该方法,并返回该方法的返回值作为查询结果。
以下是一个简单的例子:
class MyDict(dict):
def __missing__(self, key):
return 'Not Found'
my_dict = MyDict({'a': 1, 'b': 2})
print(my_dict['a']) # 输出 1
print(my_dict['c']) # 输出 Not Found
在这个例子中,我们定义了一个继承自字典的新类MyDict
,并重写了__missing__
方法。当我们通过my_dict['a']
访问已经存在的键时,会正常返回键的值,而当我们通过my_dict['c']
访问不存在的键时,会自动调用__missing__
方法并返回该方法的返回值,即Not Found
。
__enter__
和__exit__
是Python中上下文管理器协议(Context Manager Protocol)的魔术方法。上下文管理器用于在进入和退出代码块时执行相关操作,例如文件对象的打开和关闭。这些魔术方法在使用Python中的with
语句时被调用。
__enter__
方法被用于进入代码块时初始化上下文管理器。这个方法返回一个对象,通常是上下文管理器对象本身。如果有必要,可以使用as
关键字将返回的对象赋值给一个变量。
__exit__
方法被用于退出代码块时清理上下文管理器。这个方法通常不返回任何值。如果代码块内发生了异常,__exit__
方法可以处理它,并在必要时将异常重新抛出,以便在代码块外处理。
下面是一个简单的例子,演示了如何使用__enter__
和__exit__
方法来创建一个上下文管理器,以便在使用with
语句打开和关闭文件。
class FileHandler:
def __init__(self, filename, mode):
self.file = open(filename, mode)
def __enter__(self):
print("__enter__")
return self.file
def __exit__(self, exc_type, exc_value, traceback):
print("__exit__")
self.file.close()
with FileHandler('example.txt', 'w') as f:
f.write('Hello, World!')
输出结果为:
__enter__
__exit__
在这个例子中,FileHandler
类是一个上下文管理器,它的__enter__
方法返回文件对象本身,以便可以在with
语句内使用。在退出代码块时,__exit__
方法负责关闭文件。