前言先说一个前提「类的高级函数」这个说法本身并不标准Python 官方文档里没有这个术语。它通常指的是两类东西一是类里定义的那批特殊方法special method也叫魔法方法magic method名字两头带双下划线如__init__、__len__、__call__二是面向对象意义上的「函数式能力」比如让实例像函数一样被调用、用property把方法伪装成属性、用classmethod/staticmethod写类级别的函数。本文按这个更准确的范围来讲两者都会覆盖。第二个要澄清的误解是很多人以为「双下划线方法」是给dir()看的、平时用不到。恰恰相反你写的每一个len(obj)、obj[key]、obj other、with obj as x:背后都在调用对象的特殊方法。理解了这层你就能让自己定义的类表现得和内置类型一样自然。本文以 Python 3 为基准。Python 2.7 已于 2020 年 1 月 1 日停止维护Python 2 里类通常要继承object才是新式类Python 3 里所有类默认就是新式类别再写class Foo(object)这种遗留风格。一、认识特殊方法语法糖背后是什么特殊方法是 Python 数据模型约定好的一批「协议钩子」。你实现它们就等于告诉解释器「我的对象支持这种操作」。对应关系如下你写的代码实际调用的特殊方法len(obj)obj.__len__()obj[key]obj.__getitem__(key)obj[key] vobj.__setitem__(key, value)for x in objobj.__iter__()obj otherobj.__add__(other)obj(args)obj.__call__(args)print(obj)obj.__str__()repr(obj)obj.__repr__()obj otherobj.__eq__(other)with obj as x:obj.__enter__()/obj.__exit__()特殊方法不建议直接调用交给内置语法触发即可。你只需要定义不需要自己写obj.__len__()。二、构造与显示__init__、__repr__、__str____init__在实例创建后调用用来初始化属性。注意它不做「创建对象」这件事创建是__new__的活日常几乎不用重写后者。# 适用于 Python 3.8class Point:def __init__(self, x, y):self.x xself.y ydef __repr__(self):return fPoint({self.x}, {self.y})def __str__(self):return f({self.x}, {self.y})p Point(1, 2)print(repr(p)) # Point(1, 2)print(str(p)) # (1, 2)print(p) # (1, 2)__repr__面向开发者理想情况下返回「能重新构造出该对象的表达式」调试和容器显示都靠它。__str__面向用户print优先用它没定义时退回__repr__。只定义__repr__一个通常也够用。三、让实例像内置类型长度、下标、迭代、比较与调用定义__len__后len(obj)才可用定义__getitem__后obj[i]才可用定义__iter__或提供__getitem__后就能for遍历。# 适用于 Python 3.8class Playlist:def __init__(self, songs):self.songs list(songs)def __len__(self):return len(self.songs)def __getitem__(self, index):return self.songs[index]def __iter__(self):return iter(self.songs)pl Playlist([A, B, C])print(len(pl)) # 3print(pl[1]) # Bprint(list(pl)) # [A, B, C]for song in pl:print(song) # A B C__len__必须返回非负整数否则TypeError。__getitem__收到切片时index会是一个slice对象要不要支持取决于你。比较用__eq__同时要处理「和不同类型的对象比较」的情况返回NotImplemented让 Python 去尝试对方的__eq__# 适用于 Python 3.8class Amount:def __init__(self, value):self.value valuedef __eq__(self, other):if not isinstance(other, Amount):return NotImplementedreturn self.value other.valuedef __hash__(self):return hash(self.value)print(Amount(5) Amount(5)) # Trueprint({Amount(5), Amount(5)}) # {Amount object}只有一个元素这里必须同时定义__hash__一旦你重写__eq__而没有重写__hash__Python 会把该类的__hash__置为None实例就不能再放进set或当作dict的键。定义了__call__的实例本身就像函数一样能加括号调用这类对象叫可调用对象callable。# 适用于 Python 3.8class Multiplier:def __init__(self, factor):self.factor factordef __call__(self, x):return x * self.factortriple Multiplier(3)print(triple(10)) # 30print(callable(triple)) # True比起返回一个闭包用带状态的类来实现「多参数化的函数」往往更清晰因为状态是显式的实例属性。四、类级别的函数property、classmethod、staticmethod这三个装饰器让方法有不同的绑定方式装饰器第一个参数调用方式典型用途普通方法selfinstance.method()多数情况propertyselfinstance.attr不加括号计算属性、只读属性classmethodclsClass.method()或实例调用备用构造器staticmethod无Class.method()与实例无关的工具函数# 适用于 Python 3.8class Circle:def __init__(self, radius):self.radius radiuspropertydef area(self):return 3.14159 * self.radius ** 2classmethoddef from_diameter(cls, diameter):return cls(diameter / 2)staticmethoddef unit_name():return cmc Circle(2)print(round(c.area, 2)) # 12.57 ← 当属性用不加括号print(Circle.unit_name()) # cmc2 Circle.from_diameter(10)print(c2.radius) # 5.0property让area看起来像属性读的时候自动算classmethod的cls是类本身适合写「换个参数构造」的备用构造器staticmethod只是放在类命名空间里的普通函数。实战一个综合小类把构造、显示、比较、迭代、可调用拼成一个可直接运行的例子。# 适用于 Python 3.8class Temperature:表示一个摄氏温度支持比较、显示与格式化调用。def __init__(self, celsius):self.celsius float(celsius)def __repr__(self):return fTemperature({self.celsius})def __str__(self):return f{self.celsius:.1f}°Cdef __eq__(self, other):if not isinstance(other, Temperature):return NotImplementedreturn self.celsius other.celsiusdef __lt__(self, other):if not isinstance(other, Temperature):return NotImplementedreturn self.celsius other.celsiusdef __hash__(self):return hash(self.celsius)def __call__(self, scaleF):if scale F:return self.celsius * 9 / 5 32if scale K:return self.celsius 273.15return self.celsiusdef __add__(self, other):if isinstance(other, Temperature):return Temperature(self.celsius other.celsius)if isinstance(other, (int, float)):return Temperature(self.celsius other)return NotImplementedif __name__ __main__:a Temperature(20)b Temperature(25)print(a) # 20.0°Cprint(repr(a)) # Temperature(20.0)print(a b) # Trueprint(a Temperature(20)) # Trueprint(a(F)) # 68.0print(a(K)) # 293.15print((a 5)) # 25.0°Cprint(sorted([b, a])) # [Temperature(20.0), Temperature(25.0)]要点__lt__让实例支持也就能被sorted排序。__call__(scaleF)让实例能像函数一样调用默认返回华氏度。__add__对非数值、非同类对象返回NotImplemented交给 Python 判定而不是抛异常或返回错值。__eq__与__hash__成对出现实例才能进集合。常见坑点重写__eq__却忘了__hash__❌ 只写__eq__然后把实例放进set——TypeError: unhashable type。 ✅ 同时定义__hash__确实不该可哈希时显式写__hash__ None。__repr__返回非字符串❌def __repr__(self): return self.x—— 若self.x不是字符串TypeError。 ✅def __repr__(self): return fPoint({self.x!r}, {self.y!r})。__len__返回负数或非整数❌def __len__(self): return -1——ValueError。 ✅ 返回len(self.items)这类非负整数。__init__里用可变默认参数❌def __init__(self, items[]): self.items items—— 所有实例共享同一列表。 ✅def __init__(self, itemsNone): self.items list(items) if items else []。把property当方法调用❌ 对property修饰的area写c.area()—— 会尝试调用返回的浮点数TypeError。 ✅ 直接写c.area不加括号。classmethod与staticmethod混淆❌ 备用构造器写成staticmethod拿不到cls只能硬编码类名子类继承时出错。 ✅ 需要cls就用classmethod与类无关的工具函数才用staticmethod。__eq__里对不同类型不返回NotImplemented❌return self.value other.value—— 传进别的类型会AttributeError。 ✅ 先if not isinstance(other, 本类): return NotImplemented。直接调用双下划线方法❌ 到处写obj.__len__()、obj.__add__(x)—— 绕过了语法层的类型检查。 ✅ 用len(obj)、obj x等内置语法让解释器去调用。总结能力需要定义的特殊方法初始化 / 显示__init__、__repr__、__str__长度 / 下标 / 迭代__len__、__getitem__、__iter__比较 / 哈希__eq__、__lt__、__hash__可调用__call__算术__add__、__mul__等上下文管理__enter__、__exit__所谓「类的高级函数」本质就是数据模型约定的一批协议钩子。你实现哪个你的类就获得哪种能力。写的时候记住两条底线__eq__与__hash__要配对比较方法遇到不认识的对象要返回NotImplemented。把这两条守住自定义类就能像内置类型一样自然地融入 Python 的语法。