飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

方法

类中定义的函数称为方法。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

方法内部引用全局名称的规则与普通函数相同:方法的全局作用域是包含该方法定义语句的模块,类本身永远不会被用作全局作用域。

上一页
类与对象
下一页
实例变量与类变量