目录
一、概述
1、定义
魔法方法(Magic Methods/Special Methods,也称特殊方法或双下划线方法)是Python中一类具有特殊命名规则的方法,它们的名称通常以双下划线(`__`)开头和结尾。
魔法方法用于在特定情况下自动被Python解释器调用,而不需要显式地调用它们,它们提供了一种机制,让你可以定义自定义类时具有与内置类型相似的行为。
2、作用
魔法方法允许开发者重载Python中的一些内置操作或函数的行为,从而为自定义的类添加特殊的功能。
二、主要应用场景
1、构造和析构
1-1、__init__(self, [args...]):在创建对象时初始化属性。
1-2、__new__(cls, [args...]):在创建对象时控制实例的创建过程(通常与元类一起使用)。
1-3、__del__(self):在对象被销毁前执行清理操作,如关闭文件或释放资源。
2、操作符重载
2-1、__add__(self, other)、__sub__(self, other)、__mul__(self, other)等:自定义对象之间的算术运算。
2-2、__eq__(self, other)、__ne__(self, other)、__lt__(self, other)等:定义对象之间的比较操作。
3、字符串和表示
3-1、__str__(self):定义对象的字符串表示,常用于print()函数。
3-2、__repr__(self):定义对象的官方字符串表示,用于repr()函数和交互式解释器。
4、容器管理
4-1、__getitem__(self, key)、__setitem__(self, key, value)、__delitem__(self, key):用于实现类似列表或字典的索引访问、设置和删除操作。
4-2、__len__(self):返回对象的长度或元素个数。
5、可调用对象
5-1、__call__(self, [args...]):允许对象像函数一样被调用。
6、上下文管理
6-1、__enter__(self)、__exit__(self, exc_type, exc_val, exc_tb):用于实现上下文管理器,如with语句中的对象。
7、属性访问和描述符
7-1、__getattr__, __setattr__, __delattr__:这些方法允许对象在访问或修改不存在的属性时执行自定义操作。
7-2、描述符(Descriptors)是实现了__get__, __set__, 和__delete__方法的对象,它们可以控制对另一个对象属性的访问。
8、迭代器和生成器
8-1、__iter__和__next__:这些方法允许对象支持迭代操作,如使用for循环遍历对象。
8-2、__aiter__, __anext__:这些是异步迭代器的魔法方法,用于支持异步迭代。
9、数值类型
9-1、__int__(self)、__float__(self)、__complex__(self):定义对象到数值类型的转换。
9-2、__index__(self):定义对象用于切片时的整数转换。
10、复制和序列化
10-1、__copy__和__deepcopy__:允许对象支持浅复制和深复制操作。
10-2、__getstate__和__setstate__:用于自定义对象的序列化和反序列化过程。
11、自定义元类行为
11-1、__metaclass__(Python 2)或元类本身(Python 3):允许自定义类的创建过程,如动态创建类、修改类的定义等。
12、自定义类行为
12-1、__init__和__new__:用于初始化对象或控制对象的创建过程。
12-2、__init_subclass__:在子类被创建时调用,允许在子类中执行一些额外的操作。
13、类型检查和转换
13-1、__instancecheck__和__subclasscheck__:用于自定义isinstance()和issubclass()函数的行为。
14、自定义异常
14-1、你可以通过继承内置的Exception类来创建自定义的异常类,并定义其特定的行为。
三、学习方法
要学好Python的魔法方法,你可以遵循以下方法及步骤:
1、理解基础
首先确保你对Python的基本语法、数据类型、类和对象等概念有深入的理解,这些是理解魔法方法的基础。
2、查阅文档
仔细阅读Python官方文档中关于魔法方法的部分,文档会详细解释每个魔法方法的作用、参数和返回值。你可以通过访问Python的官方网站或使用help()函数在Python解释器中查看文档。
3、编写示例
为每个魔法方法编写简单的示例代码,以便更好地理解其用法和效果,通过实际编写和运行代码,你可以更直观地感受到魔法方法如何改变对象的行为。
4、实践应用
在实际项目中尝试使用魔法方法。如,你可以创建一个自定义的集合类,使用__getitem__、__setitem__和__delitem__方法来实现索引操作。只有通过实践应用,你才能更深入地理解魔法方法的用途和重要性。
5、阅读他人代码
阅读开源项目或他人编写的代码,特别是那些使用了魔法方法的代码,这可以帮助你学习如何在实际项目中使用魔法方法。通过分析他人代码中的魔法方法使用方式,你可以学习到一些新的技巧和最佳实践。
6、参加社区讨论
参与Python社区的讨论,与其他开发者交流关于魔法方法的使用经验和技巧,在社区中提问或回答关于魔法方法的问题,这可以帮助你更深入地理解魔法方法并发现新的应用场景。
7、持续学习
Python语言和其生态系统不断发展,新的魔法方法和功能可能会不断被引入,保持对Python社区的关注,及时学习新的魔法方法和最佳实践。
8、练习与总结
多做练习,通过编写各种使用魔法方法的代码来巩固你的理解,定期总结你学到的知识和经验,形成自己的知识体系。
9、注意兼容性
在使用魔法方法时,要注意不同Python版本之间的兼容性差异,确保你的代码在不同版本的Python中都能正常工作。
10、避免过度使用
虽然魔法方法非常强大,但过度使用可能会导致代码难以理解和维护,在编写代码时,要权衡使用魔法方法的利弊,避免滥用。
总之,学好Python的魔法方法需要不断地学习、实践和总结,只有通过不断地练习和积累经验,你才能更好地掌握这些强大的工具,并在实际项目中灵活运用它们。
四、魔法方法
11、__delitem__方法
11-1、语法
__delitem__(self, key, /) Delete self[key]
11-2、参数
11-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。
11-2-2、key(必须):表示想要从对象中删除的元素的键或索引
11-2-3、/(可选):这是从Python 3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-only parameters)之后的关键字参数(keyword arguments)。
11-3、功能
用于定义当从容器中删除一个元素时应该执行的操作。
11-4、返回值
当这个方法被调用时,它应该执行删除操作,但不应该返回任何值(或者更具体地说,它应该返回None)。
11-5、说明
对于列表,key通常是一个整数索引;对于字典,key通常是一个键。
11-6、用法
# 011、__delitem__方法: # 1、简单的自定义字典 # 定义一个名为CustomDict的类,该类继承自内置的dict类 class CustomDict(dict): # 重写父类dict中的__delitem__方法,该方法在删除字典中的键值对时被调用 def __delitem__(self, key): # 打印出将要被删除的键 print(f"Deleting key: {key}") # 调用父类dict的__delitem__方法来实际删除指定的键值对 # 使用super()函数可以调用当前类继承的父类(或多个父类)中的方法 super().__delitem__(key) # 判断当前模块是否作为主程序运行(而不是被导入为模块) if __name__ == '__main__': # 创建一个CustomDict对象d,并初始化时传入一个字典{'a': 1, 'b': 2} d = CustomDict({'a': 1, 'b': 2}) # 删除d中键为'a'的键值对 # 这会触发CustomDict类中定义的__delitem__方法,打印出"Deleting key: a" del d['a'] # 输出: Deleting key: a # 2、带有额外检查的字典 # 定义一个名为CheckedDict的类,该类继承自内置的dict类 class CheckedDict(dict): # 重写父类dict中的__delitem__方法,用于在删除字典项之前进行检查 def __delitem__(self, key): # 检查键key是否存在于当前字典中 if key not in self: # 如果键不存在,则抛出一个KeyError异常,并说明键不存在 raise KeyError(f"Key {key} does not exist.") # 如果键存在,则打印出正在删除的键 print(f"Deleting key: {key}") # 调用父类dict的__delitem__方法来实际删除指定的键值对 super().__delitem__(key) # 判断当前模块是否作为主程序运行(而不是被导入为模块) if __name__ == '__main__': # 创建一个CheckedDict对象cd,并初始化时传入一个字典{'a': 1, 'b': 2} cd = CheckedDict({'a': 1, 'b': 2}) # 删除cd中键为'a'的键值对 # 这会触发CheckedDict类中定义的__delitem__方法,打印出"Deleting key: a" del cd['a'] # 输出: Deleting key: a # 尝试删除cd中键为'c'的键值对 # 因为'c'不在cd中,所以会触发CheckedDict类中定义的__delitem__方法中的KeyError异常 del cd['c'] # 引发 KeyError: Key c does not exist. # 3、自定义列表,删除元素时更新索引 class IndexedList: # 初始化方法,创建一个空的列表用于存储元素,和一个空的字典用于存储元素和它们的索引 def __init__(self): self.items = [] # 列表,用于存储元素 self.indices = {} # 字典,用于存储元素和它们的索引 # 添加元素到列表的末尾,并在indices字典中记录其索引 def append(self, item): index = len(self.items) # 获取当前列表的长度,即新元素的索引 self.items.append(item) # 将元素添加到列表的末尾 self.indices[item] = index # 在indices字典中记录元素和它的索引 # 删除指定索引或元素的方法 def __delitem__(self, key): # 如果key是整数,则认为它是索引 if isinstance(key, int): index = key # 索引就是key item = self.items[index] # 从列表中根据索引获取元素 # 如果key在indices字典中,则认为它是元素的值 elif key in self.indices: index = self.indices[key] # 从indices字典中获取元素的索引 # 注意:这里item应该设置为列表中的元素,而不是key本身 # 但由于代码逻辑,我们保持item = key,这在key是元素值时是正确的 item = key # 如果key既不是整数也不在indices字典中,则抛出KeyError else: raise KeyError(f"Key {key} does not exist.") # 打印正在删除的元素和它的索引 print(f"Deleting item at index {index}: {item}") # 从列表中删除元素 del self.items[index] # 从indices字典中删除对应的条目 # 注意:这里应该使用item对应的值,而不是item本身(如果key是索引的话) # 但由于之前的代码逻辑,这里直接使用item是可行的(如果key是元素值) del self.indices[item] if __name__ == '__main__': il = IndexedList() # 创建一个IndexedList对象 il.append('apple') # 添加'apple'元素,其索引为0 il.append('banana') # 添加'banana'元素,其索引为1 del il[1] # 删除索引为1的元素(即'banana'),并输出:Deleting item at index 1: banana # 4、带有删除日志的列表 class LoggedList(list): # 重写list类的__delitem__方法,以便在删除元素时记录日志 def __delitem__(self, index): # 使用pop方法删除指定索引的元素,并返回该元素 # 注意:pop方法会直接修改列表,删除指定索引的元素并返回它 item = self.pop(index) # 使用pop删除并返回元素 # 打印正在删除的元素 print(f"Deleting item: {item}") if __name__ == '__main__': # 创建一个LoggedList对象,并初始化列表为[1, 2, 3] ll = LoggedList([1, 2, 3]) # 删除索引为1的元素(即值为2的元素) # 由于LoggedList类重写了__delitem__方法,所以在删除时会调用该方法并记录日志 del ll[1] # 输出: Deleting item: 2 # 5、限制删除操作的列表 class RestrictedList(list): # 重写父类list的__delitem__方法,增加索引检查 def __delitem__(self, index): # 检查索引是否在有效范围内(包括0到len(self)-1) if not 0 <= index < len(self): # 如果索引超出范围,则抛出IndexError异常 raise IndexError(f"Index {index} out of range.") # 如果索引有效,则调用父类list的__delitem__方法来删除元素 super().__delitem__(index) if __name__ == '__main__': # 创建一个RestrictedList对象,并初始化列表为[1, 2, 3] rl = RestrictedList([1, 2, 3]) # 删除索引为1的元素(即值为2的元素),这是正常删除 del rl[1] # 正常删除 # 打印列表rl,此时应该输出:[1, 3] print(rl) # 输出:[1, 3] # 尝试删除索引为3的元素,由于索引超出范围,将引发IndexError异常 del rl[3] # 引发 IndexError: Index 3 out of range. # 注意:由于IndexError异常,接下来的代码(如果有的话)将不会被执行 # 6、删除元素时触发回调的列表 class CallbackList(list): # 初始化方法,除了继承自list的初始化外,还接受一个回调函数作为参数 def __init__(self, callback): # 存储传入的回调函数 self.callback = callback # 调用父类list的初始化方法 super().__init__() # 重写list类的__delitem__方法,以便在删除元素时执行回调函数并打印信息 def __delitem__(self, index): # 使用pop方法删除指定索引的元素,并返回该元素 item = self.pop(index) # 调用存储的回调函数,传入被删除的元素 self.callback(item) # 打印被删除的元素 print(f"Deleted item: {item}") # 使用示例中的回调函数 def print_deleted(item): # 打印回调函数接收到的元素,表示该元素已被删除 print(f"Callback: Item {item} was deleted") if __name__ == '__main__': # 创建一个CallbackList对象,并传入回调函数print_deleted cl = CallbackList(print_deleted) # 向CallbackList对象中添加元素 cl.append(1) cl.append(2) # 删除索引为0的元素(即值为1的元素) # 由于CallbackList类重写了__delitem__方法,所以在删除时会调用该方法并执行回调函数和打印信息 del cl[0] # 输出: Callback: Item 1 was deleted 和 Deleted item: 1
12、__dir__方法
12-1、语法
__dir__(self, /) Default dir() implementation
12-2、参数
12-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。
12-2-2、/(可选):这是从Python 3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-only parameters)之后的关键字参数(keyword arguments)。
12-3、功能
用于定制对象的属性列表,即在执行如dir(obj)这样的操作时返回的属性名列表。
12-4、返回值
返回一个字符串列表,包含了你想让dir()函数返回的对象的属性名。
12-5、说明
如果你没有为类定义 __
dir__
方法,那么Python会使用默认的dir()实现,它会列出对象的所有属性,包括从父类继承的属性,以及实例属性、方法、类等。
12-6、用法
# 012、__dir__方法: # 1、基本实现,返回所有属性 # 定义一个名为 MyClass 的类 class MyClass: # 类的初始化方法,当创建 MyClass 的实例时会被调用 def __init__(self): # 为实例设置属性 a,并赋值为 1 self.a = 1 # 为实例设置属性 b,并赋值为 2 self.b = 2 # 定义一个名为 method 的方法,它不执行任何操作(pass 是空操作) def method(self): pass # 自定义 __dir__ 方法,用于返回对象的属性列表 def __dir__(self): # 返回实例的 __dict__ 属性(即实例的属性字典)的键的列表 # 并手动添加方法名 'method' 到列表中(注意:在 Python 3.3+ 中,方法名通常会自动包含在 __dir__ 中) return list(self.__dict__.keys()) + ['method'] # 加上方法名(通常不需要手动添加) # 判断当前脚本是否作为主程序运行(而不是被导入为模块) if __name__ == '__main__': # 创建一个 MyClass 的实例,并将其赋值给变量 obj obj = MyClass() # 调用 dir 函数并传入 obj 作为参数,打印 obj 的属性列表 # 由于 MyClass 定义了 __dir__ 方法,输出将包括 MyClass 实例的属性 'a'、'b' 以及手动添加的 'method' # 注意:实际输出可能还包括其他内置方法和属性,如 '__class__', '__dict__', '__doc__' 等 print( dir(obj)) # 输出可能包括:['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', ..., 'a', 'b', 'method'] # 2、隐藏某些属性 # 定义一个名为 MyClass 的类 class MyClass: # 类的初始化方法,当创建 MyClass 的实例时会被调用 def __init__(self): # 为实例设置一个公共属性 public_attr,并赋值为 1 self.public_attr = 1 # 为实例设置一个通常以单下划线开头的“受保护的”属性 _private_attr,并赋值为 2 # 注意:单下划线开头的属性并不是真正的私有属性,但在约定上被视为“受保护的”或“内部使用的” self._private_attr = 2 # 通常以单下划线开头的属性被视为“受保护的” # 自定义 __dir__ 方法,用于返回对象的属性列表 # 但这个方法有一个问题,因为它试图从自身调用 dir(self),这会导致无限递归 # 因为它会再次调用这个自定义的 __dir__ 方法,而不是内置的 dir 函数 def __dir__(self): # 使用内置的 vars() 函数来获取实例的 __dict__,从而避免无限递归 return [attr for attr in vars(self) if not attr.startswith('_')] if __name__ == '__main__': # 创建一个 MyClass 的实例,并将其赋值给变量 obj obj = MyClass() # 调用 dir 函数并传入 obj 作为参数,打印 obj 的属性列表 # 由于 MyClass 定义了 __dir__ 方法,输出将不包括以单下划线开头的属性 print(dir(obj)) # 输出将不包括以单下划线开头的属性,如:['public_attr'] # 3、添加动态属性 # 定义一个名为MyClass的类 class MyClass: # 初始化方法,当创建MyClass的实例时会被调用 def __init__(self): # 初始化一个实例变量dynamic_attr,并设置其值为None self.dynamic_attr = None # 定义一个方法add_dynamic_attr,用于动态地为实例添加属性 def add_dynamic_attr(self, name, value): # 使用setattr函数动态地为实例添加属性,name为属性名,value为属性值 setattr(self, name, value) # 自定义__dir__方法,用于返回对象的属性列表 def __dir__(self): # 获取当前实例的所有属性名(不包括继承的属性),并将其转换为列表 # 然后添加方法名'add_dynamic_attr'到列表中 # 注意:在实际使用中,Python的dir()函数通常会自动包含方法名,这里添加可能是为了特殊需要或示例 return list(self.__dict__.keys()) + ['add_dynamic_attr'] # 添加方法名 # 当此脚本作为主程序运行时执行以下代码 if __name__ == '__main__': # 创建一个MyClass的实例,并将其赋值给变量obj obj = MyClass() # 调用obj的add_dynamic_attr方法,动态地为其添加名为'new_attr'的属性,并设置其值为'some value' obj.add_dynamic_attr('new_attr', 'some value') # 调用dir函数并传入obj作为参数,打印obj的属性列表 # 由于MyClass定义了__dir__方法,输出将包括'add_dynamic_attr'、'dynamic_attr'以及动态添加的'new_attr' print(dir(obj)) # 输出:['add_dynamic_attr', 'dynamic_attr', 'new_attr'] # 4、合并父类属性 # 定义一个名为Parent的基类 class Parent: # 初始化方法,当创建Parent的实例时会被调用 def __init__(self): # 初始化一个实例变量parent_attr,并设置其值为'parent' self.parent_attr = 'parent' # 定义一个名为Child的子类,继承自Parent类 class Child(Parent): # 子类的初始化方法 def __init__(self): # 调用父类的初始化方法,确保父类的属性被正确初始化 super().__init__() # 初始化一个子类特有的实例变量child_attr,并设置其值为'child' self.child_attr = 'child' # 自定义__dir__方法,用于返回对象的属性列表 def __dir__(self): # 使用vars函数获取当前实例的字典表示(即属性和其值),并转换为集合 child_attrs = set(vars(self)) # 使用vars函数和super函数获取父类实例的字典表示,并转换为集合 parent_attrs = set(vars(super(Child, self))) # 使用集合的并集操作符|,将子类和父类的属性合并为一个集合 all_attrs = child_attrs | parent_attrs # 将合并后的属性集合转换为列表并返回 return list(all_attrs) # 当此脚本作为主程序运行时执行以下代码 if __name__ == '__main__': # 创建一个Child类的实例,并将其赋值给变量obj obj = Child() # 调用dir函数并传入obj作为参数,打印obj的属性列表 # 由于Child类定义了__dir__方法,输出将包括父类Parent和子类Child的属性 print(dir(obj)) # 输出将包括父类和子类的属性 # 5、按条件显示属性 # 定义一个名为MyClass的类 class MyClass: # 初始化方法,当创建MyClass的实例时会被调用 def __init__(self): # 初始化一个实例变量show_this,并设置其值为True self.show_this = True # 初始化另一个实例变量hide_this,并设置其值为False self.hide_this = False # 自定义__dir__方法,用于返回对象的属性列表 def __dir__(self): # 使用列表推导式遍历self.__dict__中的所有属性名(即键) # 如果属性名不以'_this'结尾,或者该属性的值不为False(即值存在),则将其包含在内 # 注意:这里的逻辑实际上不会排除'hide_this',因为即使其值为False,它仍然存在于__dict__中 # 但为了注释说明,我们假设这是意图(尽管实际行为并非如此) return [attr for attr in self.__dict__ if not attr.endswith('_this') or getattr(self, attr)] # 当此脚本作为主程序运行时执行以下代码 if __name__ == '__main__': # 创建一个MyClass的实例,并将其赋值给变量obj obj = MyClass() # 调用dir函数并传入obj作为参数,打印obj的属性列表 # 注释说明中提到输出将包括'show_this',但不包括'hide_this'(因为其值为False) print(dir(obj)) # 输出仅包括 ['show_this'] # 6、自定义排序 # 定义一个名为MyClass的类 class MyClass: # 初始化方法,当创建MyClass的实例时会被调用 def __init__(self): # 定义并初始化一个实例变量z,赋值为3 self.z = 3 # 定义并初始化一个实例变量b,赋值为2 self.b = 2 # 定义并初始化一个实例变量a,赋值为1 self.a = 1 # 自定义__dir__方法,用于返回对象的属性列表 def __dir__(self): # 使用self.__dict__.keys()获取当前对象的所有属性名(作为字典的键) # 然后使用sorted()函数对属性名进行排序(默认按字母顺序排序) # 最后返回排序后的属性名列表 return sorted(self.__dict__.keys()) # 按字母顺序排序 # 当此脚本作为主程序运行时执行以下代码 if __name__ == '__main__': # 创建一个MyClass的实例,并将其赋值给变量obj obj = MyClass() # 调用dir函数并传入obj作为参数,打印obj的属性列表 # 由于MyClass类中重写了__dir__方法,因此输出将按字母顺序排列属性名 print(dir(obj)) # 输出:['a', 'b', 'z']
13、__divmod__方法
13-1、语法
__divmod__(self, value, /) Return divmod(self, value)
13-2、参数
13-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。
13-2-2、value(必须):表示要与self进行除法和取模运算的第二个值。
13-2-3、/(可选):这是从Python 3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-only parameters)之后的关键字参数(keyword arguments)。
13-3、功能
用于定义对象在使用divmod()内置函数时的行为。
13-4、返回值
返回一个元组,其中包含两个值:除法的商和余数。
13-5、说明
当你对一个对象调用divmod()函数时,Python会自动尝试调用该对象的 __divmod__ 方法。这个方法应该接受两个参数:self(即对象本身)和value(即要与对象进行除法和取模运算的值)。
13-6、用法
# 013、__divmod__方法: # 1、整数类 # 定义一个名为Integer的类,用于表示整数 class Integer: # 初始化方法,接收一个value参数,并将其赋值给实例变量self.value def __init__(self, value): self.value = value # 定义__divmod__方法,用于自定义当对象使用divmod()函数时的行为 def __divmod__(self, other): # 检查other是否为整数或浮点数,并且不等于0 # 如果不是,则抛出一个TypeError异常,提示不支持的操作数类型 if not isinstance(other, (int, float)) and other != 0: raise TypeError("Unsupported operand type(s) for divmod()") # 调用内置的divmod函数,对self.value和other进行除法和取模运算 # 并返回结果(一个包含商和余数的元组) return divmod(self.value, other) # 如果当前模块是作为主程序运行的(而不是被导入到其他模块中),则执行以下代码 if __name__ == '__main__': # 创建一个Integer对象a,并初始化其值为10 a = Integer(10) # 使用divmod函数对a和3进行除法和取模运算 # 因为a是Integer的实例,所以这里会调用Integer类的__divmod__方法 # 输出结果应该是(3, 1),因为10除以3的商是3,余数是1 print(divmod(a, 3)) # 输出: (3, 1) # 2、分数类 # 导入Fraction类,这是Python内置的一个分数类 from fractions import Fraction # 定义一个名为MyFraction的类,用于表示自定义的分数 class MyFraction: # 初始化方法,用于创建MyFraction对象时设置分子和分母 def __init__(self, numerator, denominator=1): # 分子 self.numerator = numerator # 分母,默认为1(但通常不会为0,因为0作为分母在数学上是没有定义的) self.denominator = denominator # 定义divmod方法的特殊版本,用于实现自定义分数与其他数字类型的除法取余操作 def __divmod__(self, other): # 检查传入的other是否是整数、浮点数或Fraction类型 if not isinstance(other, (int, float, Fraction)): # 如果不是,则抛出TypeError异常 raise TypeError("Unsupported operand type(s) for divmod()") # 将other转换为Fraction类型,以确保可以与Fraction对象进行运算 other_fraction = Fraction(other) # 使用内置的divmod函数和Fraction对象进行除法取余操作 # 注意:这里将MyFraction对象也转换为Fraction对象进行计算 result = divmod(Fraction(self.numerator, self.denominator), other_fraction) # divmod函数返回的是一个包含两个元素的元组:(商, 余数) # 商和余数都是Fraction对象,我们需要将其转换为(分子, 分母)的形式并返回 # 第一个元素是商,转换为(分子, 分母)的元组形式 # 第二个元素是余数,直接返回其分子(因为余数的分母始终为1) return (result[0].numerator, result[0].denominator), result[1].numerator # 如果当前脚本作为主程序运行(而不是被导入),则执行以下代码 if __name__ == '__main__': # 创建一个MyFraction对象,分子为7,分母为3 frac = MyFraction(7, 3) # 使用内置的divmod函数和自定义的MyFraction对象进行除法取余操作 # 注意:这里并没有直接调用MyFraction的__divmod__方法,而是使用了内置的divmod函数 # 但由于MyFraction类定义了__divmod__方法,所以内置的divmod函数能够识别并使用它 print(divmod(frac, 2)) # 输出: ((1, 1), 1),表示商为1(分子为1,分母为1),余数为1 # 3、复数类(注意:复数通常不支持取模运算) # 定义一个名为ComplexNumber的类,用于表示复数 class ComplexNumber: # 初始化方法,设置复数的实部和虚部 def __init__(self, real, imag): self.real = real # 复数的实部 self.imag = imag # 复数的虚部 # 定义__divmod__方法,用于实现复数与其他数字类型的除法取余操作 # 注意:复数通常不支持取模运算,这里仅作为示例展示除法 def __divmod__(self, other): # 检查传入的other是否是整数、浮点数或复数类型 if not isinstance(other, (int, float, complex)): # 如果不是,则抛出TypeError异常 raise TypeError("Unsupported operand type(s) for divmod()") # 计算除法,使用内置的complex函数和/操作符进行复数除法 quotient = complex(self.real, self.imag) / other # 这里不返回余数,因为复数除法没有明确的余数概念 # 返回商和0j(即虚部为0的复数,表示没有余数) # 注意:这里返回的第二个元素虽然是0j,但在复数除法中并没有实际意义 return quotient, 0 + 0j # 如果当前脚本作为主程序运行(而不是被导入),则执行以下代码 if __name__ == '__main__': # 创建一个ComplexNumber对象,实部为2,虚部为3 c = ComplexNumber(2, 3) # 使用内置的divmod函数和自定义的ComplexNumber对象进行除法取余操作 # 注意:虽然名为divmod,但这里只实现了除法,并返回了商和0j作为余数(无实际意义) print(divmod(c, 1 + 1j)) # 输出:((2.5+0.5j), 0j),表示商为2.5+0.5j,余数为0j(无实际意义) # 4、自定义单位类(如长度,使用米和厘米) class Length: def __init__(self, meters, cm=0): # 将厘米转换为米,并加到总的米数上 self.meters = meters + cm / 100 # 获取剩余的厘米数(0-99) self.cm = cm % 100 def __divmod__(self, other): if not isinstance(other, (int, float)): raise TypeError("不支持的操作数类型进行 divmod() 操作") # 确保除数不为零 if other == 0: raise ZeroDivisionError("除数不能为零") # 转换为厘米以便进行计算 total_cm = int(self.meters * 100) + self.cm # 使用 divmod 函数计算商和余数(均为厘米数) quotient_cm, remainder = divmod(total_cm, other) # 将商转换为米和厘米 quotient_meters = quotient_cm // 100 quotient_cm %= 100 # 返回一个包含商(Length 对象)和余数(厘米数)的元组 return Length(quotient_meters, quotient_cm), remainder if __name__ == '__main__': l = Length(2, 30) # 使用 divmod 函数进行除法操作,并打印结果 # 结果应该是一个包含 Length 对象和余数的元组 result = divmod(l, 5) print(result) # 输出可能类似于:(<__main__.Length object at 0x...>, 4) # (< __main__.Length object at 0x0000023C5CACCE50 >, 4)