Python魔法之旅-魔法方法(03)

目录

 一、概述

1、定义

2、作用

二、主要应用场景

1、构造和析构

2、操作符重载

3、字符串和表示

4、容器管理

5、可调用对象

6、上下文管理

7、属性访问和描述符

8、迭代器和生成器

9、数值类型

10、复制和序列化

11、自定义元类行为

12、自定义类行为

13、类型检查和转换

14、自定义异常

三、学习方法

1、理解基础

2、查阅文档

3、编写示例

4、实践应用

5、阅读他人代码

6、参加社区讨论

7、持续学习

8、练习与总结

9、注意兼容性

10、避免过度使用

四、魔法方法

11、__delitem__方法

11-1、语法

11-2、参数

11-3、功能

11-4、返回值

11-5、说明

11-6、用法

12、__dir__方法

12-1、语法

12-2、参数

12-3、功能

12-4、返回值

12-5、说明

12-6、用法

13、__divmod__方法

13-1、语法

13-2、参数

13-3、功能

13-4、返回值

13-5、说明

13-6、用法

五、推荐阅读

1、Python筑基之旅

2、Python函数之旅

3、Python算法之旅

4、博客个人主页

 一、概述

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)

五、推荐阅读

1、Python筑基之旅

2、Python函数之旅

3、Python算法之旅

4、博客个人主页

  • 70
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 56
    评论
评论 56
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神奇夜光杯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值