目录
![]()
![]()
![]()
一、概述
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的魔法方法需要不断地学习、实践和总结,只有通过不断地练习和积累经验,你才能更好地掌握这些强大的工具,并在实际项目中灵活运用它们。
![]()
四、魔法方法
65、__round__方法
65-1、语法
- __round__(self, ndigits=None)
- Rounding an Integral returns itself
- Rounding with an ndigits argument also returns an integer
65-2、参数
65-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。
65-2-2、 ndigits(可选): 一个整数,表示要保留的小数位数。如果省略或为None,则进行标准舍入到最接近的整数;否则,ndigits必须是一个整数,表示小数点后要保留的位数。
65-3、功能
允许你自定义一个对象在调用内置的round()函数时的行为。
65-4、返回值
返回一个与对象相同类型或可以安全地转换为该类型的值,该值表示四舍五入后的结果。
65-5、说明
如果ndigits省略或为None,则四舍五入到最接近的整数;如果ndigits为负数,则四舍五入到指定的小数点左侧的位数。
65-6、用法
- # 065、__round__方法:
- # 1、简单数字舍入
- class MyNumber:
- def __init__(self, value):
- self.value = value
- def __round__(self, ndigits=None):
- return round(self.value, ndigits)
- if __name__ == '__main__':
- num = MyNumber(3.14159)
- print(round(num, 2)) # 输出: 3.14
-
- # 2、向上舍入
- class CeilingNumber:
- def __init__(self, value):
- self.value = value
- def __round__(self, ndigits=None):
- import math
- return math.ceil(self.value) if ndigits is None else math.ceil(self.value * 10 ** ndigits) / 10 ** ndigits
- if __name__ == '__main__':
- ceil_num = CeilingNumber(3.11)
- print(round(ceil_num, 1)) # 输出: 3.2
-
- # 3、银行舍入(向最接近的偶数舍入)
- class FinancialAsset:
- def __init__(self, symbol, price):
- self.symbol = symbol
- self.price = price
- def __round__(self, ndigits=2):
- """
- 自定义舍入方法,对于金融资产价格,我们可能希望使用银行家舍入法
- 并保留指定的小数位数。
- """
- if ndigits < 0:
- raise ValueError("Number of digits to round to must be non-negative.")
- # 使用decimal模块进行更精确的舍入
- from decimal import Decimal, ROUND_HALF_EVEN
- # 将float转换为Decimal进行更精确的运算
- price_decimal = Decimal(str(self.price))
- # 使用银行家舍入法进行舍入
- rounded_price = price_decimal.quantize(Decimal(f'1.{"0" * ndigits}'), rounding=ROUND_HALF_EVEN)
- # 转换回float(如果需要的话,但通常建议使用Decimal进行金融计算)
- return float(rounded_price)
- def __repr__(self):
- return f"FinancialAsset(symbol={self.symbol}, price={self.price})"
- if __name__ == '__main__':
- asset = FinancialAsset("AAPL", 172.34567)
- print(round(asset, 2)) # 输出: 172.35(假设这是使用银行家舍入法得到的结果)
- # 注意:在实际金融应用中,通常会避免使用float来表示货币值,
- # 因为float的精度问题可能会导致计算错误。相反,应该使用Decimal类型
-
- # 4、物理量舍入(如温度)
- class Temperature:
- def __init__(self, value, unit):
- self.value = value
- self.unit = unit
- def __round__(self, ndigits=None):
- """
- 根据温度的单位来定制舍入行为。
- 对于摄氏度,我们可能希望保留一位小数;
- 对于华氏度,我们可能希望保留整数。
- """
- if ndigits is None:
- ndigits = 1 if self.unit == 'C' else 0
- if ndigits < 0:
- raise ValueError("Number of digits to round to must be non-negative.")
- return round(self.value, ndigits)
- def __repr__(self):
- return f"Temperature(value={self.value}, unit={self.unit})"
- if __name__ == '__main__':
- celsius = Temperature(25.456, 'C')
- print(round(celsius)) # 输出: 25.5(摄氏度,保留一位小数)
- fahrenheit = Temperature(77.8, 'F')
- print(round(fahrenheit, 0)) # 输出: 78.0(华氏度,保留一位小数)
- # 也可以指定舍入的小数位数
- print(round(celsius, 2)) # 输出: 25.46(摄氏度,保留两位小数)
66、__set__方法
66-1、语法
- __set__(self, instance, value, /)
- Set an attribute of instance to value
66-2、参数
66-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。
66-2-2、instance(可选):表示拥有描述符的实例对象。
66-2-3、value(必须):表示你想要设置给描述符的新值。
66-2-4、/(可选):这是从Python 3.8开始引入的参数注解语法,它表示这个方法不接受任何位置参数(positional-only parameters)之后的关键字参数(keyword arguments)。
66-3、功能
用于控制当描述符的值被设置时的行为。
66-4、返回值
在大多数情况下,__set__方法没有返回值,因为它是通过赋值操作调用的,而赋值操作本身不期望有任何返回值(除了可能的异常)。如果你尝试从__set__方法返回一个值,这个值通常会被忽略。
66-5、说明
无
66-6、用法
- # 066、__set__方法:
- # 1、限制属性值的范围
- class RangeDescriptor:
- def __init__(self, name, min_val, max_val):
- self.name = name
- self.min_val = min_val
- self.max_val = max_val
- def __set__(self, instance, value):
- if self.min_val <= value <= self.max_val:
- instance.__dict__[self.name] = value
- else:
- raise ValueError(f"Value must be between {self.min_val} and {self.max_val}")
- def __get__(self, instance, owner):
- if instance is None:
- return self # 如果实例为None(例如访问类属性而非实例属性时),返回描述符本身
- return instance.__dict__.get(self.name, None) # 如果没有设置值,则返回None或默认值
- def range_property(min_val, max_val):
- def decorator(cls):
- name = None
- def getter(instance, cls):
- nonlocal name
- name = '_' + getter.__name__ # 假设我们总是使用以下划线开头的私有属性名
- return RangeDescriptor(name, min_val, max_val)
- setattr(cls, getter.__name__, property(getter))
- return cls
- return decorator
- class MyClass:
- @range_property(1, 10)
- def value(self):
- pass # 这里实际上不需要任何实现,因为描述符会处理所有的getter和setter逻辑
- if __name__ == '__main__':
- obj = MyClass()
- obj.value = 5
- print(obj.value) # 输出:5
- try:
- obj.value = 15
- except ValueError as e:
- print(e) # 输出:ValueError: Value must be between 1 and 10
-
- # 2、字符串长度限制描述符
- class StringLengthDescriptor:
- def __init__(self, max_length):
- self.max_length = max_length
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, '')
- def __set__(self, instance, value):
- if isinstance(value, str) and len(value) <= self.max_length:
- instance.__dict__[self.name] = value
- else:
- raise ValueError(f"{self.name} must be a string with length less than or equal to {self.max_length}")
- class MyAnotherClass:
- short_string = StringLengthDescriptor(10)
- if __name__ == '__main__':
- obj = MyAnotherClass()
- obj.short_string = "short"
- print(obj.short_string) # 输出:short
- try:
- obj.short_string = "too_long_string"
- except ValueError as e:
- print(e) # 输出:short_string must be a string with length less than or equal to 10
-
- # 3、类型检查描述符
- class TypeCheckedDescriptor:
- def __init__(self, expected_type):
- self.expected_type = expected_type
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, None)
- def __set__(self, instance, value):
- if isinstance(value, self.expected_type):
- instance.__dict__[self.name] = value
- else:
- raise TypeError(f"{self.name} must be of type {self.expected_type.__name__}")
- class MyTypedClass:
- typed_value = TypeCheckedDescriptor(int)
- if __name__ == '__main__':
- obj = MyTypedClass()
- obj.typed_value = 10
- print(obj.typed_value) # 输出:10
- try:
- obj.typed_value = "not_an_int"
- except TypeError as e:
- print(e) # 输出:typed_value must be of type int
-
- # 4、只读描述符
- class ReadOnlyDescriptor:
- def __init__(self, initial_value):
- self.value = initial_value
- def __get__(self, instance, owner):
- return self.value
- def __set__(self, instance, value):
- raise AttributeError("This attribute is read-only")
- class MyClassWithReadOnly:
- read_only_attr = ReadOnlyDescriptor(10)
- if __name__ == '__main__':
- obj = MyClassWithReadOnly()
- print(obj.read_only_attr) # 输出:10
- try:
- obj.read_only_attr = 20
- except AttributeError as e:
- print(e) # 输出:This attribute is read-only
-
- # 5、带有默认值的描述符
- class DefaultValueDescriptor:
- def __init__(self, default_value):
- self.default_value = default_value
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, self.default_value)
- def __set__(self, instance, value):
- instance.__dict__[self.name] = value
- class MyClassWithDefault:
- default_value_attr = DefaultValueDescriptor('default')
- if __name__ == '__main__':
- obj = MyClassWithDefault()
- print(obj.default_value_attr) # 输出:'default'
- obj.default_value_attr = 'new_value'
- print(obj.default_value_attr) # 输出:'new_value'
-
- # 6、属性验证描述符
- class ValidatedDescriptor:
- def __init__(self, validator):
- self.validator = validator
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, None)
- def __set__(self, instance, value):
- if self.validator(value):
- instance.__dict__[self.name] = value
- else:
- raise ValueError(f"{self.name} value is not valid")
- def is_even(n):
- return n % 2 == 0
- class MyClassWithValidation:
- even_number = ValidatedDescriptor(is_even)
- if __name__ == '__main__':
- obj = MyClassWithValidation()
- obj.even_number = 2
- print(obj.even_number) # 输出:2
- try:
- obj.even_number = 3
- except ValueError as e:
- print(e) # 输出:even_number value is not valid
67、__set_name__方法
67-1、语法
- __set_name__(self, owner, name)
- Method to set name of a property
67-2、参数
67-2-1、self(必须):一个对实例对象本身的引用,在类的所有方法中都会自动传递。
67-2-2、owner(必须):表示将描述符分配给的类对象。
67-2-3、name(必须):表示描述符在owner类中的属性名。
67-3、功能
为描述符实例提供有关其被分配到的类和属性名的信息。
67-4、返回值
没有特定的返回值要求,它通常被设计为一个“副作用”方法,即它的主要目的是修改描述符实例的内部状态或执行一些操作,而不是返回任何值。
67-5、说明
如果你尝试从__set_name__方法返回一个值,那么这个返回值通常会被忽略,因为Python解释器不会对这个返回值做任何特殊处理。
67-6、用法
- # 067、__set_name__方法:
- # 1、字符串长度限制描述符
- class StringLengthDescriptor:
- def __init__(self, max_length):
- self.max_length = max_length
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, '')
- def __set__(self, instance, value):
- if isinstance(value, str) and len(value) <= self.max_length:
- instance.__dict__[self.name] = value
- else:
- raise ValueError(f"{self.name} must be a string with length less than or equal to {self.max_length}")
- class MyAnotherClass:
- short_string = StringLengthDescriptor(10)
- if __name__ == '__main__':
- obj = MyAnotherClass()
- obj.short_string = "short"
- print(obj.short_string) # 输出:short
- try:
- obj.short_string = "too_long_string"
- except ValueError as e:
- print(e) # 输出:short_string must be a string with length less than or equal to 10
-
- # 2、类型检查描述符
- class TypeCheckedDescriptor:
- def __init__(self, expected_type):
- self.expected_type = expected_type
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, None)
- def __set__(self, instance, value):
- if isinstance(value, self.expected_type):
- instance.__dict__[self.name] = value
- else:
- raise TypeError(f"{self.name} must be of type {self.expected_type.__name__}")
- class MyTypedClass:
- typed_value = TypeCheckedDescriptor(int)
- if __name__ == '__main__':
- obj = MyTypedClass()
- obj.typed_value = 10
- print(obj.typed_value) # 输出:10
- try:
- obj.typed_value = "not_an_int"
- except TypeError as e:
- print(e) # 输出:typed_value must be of type int
-
- # 3、带有默认值的描述符
- class DefaultValueDescriptor:
- def __init__(self, default_value):
- self.default_value = default_value
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, self.default_value)
- def __set__(self, instance, value):
- instance.__dict__[self.name] = value
- class MyClassWithDefault:
- default_value_attr = DefaultValueDescriptor('default')
- if __name__ == '__main__':
- obj = MyClassWithDefault()
- print(obj.default_value_attr) # 输出:'default'
- obj.default_value_attr = 'new_value'
- print(obj.default_value_attr) # 输出:'new_value'
-
- # 4、属性验证描述符
- class ValidatedDescriptor:
- def __init__(self, validator):
- self.validator = validator
- self.name = None
- def __set_name__(self, owner, name):
- self.name = name
- def __get__(self, instance, owner):
- if instance is None:
- return self
- return instance.__dict__.get(self.name, None)
- def __set__(self, instance, value):
- if self.validator(value):
- instance.__dict__[self.name] = value
- else:
- raise ValueError(f"{self.name} value is not valid")
- def is_even(n):
- return n % 2 == 0
- class MyClassWithValidation:
- even_number = ValidatedDescriptor(is_even)
- if __name__ == '__main__':
- obj = MyClassWithValidation()
- obj.even_number = 2
- print(obj.even_number) # 输出:2
- try:
- obj.even_number = 3
- except ValueError as e:
- print(e) # 输出:even_number value is not valid
评论记录:
回复评论: