Python 类与面向对象基础
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
当数据和处理数据的逻辑需要绑在一起时,就该用类。这一篇讲类的正确写法,以及 Python 特有的魔术方法。
环境准备
示例在 Python 3.13 下验证,无需第三方依赖。
定义类
class User:
def __init__(self, name: str, age: int = 0):
self.name = name # 实例属性
self.age = age
def greet(self) -> str:
return f"你好,我是 {self.name}"
u = User("小明", 18)
u.greet() # '你好,我是 小明'
__init__是初始化方法(不是构造函数本体,对象由__new__创建)。self指当前实例,方法第一个参数必须是它(名字只是约定)。- 调用
u.greet()等价于User.greet(u)。
实例属性 vs 类属性
class Counter:
total = 0 # 类属性,所有实例共享
def __init__(self):
self.count = 0 # 实例属性,每个实例一份
Counter.total += 1
类属性适合放常量或跨实例计数;别把可变对象当类属性默认值(见常见错误)。
三种方法
class User:
role = "member" # 类属性
def __init__(self, name: str):
self.name = name
def greet(self): # 实例方法
return f"我是 {self.name}"
@classmethod
def admin(cls, name: str) -> "User": # 类方法:拿到类本身
return cls(name)
@staticmethod
def is_valid_name(name: str) -> bool: # 静态方法:不需要实例或类
return len(name.strip()) > 0
- 实例方法:操作实例状态。
@classmethod:替代构造器(工厂方法),cls会被正确绑定为子类。@staticmethod:逻辑上属于这个类,但不访问任何状态。
@property:把计算伪装成属性
class User:
def __init__(self, first: str, last: str):
self.first = first
self.last = last
@property
def full_name(self) -> str:
return f"{self.first} {self.last}"
@full_name.setter
def full_name(self, value: str) -> None:
self.first, self.last = value.split(" ", 1)
u = User("张", "三")
u.full_name # '张 三',像属性一样访问
u.full_name = "李 四" # 走 setter
好处:以后改成存储字段(或加校验)时,调用方代码不用改。
继承
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
raise NotImplementedError
class Dog(Animal):
def speak(self) -> str: # 重写父类方法
return "汪"
def fetch(self) -> str:
return f"{self.name} 去捡球"
class Dog(Animal):
def __init__(self, name: str, breed: str):
super().__init__(name) # 调用父类初始化
self.breed = breed
super() 在多重继承里按 MRO(方法解析顺序)找下一个类,不要把父类名写死。查看顺序:
Dog.__mro__
多重继承要慎用,容易变成菱形问题;优先用组合:
class Order:
def __init__(self, user: User, items: list[str]): # 持有对象,而不是继承它
...
常用魔术方法
class Point:
def __init__(self, x: float, y: float):
self.x, self.y = x, y
def __repr__(self) -> str: # 调试输出,给开发者看
return f"Point({self.x}, {self.y})"
def __str__(self) -> str: # str() / print,给用户看
return f"({self.x}, {self.y})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self) -> int: # 定义 __eq__ 后默认不可哈希
return hash((self.x, self.y))
def __len__(self) -> int: ... # len(obj)
def __iter__(self): ... # for x in obj
def __getitem__(self, key): ... # obj[key]
def __enter__ / __exit__: ... # with 语句
至少要写 __repr__:调试和日志全靠它。
__slots__ 可以省内存并禁止动态加属性:
class Point:
__slots__ = ("x", "y")
常见错误与注意事项
1. 可变对象当类属性
class Bag:
items = [] # 危险:所有实例共享同一个列表
b1, b2 = Bag(), Bag()
b1.items.append("x")
b2.items # ['x'],被污染了
放进 __init__ 里创建。
2. 方法忘了写 self
调用时报 TypeError: greet() takes 0 positional arguments but 1 was given。
3. __eq__ 没处理其他类型
直接访问 other.x 会在比较不同类时抛 AttributeError。用 isinstance 检查,不匹配返回 NotImplemented。
4. 定义了 __eq__ 却忘了 __hash__
对象会变成不可哈希,放进 set 或当字典键时报错。
5. 过度使用继承
只为复用一两个方法就继承,会让类层次变脆。优先组合与模块级函数。
小结
- 类把数据和行为绑在一起;
__init__初始化,self是实例。 @property让计算属性与存储属性可以互换;@classmethod做工厂方法。- 继承用
super(),多重继承谨慎,优先组合。 - 至少实现
__repr__;__eq__与__hash__成对定义。