Python 3.12 Magic Method -__neg__(self)__neg__是 Python 中用于定义一元负号运算符-的魔术方法。当对一个对象使用-obj时Python 会自动调用该对象的__neg__方法。它最常见的用途是实现数值类型的取反操作也可用于自定义类型如向量、矩阵等的取相反数。正确实现__neg__可以让自定义类自然地支持-运算与内置类型保持一致。本文将详细解析其定义、底层机制、设计原则并通过多个示例逐行演示如何正确实现。1. 定义与签名def__neg__(self)-object:...参数self当前对象。返回值应返回一个新的对象代表-self的结果。通常返回值类型与self相同或兼容。如果运算未定义一般不会因为一元运算总应返回一个值但理论上可以返回单例NotImplemented虽然很少这样用。调用时机当在表达式-x中使用对象x时Python 会调用x.__neg__()。2. 用途与典型场景数值类型自定义整数、浮点数、复数等支持取反。向量/矩阵返回一个各分量取反的新向量。坐标变换如点类的取反。符号反转在数学表达式中表示相反数。一元负号通常不应修改原对象而是返回一个新对象保持对象的不可变性除非类特意设计为可变但通常不推荐。3. 底层实现机制在 Python/C API 层面一元负号操作由PyNumber_Negative函数处理。每个类型对象PyTypeObject都有一个tp_as_number结构体其中包含nb_negative槽位这是一个函数指针用于处理一元负号。当执行-x时解释器会获取x的类型对象的tp_as_number结构。如果存在nb_negative则调用它传入x返回结果对象或Py_NotImplemented。如果nb_negative不存在或返回Py_NotImplemented则抛出TypeError。对于 Python 层定义的__neg__它会被包装到nb_negative槽位中。因此实现__neg__后-obj就能正确调用。4. 设计原则与最佳实践返回新对象通常不应修改self而应返回一个包含结果的新对象。这符合数学上“取反”不改变原值的语义。与__pos__对称如果实现了__neg__通常也应考虑实现__pos__一元正号使行为完整。类型一致性返回的对象应与self类型相同除非有特殊理由如-int返回int-float 返回float。处理不可变对象对于不可变对象自然要返回新对象对于可变对象虽然可以就地修改后返回self但这样做会破坏-的语义容易引起混淆不建议。简单高效__neg__通常计算量很小应快速执行。5. 示例与逐行解析示例 1自定义整数类classMyInt:def__init__(self,value):self.valuevaluedef__neg__(self):返回一个新的 MyInt其值为原值的相反数returnMyInt(-self.value)def__repr__(self):returnfMyInt({self.value})逐行解析行代码解释1-3__init__初始化整数值。4-6__neg__定义取反操作。5return MyInt(-self.value)创建一个新对象值为self.value的相反数。注意不修改原对象。7-8__repr__便于显示。为什么这样写返回新对象符合不可变语义原对象不受影响。如果忘记返回新对象而修改自身会导致-a后a的值改变违反直觉。验证aMyInt(5)b-aprint(a)# MyInt(5) 原对象不变print(b)# MyInt(-5)运行结果MyInt(5) MyInt(-5)示例 2二维向量类classVector:def__init__(self,x,y):self.xx self.yydef__neg__(self):返回一个新的向量每个分量取反returnVector(-self.x,-self.y)def__repr__(self):returnfVector({self.x},{self.y})逐行解析与示例1类似__neg__中创建新向量坐标取反。由于向量通常设计为不可变这样做是合理的。验证vVector(2,-3)neg_v-vprint(v)# Vector(2, -3)print(neg_v)# Vector(-2, 3)运行结果Vector(2, -3) Vector(-2, 3)示例 3带符号的可变类不推荐classMutableNumber:def__init__(self,value):self.valuevaluedef__neg__(self):# 就地取反并返回自身不推荐self.value-self.valuereturnselfdef__repr__(self):returnfMutableNumber({self.value})验证nMutableNumber(10)print(n)print(id(n))print(-n)# MutableNumber(-10)print(n)# MutableNumber(-10) 原对象被修改了print(id(n))解析这里__neg__修改了自身并返回self导致-n后n的值也改变了。这种用法违背了-的常见语义容易引起混淆因此不推荐。除非类明确设计为“每次取反都改变自身”但这种情况很少见通常应使用其他方法如invert来实现。运行结果MutableNumber(10) 2359803072768 MutableNumber(-10) MutableNumber(-10) 23598030727686. 与__pos__和__abs__的关系__pos__(self)对应一元正号obj。通常直接返回self或其拷贝语义是“保持原值”。如果实现了__neg__通常也应实现__pos__以保持对称。__abs__(self)对应绝对值abs(obj)。与__neg__相关但不相同。示例classMyInt:# ... __neg__, __pos__, __abs__ ...def__pos__(self):returnMyInt(self.value)# 或直接 return selfdef__abs__(self):returnMyInt(abs(self.value))7. 注意事项与陷阱不要修改原对象除非有极其充分的理由否则应返回新对象。返回类型应一致例如取反一个MyInt应该返回MyInt而不是普通int否则会破坏类型一致性。与反向运算符无关__neg__是一元运算符没有反向版本。与__invert__区分__invert__对应按位取反~不要混淆。8. 总结特性说明角色定义一元负号运算符-签名__neg__(self) - object返回值新对象通常与self同类型调用时机-x底层C 层的nb_negative槽位最佳实践返回新对象不修改原对象与__pos__对称掌握__neg__是实现自定义数值类型的基本功。通过正确实现它你的类可以像内置类型一样自然地支持取反操作提升代码的可读性和数学直观性。如果在学习过程中遇到问题欢迎在评论区留言讨论!
Python 3.12 MagicMethods - 65 - __neg__
Python 3.12 Magic Method -__neg__(self)__neg__是 Python 中用于定义一元负号运算符-的魔术方法。当对一个对象使用-obj时Python 会自动调用该对象的__neg__方法。它最常见的用途是实现数值类型的取反操作也可用于自定义类型如向量、矩阵等的取相反数。正确实现__neg__可以让自定义类自然地支持-运算与内置类型保持一致。本文将详细解析其定义、底层机制、设计原则并通过多个示例逐行演示如何正确实现。1. 定义与签名def__neg__(self)-object:...参数self当前对象。返回值应返回一个新的对象代表-self的结果。通常返回值类型与self相同或兼容。如果运算未定义一般不会因为一元运算总应返回一个值但理论上可以返回单例NotImplemented虽然很少这样用。调用时机当在表达式-x中使用对象x时Python 会调用x.__neg__()。2. 用途与典型场景数值类型自定义整数、浮点数、复数等支持取反。向量/矩阵返回一个各分量取反的新向量。坐标变换如点类的取反。符号反转在数学表达式中表示相反数。一元负号通常不应修改原对象而是返回一个新对象保持对象的不可变性除非类特意设计为可变但通常不推荐。3. 底层实现机制在 Python/C API 层面一元负号操作由PyNumber_Negative函数处理。每个类型对象PyTypeObject都有一个tp_as_number结构体其中包含nb_negative槽位这是一个函数指针用于处理一元负号。当执行-x时解释器会获取x的类型对象的tp_as_number结构。如果存在nb_negative则调用它传入x返回结果对象或Py_NotImplemented。如果nb_negative不存在或返回Py_NotImplemented则抛出TypeError。对于 Python 层定义的__neg__它会被包装到nb_negative槽位中。因此实现__neg__后-obj就能正确调用。4. 设计原则与最佳实践返回新对象通常不应修改self而应返回一个包含结果的新对象。这符合数学上“取反”不改变原值的语义。与__pos__对称如果实现了__neg__通常也应考虑实现__pos__一元正号使行为完整。类型一致性返回的对象应与self类型相同除非有特殊理由如-int返回int-float 返回float。处理不可变对象对于不可变对象自然要返回新对象对于可变对象虽然可以就地修改后返回self但这样做会破坏-的语义容易引起混淆不建议。简单高效__neg__通常计算量很小应快速执行。5. 示例与逐行解析示例 1自定义整数类classMyInt:def__init__(self,value):self.valuevaluedef__neg__(self):返回一个新的 MyInt其值为原值的相反数returnMyInt(-self.value)def__repr__(self):returnfMyInt({self.value})逐行解析行代码解释1-3__init__初始化整数值。4-6__neg__定义取反操作。5return MyInt(-self.value)创建一个新对象值为self.value的相反数。注意不修改原对象。7-8__repr__便于显示。为什么这样写返回新对象符合不可变语义原对象不受影响。如果忘记返回新对象而修改自身会导致-a后a的值改变违反直觉。验证aMyInt(5)b-aprint(a)# MyInt(5) 原对象不变print(b)# MyInt(-5)运行结果MyInt(5) MyInt(-5)示例 2二维向量类classVector:def__init__(self,x,y):self.xx self.yydef__neg__(self):返回一个新的向量每个分量取反returnVector(-self.x,-self.y)def__repr__(self):returnfVector({self.x},{self.y})逐行解析与示例1类似__neg__中创建新向量坐标取反。由于向量通常设计为不可变这样做是合理的。验证vVector(2,-3)neg_v-vprint(v)# Vector(2, -3)print(neg_v)# Vector(-2, 3)运行结果Vector(2, -3) Vector(-2, 3)示例 3带符号的可变类不推荐classMutableNumber:def__init__(self,value):self.valuevaluedef__neg__(self):# 就地取反并返回自身不推荐self.value-self.valuereturnselfdef__repr__(self):returnfMutableNumber({self.value})验证nMutableNumber(10)print(n)print(id(n))print(-n)# MutableNumber(-10)print(n)# MutableNumber(-10) 原对象被修改了print(id(n))解析这里__neg__修改了自身并返回self导致-n后n的值也改变了。这种用法违背了-的常见语义容易引起混淆因此不推荐。除非类明确设计为“每次取反都改变自身”但这种情况很少见通常应使用其他方法如invert来实现。运行结果MutableNumber(10) 2359803072768 MutableNumber(-10) MutableNumber(-10) 23598030727686. 与__pos__和__abs__的关系__pos__(self)对应一元正号obj。通常直接返回self或其拷贝语义是“保持原值”。如果实现了__neg__通常也应实现__pos__以保持对称。__abs__(self)对应绝对值abs(obj)。与__neg__相关但不相同。示例classMyInt:# ... __neg__, __pos__, __abs__ ...def__pos__(self):returnMyInt(self.value)# 或直接 return selfdef__abs__(self):returnMyInt(abs(self.value))7. 注意事项与陷阱不要修改原对象除非有极其充分的理由否则应返回新对象。返回类型应一致例如取反一个MyInt应该返回MyInt而不是普通int否则会破坏类型一致性。与反向运算符无关__neg__是一元运算符没有反向版本。与__invert__区分__invert__对应按位取反~不要混淆。8. 总结特性说明角色定义一元负号运算符-签名__neg__(self) - object返回值新对象通常与self同类型调用时机-x底层C 层的nb_negative槽位最佳实践返回新对象不修改原对象与__pos__对称掌握__neg__是实现自定义数值类型的基本功。通过正确实现它你的类可以像内置类型一样自然地支持取反操作提升代码的可读性和数学直观性。如果在学习过程中遇到问题欢迎在评论区留言讨论!