方法
类中定义的函数称为方法。Python 的方法机制与许多语言不同:方法在定义时有一个显式的第一个参数代表实例,调用时由解释器隐式传入。这种设计使得函数对象与方法对象清晰分离,也为类方法和静态方法提供了统一的底层支持。
实例方法
实例方法是最常见的方法类型。定义时第一个参数为实例引用(约定命名为 self),调用时不需要显式提供该参数。
class MyClass:
def __init__(self, value):
self.value = value
def show(self):
return f'value = {self.value}'
obj = MyClass(42)
print(obj.show()) # value = 42
print(MyClass.show(obj)) # 等价调用,显式传入实例
当通过实例访问方法时,Python 执行方法绑定:将实例对象和函数对象打包为一个方法对象。调用方法对象时,实例被自动插入为第一个参数。
m = obj.show # m 是绑定方法对象
print(m()) # value = 42
# 方法对象持有实例和原始函数
print(m.__self__) # <__main__.MyClass object at ...>
print(m.__func__) # <function MyClass.show at ...>
方法对象可以被保存、传递,稍后再调用,这在回调和事件处理中非常有用。
在类外部定义方法
作为类属性的函数对象即为实例方法,函数定义的文本不必包含在类体内部。将外部函数赋值给类属性,效果与在类内定义完全相同。
def external_func(self, x, y):
return min(x, x + y)
class C:
f = external_func
def g(self):
return 'hello world'
h = g # h 与 g 完全等价
c = C()
print(c.f(2, 3)) # 2
print(c.g()) # hello world
print(c.h()) # hello world
虽然语法上允许,但这种做法通常会使代码阅读者感到困惑,应谨慎使用。更常见的场景是在运行时动态地为类添加方法。
类方法
使用 @classmethod 装饰器定义的方法为类方法。其第一个参数是类对象本身(约定命名为 cls),调用时由解释器自动传入。类方法既可以被类调用,也可以被实例调用,但传入的第一个参数始终是类。
class Employee:
_count = 0
def __init__(self, name):
self.name = name
Employee._count += 1
@classmethod
def get_count(cls):
return cls._count
@classmethod
def from_dict(cls, data):
"""替代构造器:从字典创建实例"""
return cls(data['name'])
e1 = Employee('Alice')
e2 = Employee('Bob')
print(Employee.get_count()) # 2
print(e1.get_count()) # 2 —— 实例调用,传入的仍是 Employee
# 使用替代构造器
e3 = Employee.from_dict({'name': 'Charlie'})
类方法的核心价值在于替代构造器(alternative constructor)。当对象有多种创建方式时,与其在 __init__ 中堆砌复杂的条件判断,不如提供语义清晰的类方法。dict.fromkeys()、int.from_bytes() 等内置方法都是这一模式的典范。
在继承体系中,类方法使用 cls 而非硬编码类名,确保子类调用时创建的是子类实例:
class Manager(Employee):
pass
m = Manager.from_dict({'name': 'Dave'})
print(type(m)) # <class '__main__.Manager'>
如果 from_dict 中使用的是 Employee(...) 而非 cls(...),则 Manager.from_dict() 会错误地返回 Employee 实例。
静态方法
使用 @staticmethod 装饰器定义的方法为静态方法。它既不接收实例,也不接收类,本质上就是挂载在类命名空间中的普通函数。
class MathUtil:
@staticmethod
def clamp(value, low, high):
"""将 value 限制在 [low, high] 范围内"""
return max(low, min(value, high))
@staticmethod
def is_valid_id(s):
return isinstance(s, str) and len(s) == 18
# 通过类或实例调用效果相同
print(MathUtil.clamp(150, 0, 100)) # 100
print(MathUtil.is_valid_id('123')) # False
静态方法适用于与类概念相关、但不需要访问类或实例状态的工具函数。将其放在类内部,主要是为了组织代码和命名空间,而非面向对象的需要。如果函数与类完全无关,放在模块级别通常更合适。
三种方法的对比
class Demo:
attr = 'class_attr'
def instance_method(self):
return f'instance: {self.attr}'
@classmethod
def class_method(cls):
return f'class: {cls.attr}'
@staticmethod
def static_method():
return 'no implicit argument'
d = Demo()
print(d.instance_method()) # instance: class_attr
print(d.class_method()) # class: class_attr
print(d.static_method()) # no implicit argument
# 通过类调用
print(Demo.class_method()) # class: class_attr
print(Demo.static_method()) # no implicit argument
# print(Demo.instance_method()) # TypeError: 缺少 self 参数
方法绑定机制的细节
当通过实例引用非数据属性时,Python 搜索该实例所属的类。如果名称对应一个函数对象,解释器会将实例和函数打包为方法对象。这一机制意味着:只有从实例访问时才会发生绑定,直接从类访问得到的是原始函数。
class Bag:
def add(self, item):
pass
b = Bag()
print(type(Bag.add)) # <class 'function'>
print(type(b.add)) # <class 'method'>
# 函数与方法的区别
print(Bag.add) # <function Bag.add at ...>
print(b.add) # <bound method Bag.add of <__main__.Bag object ...>>
方法对象在调用时构造新的参数列表:将 __self__ 插入到参数列表最前面,再调用底层的函数对象。因此 b.add('apple') 等价于 Bag.add(b, 'apple')。
方法调用其他方法
实例方法可以通过 self 调用同一对象的其他方法,包括 __init__ 中定义的方法或继承自父类的方法。
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def push_many(self, items):
for item in items:
self.push(item) # 通过 self 调用另一个实例方法
def size(self):
return len(self._items)
def is_empty(self):
return self.size() == 0 # 复用 size 方法
s = Stack()
s.push_many([1, 2, 3])
print(s.size()) # 3
print(s.is_empty()) # False
方法内部引用全局名称的规则与普通函数相同:方法的全局作用域是包含该方法定义语句的模块,类本身永远不会被用作全局作用域。