
Python 3.12 Magic Method -__invert__(self)__invert__是 Python 中用于定义一元按位取反运算符~的核心魔术方法。当对一个对象使用~obj时Python 会自动调用该对象的__invert__方法。它最常见的用途是实现整数的按位取反即~x -x - 1但也可以被重载用于任何需要“反转”或“互补”语义的操作例如集合的补集、布尔状态的翻转等。正确实现__invert__可以让自定义类支持~运算并与其他一元运算符如-、保持一致的语义。本文将详细解析其定义、底层机制、设计原则并通过多个示例逐行演示如何正确实现。1. 定义与签名def__invert__(self)-object:...参数self当前对象。返回值应返回一个新的对象代表~self的结果。如果运算未定义应返回单例NotImplemented。调用时机当在表达式~x中使用对象x时Python 会调用x.__invert__()。2. 用途与典型场景整数位运算对于整数类型~x定义为-x - 1即按位取反包括符号位。自定义位掩码实现位标志的按位取反。布尔反转在自定义布尔逻辑类中~可用于逻辑非但通常用not或__invert__重载。集合补集在全集的上下文中~set可以表示集合的补集。状态切换用于切换枚举状态或标志位。一元按位取反运算通常不应修改原对象而是返回一个新对象。3. 底层实现机制在 Python/C API 层面一元按位取反操作由PyNumber_Invert函数处理。每个类型对象PyTypeObject都有一个tp_as_number结构体其中包含nb_invert槽位这是一个函数指针用于处理一元按位取反。当执行~x时解释器会获取x的类型对象的tp_as_number结构。如果存在nb_invert则调用它传入x返回结果对象或Py_NotImplemented。如果nb_invert不存在或返回Py_NotImplemented则抛出TypeError。对于 Python 层定义的__invert__它会被包装到nb_invert槽位中。因此实现__invert__后~obj就能正确调用。在 CPython 源码中PyNumber_Invert的实现类似于PyObject*PyNumber_Invert(PyObject*o){PyNumberMethods*m;if(oNULL)returnnull_error();mo-ob_type-tp_as_number;if(mm-nb_invert)returnm-nb_invert(o);returntype_error(bad operand type for unary ~: %.200s,o);}4. 设计原则与最佳实践返回新对象通常不应修改self而应返回一个包含结果的新对象。这符合数学上“取反”不改变原值的语义。数学一致性对于整数~x应等于-x - 1这是 Python 内置规则。自定义整数类应遵循这一规则。语义明确如果用于非位运算场景如集合补集应确保操作意义清晰并在文档中说明。与__neg__、__pos__区分~是位取反-是算术取反两者不同。对于不可变对象自然返回新对象对于可变对象虽然可以就地修改后返回self但这样做会破坏~的常见语义容易引起混淆不建议。掩码处理如果对象表示有限位宽如标志位取反后应进行掩码否则结果会包含高位无用位。5. 示例与逐行解析示例 1自定义整数类实现按位取反classMyInt:def__init__(self,value):self.valuevaluedef__invert__(self):返回一个新的 MyInt值为原值的按位取反~x -x - 1returnMyInt(~self.value)def__repr__(self):returnfMyInt({self.value})逐行解析行代码解释1-3__init__初始化整数值。4-6__invert__定义按位取反。5return MyInt(~self.value)使用内置~计算原值的按位取反并包装成新MyInt返回。不修改原对象。7-8__repr__便于显示。为什么这样写返回新对象符合不可变语义原对象不受影响。使用内置~确保与 Python 整数规则一致。验证aMyInt(5)# 5 的二进制 0101b~aprint(a)# MyInt(5) 原对象不变print(b)# MyInt(-6) 因为 ~5 -6print(bin(5))print(bin(-6))运行结果MyInt(5) MyInt(-6) 0b101 -0b110示例 2自定义位掩码类标志位取反classPermissions:READ0b100WRITE0b010EXECUTE0b001def__init__(self,flags0):self.flagsflagsdef__invert__(self):返回一个新的 Permissions所有标志位取反补集# 假设只关心低3位inverted(~self.flags)0b111returnPermissions(inverted)def__repr__(self):returnfPermissions({bin(self.flags)})逐行解析行代码解释1-4定义权限常量方便使用。5-7__init__存储标志位。8-12__invert__实现标志位取反。10-11取反并掩码~self.flags对整数按位取反但会反转高位所以我们用 0b111只保留低3位然后创建新对象返回。13-14__repr__便于显示。为什么这样写按位取反在权限系统中可以表示“除指定权限外的所有权限”即补集。返回新对象原对象不变。通过掩码确保结果在有效位范围内。验证pPermissions(Permissions.READ|Permissions.WRITE)# 0b110print(~p)# Permissions(0b001) 即 EXECUTE运行结果Permissions(0b1)示例 3布尔反转类演示自定义语义classMyBool:def__init__(self,value):self.valuebool(value)def__invert__(self):返回一个逻辑取反的新 MyBoolreturnMyBool(notself.value)def__repr__(self):returnfMyBool({self.value})解析这里~被重载为逻辑非虽然 Python 通常用not实现逻辑非但__invert__也可以用于此目的。注意这可能会引起混淆应谨慎使用。验证bMyBool(True)print(~b)# MyBool(False)运行结果MyBool(False)示例 4不可变对象与缓存classImmutableBits:def__init__(self,bits):self._bitsbits self._invertedNone# 缓存def__invert__(self):ifself._invertedisNone:self._invertedImmutableBits(~self._bits)returnself._inverteddef__repr__(self):returnfImmutableBits({bin(self._bits)})解析这里__invert__使用了缓存避免重复计算。由于对象不可变缓存是安全的。验证i1ImmutableBits(0b011)print(~i1)print(~i1)运行结果ImmutableBits(-0b100) ImmutableBits(-0b100)6. 注意事项与陷阱不要修改原对象__invert__应返回新对象除非类特意设计为可变且取反操作修改自身但极不推荐。与内置整数行为一致对于自定义整数类应遵循~x -x - 1否则用户会感到困惑。注意掩码范围如果对象表示有限位宽如标志位取反后应进行掩码否则结果会包含高位无用位。与__neg__混淆~和-是完全不同的运算不要混淆。性能考虑如果__invert__计算复杂可以考虑缓存结果如示例4。7. 总结特性说明角色定义一元按位取反运算符~签名__invert__(self) - object返回值新对象通常与self同类型调用时机~x底层C 层的nb_invert槽位最佳实践返回新对象不修改原对象对整数遵循~x -x - 1对有限位宽考虑掩码掌握__invert__可以让自定义类支持按位取反运算无论是数学上的位操作还是具有特殊语义的补集操作。通过理解其底层机制和设计原则你可以构建出与 Python 内置类型一样自然、健壮的类。如果在学习过程中遇到问题欢迎在评论区留言讨论!