第二十八篇:实例属性与类属性——面向对象的数据组织
前言
实例属性和类属性是面向对象编程中最基础也是最重要的概念之一。理解它们的区别、优先级和使用场景,能够帮助你设计出更清晰、更高效的类结构。本文将深入讲解实例属性的动态绑定、类属性的定义与共享机制,以及类方法与静态方法的使用。
实例属性 vs 类属性
基本定义
class User:
# 类属性:所有实例共享
company = "TechCorp"
total_users = 0
def __init__(self, name, email):
# 实例属性:每个实例独立拥有
self.name = name
self.email = email
User.total_users += 1
user1 = User("小明", "xiaoming@example.com")
user2 = User("小红", "xiaohong@example.com")
# 访问类属性
print(User.company) # TechCorp
print(user1.company) # TechCorp — 通过实例也能访问
print(User.total_users) # 2
# 访问实例属性
print(user1.name) # 小明
print(user2.name) # 小红
核心区别对比
| 特性 | 实例属性 | 类属性 |
|---|---|---|
| 定义位置 | __init__ 内(self.xxx) |
类体内(非方法中) |
| 存储位置 | 每个实例的 __dict__ |
类的 __dict__ |
| 共享性 | 各实例独立 | 所有实例共享 |
| 内存占用 | 每创建一个实例多一份 | 只有一份 |
| 修改影响 | 只影响当前实例 | 影响所有实例 |
经典误区:实例修改类属性
class Counter:
count = 0 # 类属性
def increment(self):
self.count += 1 # ⚠️ 错误!这会创建实例属性而非修改类属性
c1 = Counter()
c2 = Counter()
c1.increment()
print(c1.count) # 1 — 实际创建了实例属性
print(c2.count) # 0 — 类属性未变,仍为 0
print(Counter.count) # 0
# ✅ 正确做法
class Counter:
count = 0
def increment(self):
Counter.count += 1 # 显式使用类名引用
c1 = Counter()
c2 = Counter()
c1.increment()
print(Counter.count) # 1 — 正确更新类属性
实例属性的动态绑定
运行时添加/删除属性
Python 允许在运行时动态添加或移除实例属性:
class Employee:
def __init__(self, name, department):
self.name = name
self.department = department
emp = Employee("小明", "技术部")
# 动态添加新属性
emp.salary = 20000
emp.hire_date = "2024-01-15"
print(emp.salary) # 20000
print(emp.hire_date) # 2024-01-15
# 动态删除属性
del emp.hire_date
# print(emp.hire_date) # AttributeError!
使用 dict 查看和遍历实例属性
class Product:
def __init__(self, name, price, weight):
self.name = name
self.price = price
self.weight = weight
product = Product("机械键盘", 399.9, 850)
# 查看所有实例属性(不包括方法等)
print(product.__dict__)
# {'name': '机械键盘', 'price': 399.9, 'weight': 850}
# 遍历并格式化输出
for key, value in product.__dict__.items():
print(f"{key}: {value}")
# name: 机械键盘
# price: 399.9
# weight: 850
# 动态设置属性
for attr_name, attr_value in {"color": "黑色", "material": "铝合金"}.items():
setattr(product, attr_name, attr_value)
print(product.color) # 黑色
类属性的高级用法
类属性作为数据字典
class AppConfig:
# 配置项全部定义为类属性,便于统一管理
APP_NAME = "MyApp"
VERSION = "1.0.0"
DATABASE = {
"host": "localhost",
"port": 5432,
"name": "myapp_db",
"user": "admin",
}
FEATURES = {
"authentication": True,
"logging": True,
"cache": False,
}
# 读取配置
print(AppConfig.APP_NAME) # MyApp
print(AppConfig.DATABASE["host"]) # localhost
print(AppConfig.FEATURES["cache"]) # False
# 修改配置(全局生效)
AppConfig.VERSION = "1.1.0"
类属性与方法
import hashlib
from datetime import datetime
class PasswordHasher:
"""密码哈希器 - 类方法和类属性"""
algorithm = "sha256"
iterations = 10000
@classmethod
def hash_password(cls, password: str, salt: str = None) -> dict:
"""使用类属性计算密码哈希"""
if salt is None:
salt = hashlib.sha256(password.encode()).hexdigest()[:8]
hashed = hashlib.pbkdf2_hmac(
cls.algorithm,
password.encode(),
salt.encode(),
cls.iterations
)
return {
"algorithm": cls.algorithm,
"iterations": cls.iterations,
"salt": salt,
"hash": hashed.hex()
}
result = PasswordHasher.hash_password("mypassword")
print(result["algorithm"]) # sha256
类方法与静态方法
三种方法类型对比
| 方法类型 | 第一个参数 | 适用场景 | 装饰器 |
|---|---|---|---|
| 实例方法 | self |
访问/修改实例属性 | 无 |
| 类方法 | cls |
访问/修改类属性,工厂方法 | @classmethod |
| 静态方法 | 无 | 独立于类和实例的工具函数 | @staticmethod |
实例方法
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
"""根据分数返回等级"""
if self.score >= 90:
return "A"
elif self.score >= 80:
return "B"
elif self.score >= 60:
return "C"
else:
return "D"
student = Student("小明", 85)
print(student.get_grade()) # B
类方法 @classmethod
from datetime import datetime
class DateHelper:
current_year = datetime.now().year
@classmethod
def year_of_birth(cls, age: int) -> int:
"""计算出生年份"""
return cls.current_year - age
@classmethod
def from_string(cls, date_str: str, fmt: str = "%Y-%m-%d"):
"""从字符串创建日期对象"""
parsed = datetime.strptime(date_str, fmt)
return {
"year": parsed.year,
"month": parsed.month,
"day": parsed.day,
}
print(DateHelper.year_of_birth(25))
# 2026 - 25 = 2001
print(DateHelper.from_string("2024-09-09"))
# {'year': 2024, 'month': 9, 'day': 9}
静态方法 @staticmethod
class StringUtils:
"""纯工具函数集合,不需要访问类或实例"""
@staticmethod
def is_palindrome(s: str) -> bool:
"""判断是否为回文"""
cleaned = s.lower().replace(" ", "")
return cleaned == cleaned[::-1]
@staticmethod
def truncate(text: str, max_length: int = 50, suffix="...") -> str:
"""截断文本"""
if len(text) <= max_length:
return text
return text[:max_length - len(suffix)] + suffix
@staticmethod
def slugify(text: str) -> str:
"""将中文转换为 URL-friendly slug"""
return text.lower().replace(" ", "-").replace("_", "-")
print(StringUtils.is_palindrome("level")) # True
print(StringUtils.is_palindrome("hello")) # False
print(StringUtils.truncate("Hello World", 5)) # Hello...
print(StringUtils.slugify("Python 教程")) # python-教程
实际应用场景选择
class Configuration:
DEFAULT_CONFIG = {"timeout": 30, "retries": 3}
def __init__(self, timeout=None, retries=None):
# 实例属性:使用默认值或传入值
self.timeout = timeout or self.DEFAULT_CONFIG["timeout"]
self.retries = retries or self.DEFAULT_CONFIG["retries"]
def update(self, **kwargs):
"""更新实例配置"""
for key, value in kwargs.items():
setattr(self, key, value)
@classmethod
def from_env(cls, env_vars: dict):
"""从环境变量创建配置"""
return cls(
timeout=int(env_vars.get("TIMEOUT", 30)),
retries=int(env_vars.get("RETRIES", 3)),
)
@staticmethod
def validate_timeout(value):
"""验证超时值的合法性"""
try:
val = int(value)
return 0 < val <= 300
except (ValueError, TypeError):
return False
# 使用示例
config = Configuration(timeout=60)
print(config.validate_timeout(45)) # True
print(config.validate_timeout(-1)) # False
env_config = Configuration.from_env({"TIMEOUT": "60"})
print(env_config.timeout) # 60
实战示例
学生管理系统(综合应用)
class Student:
# 类属性
school = "阳光中学"
total_students = 0
valid_grades = list(range(0, 101))
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.scores = {}
Student.total_students += 1
@property
def average_score(self):
"""平均分(只读属性)"""
if not self.scores:
return 0
return sum(self.scores.values()) / len(self.scores)
def add_score(self, subject, score):
"""添加成绩"""
if not isinstance(score, (int, float)):
raise ValueError("成绩必须是数字")
if not 0 <= score <= 100:
raise ValueError("成绩必须在 0~100 之间")
self.scores[subject] = score
@classmethod
def create_from_dict(cls, data: dict):
"""从字典创建学生"""
student = cls(data["name"], data["student_id"])
for subject, score in data.get("scores", {}).items():
student.add_score(subject, score)
return student
def __repr__(self):
avg = self.average_score
return f"{self.name}({self.student_id}): 平均={avg:.1f}"
# 测试
data1 = {
"name": "张三",
"student_id": "001",
"scores": {"数学": 92, "语文": 88, "英语": 95},
}
s1 = Student.create_from_dict(data1)
print(s1)
# 张三(001): 平均=91.7
print(Student.school) # 阳光中学
print(Student.total_students) # 1
最佳实践
何时用实例属性?
# ✅ 适合实例属性的场景:
# 1. 对象状态数据(用户信息、商品详情、订单金额)
# 2. 需要不同实例有不同值的字段
# 3. 对象生命周期内会改变的属性
class Order:
def __init__(self, item, quantity):
self.item = item # 实例属性
self.quantity = quantity # 实例属性
def calculate_total(self):
return self.item.price * self.quantity # 访问实例属性
何时用类属性?
# ✅ 适合类属性的场景:
# 1. 常量(MAX_ATTEMPTS = 3)
# 2. 统计计数器(total_records += 1)
# 3. 统一配置(DATABASE_URL = "...")
# 4. 枚举值(STATUS_NEW, STATUS_PROCESSING)
class APIRequest:
MAX_RETRIES = 3
TIMEOUT = 30
base_url = "https://api.example.com"
def make_request(self, endpoint):
url = f"{self.base_url}/{endpoint}" # 类属性
# ... 发送请求
封装原则
class BankAccount:
"""银行账号示例:展示封装的重要性"""
# 类属性:账户前缀
_bank_code = "ICBC"
_next_account_id = 1
def __init__(self, owner):
self.owner = owner
# 双下划线开头表示私有属性(名称修饰)
self.__balance = 0 # 外部不能直接访问
self._account_number = f"{self._bank_code}{BankAccount._next_account_id:08d}"
BankAccount._next_account_id += 1
@property
def balance(self):
"""公开余额查询"""
return self.__balance
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("余额不足")
self.__balance -= amount
account = BankAccount("小明")
account.deposit(1000)
print(account.balance) # 1000
# print(account.__balance) # AttributeError! 无法直接访问
总结
本文讲解了实例属性与类属性的核心知识:
- 实例属性 — 每个实例独立,在
__init__中定义,支持动态增删 - 类属性 — 所有实例共享,在类体中定义,常用于常量/配置/统计
- 动态绑定 — 运行时可添加/移除/修改实例属性
- 类方法 —
@classmethod接收cls,用于工厂方法和类级操作 - 静态方法 —
@staticmethod无需self/cls,实现工具函数 - 封装实践 — 双下划线前缀表示私有属性
- 最佳实践 — 实例属性放状态,类属性放配置/常量
下一篇文章将深入讲解获取对象信息:type、isinstance、dir。
本文是 Python 教程系列的第二十八篇。
