likes
comments
collection
share

【python】魔术方法大全(六)——模拟篇

作者站长头像
站长
· 阅读数 28

__call__魔术方法

在 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__魔术方法

__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__魔术方法

__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__魔术方法

__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__魔术方法

__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__魔术方法

__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() 函数时,会自动调用该对象的 __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__魔术方法

__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__ 方法返回一个迭代器对象,而 __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__魔术方法

__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__魔术方法

__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__方法负责关闭文件。