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

    • 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 中不存在"成员变量声明"的概念。类体中直接赋值的名称成为类变量(class variable),而在方法内部通过 self.attr 赋值的成为实例变量(instance variable)。两者的存储位置、生命周期和共享方式截然不同,混淆它们是新手最常见的错误之一。

实例变量:每个对象独立持有

实例变量绑定在实例对象自身的 __dict__ 中,不同实例之间完全隔离。它们不需要声明,首次赋值时即产生,也可以在运行时动态删除。

class Dog:
    def __init__(self, name):
        self.name = name      # 实例变量
        self.tricks = []      # 每个实例获得独立的列表

    def add_trick(self, trick):
        self.tricks.append(trick)

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')

print(d.tricks)   # ['roll over']
print(e.tricks)   # ['play dead']
print(d.name)     # Fido

实例变量通常在 __init__ 中初始化,但也可以在其他方法或外部代码中随时创建。如果访问一个尚未赋值的实例属性,会抛出 AttributeError:

class Cat:
    pass

c = Cat()
# print(c.name)   # AttributeError: 'Cat' object has no attribute 'name'
c.name = 'Whiskers'   # 动态创建
print(c.name)         # Whiskers

类变量:所有实例共享

类变量存储在类对象的 __dict__ 中,被该类的所有实例共享。它适合存放常量、默认值或跨实例统计信息。

class Dog:
    kind = 'canine'       # 类变量:所有 Dog 共享
    count = 0             # 类变量:用于统计实例数
    
    def __init__(self, name):
        self.name = name  # 实例变量
        Dog.count += 1    # 修改类变量

d = Dog('Fido')
e = Dog('Buddy')
print(d.kind)     # canine
print(e.kind)     # canine
print(Dog.count)  # 2

通过类名访问类变量(如 Dog.count)语义清晰,直接表明操作的是共享数据。虽然通过实例也能读取类变量,但修改时行为差异极大,需要格外小心。

可变类变量的陷阱

当类变量指向可变对象(列表、字典、集合等)时,所有实例操作的是同一个对象。这是 Python 面向对象中最隐蔽的陷阱之一。

class Dog:
    tricks = []           # 危险:所有实例共享同一个列表
    
    def __init__(self, name):
        self.name = name
    
    def add_trick(self, trick):
        self.tricks.append(trick)   # 修改的是共享列表!

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')

print(d.tricks)   # ['roll over', 'play dead']  —— 非预期结果!
print(e.tricks)   # ['roll over', 'play dead']
print(d.tricks is e.tricks)   # True,确实是同一个对象

正确的做法是在 __init__ 中为每个实例创建独立的可变对象:

class Dog:
    def __init__(self, name):
        self.name = name
        self.tricks = []      # 每个实例获得新列表
    
    def add_trick(self, trick):
        self.tricks.append(trick)

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
print(d.tricks)   # ['roll over']
print(e.tricks)   # ['play dead']

属性查找顺序

当通过实例访问属性时,Python 按照以下顺序查找:

  1. 首先在实例的 __dict__ 中查找(数据属性)
  2. 如果未找到,在类的 __dict__ 中查找
  3. 如果类中未找到,沿继承链向上查找(见继承章节)

这一规则意味着实例属性会遮蔽类属性。当实例和类拥有同名属性时,实例优先。

class Warehouse:
    purpose = 'storage'
    region = 'west'

w1 = Warehouse()
w2 = Warehouse()

print(w1.region)      # west —— 读取类变量

w2.region = 'east'    # 在 w2 的 __dict__ 中创建实例变量
print(w2.region)      # east —— 实例变量遮蔽类变量
print(w1.region)      # west —— w1 不受影响

# 查看命名空间
print(w2.__dict__)    # {'region': 'east'}
print(Warehouse.__dict__['region'])   # west

删除实例属性后,对该属性的访问会重新回落到类属性:

del w2.region
print(w2.region)      # west —— 重新暴露类属性

通过实例修改类变量的误区

初学者常犯的错误是通过实例对类变量赋值,期望修改所有实例共享的值:

class Counter:
    total = 0

c1 = Counter()
c2 = Counter()

c1.total = 100        # 这不是修改类变量!
print(c1.total)       # 100 —— 实例变量
print(c2.total)       # 0   —— 仍是类变量
print(Counter.total)  # 0   —— 类变量未被修改

c1.total = 100 在 c1 的 __dict__ 中新建了键 total,完全遮蔽了类变量 Counter.total。要真正修改类变量,必须通过类名赋值:

Counter.total = 200
print(c2.total)       # 200
print(c1.total)       # 100 —— c1 的实例变量仍遮蔽类变量

类变量作为默认值

类变量有时被用作实例属性的默认值。当实例没有显式设置该属性时,自动回退到类变量。

class Config:
    timeout = 30          # 类级别的默认超时
    
    def __init__(self):
        self.host = 'localhost'
    
    def get_timeout(self):
        # 优先返回实例设置,否则使用类默认值
        return getattr(self, 'timeout', Config.timeout)

c = Config()
print(c.get_timeout())    # 30

c.timeout = 60            # 为特定实例覆盖
print(c.get_timeout())    # 60

这种设计模式在框架和库中很常见,但需要注意可变默认值的陷阱。如果默认值是列表或字典,务必在 __init__ 中创建副本或新对象。

上一页
方法
下一页
私有变量