前言
Python 里"一切皆对象",但很多同学写脚本一直用不上自定义类。这篇把 OOP 核心概念讲到位,同时告诉你 Pythonic 的取舍——不为了面向对象而面向对象。
一、类与实例
class Server:
"""表示一台服务器的类""" # docstring
def __init__(self, host, port=80): # 构造方法
self.host = host # 实例属性
self.port = port
self._status = "unknown" # _前缀 = 约定私有
self.__secret = "x" # __双下划线 = 名称改写(name mangling)
def ping(self): # 实例方法
return f"ping {self.host}:{self.port} -> {self._status}"
def set_status(self, status): # 实例方法: 修改状态
self._status = status
web = Server("10.0.0.1", 8080) # 实例化(不需要 new)
web.set_status("up")
print(web.ping()) # ping 10.0.0.1:8080 -> up
db = Server("10.0.0.2", 5432) # 每个实例状态独立
print(web.port, db.port) # 8080 5432
要点:
self是实例自身的引用,实例方法第一个参数永远是它(调用时不用传)- Python 没有 true private,
_x/__x只是约定和名称改写
类属性 vs 实例属性:
class Server:
kind = "linux" # 类属性, 所有实例共享
def __init__(self, host):
self.host = host # 实例属性, 每个实例独立
a, b = Server("s1"), Server("s2")
print(a.kind) # linux(实例找不到就找类)
Server.kind = "bsd" # 改类属性, 全体生效
a.kind = "windows" # 给 a 创建了实例属性遮蔽类属性
print(b.kind) # bsd —— b 不受影响
二、继承与 super()
class BaseNotifier:
def __init__(self, name):
self.name = name
def send(self, msg):
raise NotImplementedError # 接口约定: 子类必须实现
def info(self):
return f"notifier: {self.name}"
class DingtalkNotifier(BaseNotifier):
def __init__(self, name, webhook):
super().__init__(name) # 复用父类初始化
self.webhook = webhook
def send(self, msg): # 方法重写(override)
print(f"[{self.name}] POST {self.webhook} body={msg}")
class EmailNotifier(BaseNotifier):
def __init__(self, name, smtp):
super().__init__(name)
self.smtp = smtp
def send(self, msg):
print(f"[{self.name}] SMTP {self.smtp} body={msg}")
# 多态: 同一接口, 不同行为
notifiers = [
DingtalkNotifier("ding", "https://oapi.dingtalk.com/robot/send?token=x"),
EmailNotifier("mail", "smtp.corp.local"),
]
for n in notifiers:
n.send("CPU usage high!") # 各自的 send 被调用
isinstance / issubclass / 鸭子类型:
isinstance(n, BaseNotifier) # True
issubclass(DingtalkNotifier, BaseNotifier) # True
# 但 Python 更推崇鸭子类型: "会 send 就行, 管你是谁生的"
class Webhook: # 没继承任何类
def send(self, msg): ...
for n in [DingtalkNotifier(...), Webhook()]:
n.send("hi") # ✅ 都能跑
三、魔术方法:让对象更 Pythonic
class Money:
def __init__(self, yuan):
self.yuan = yuan
def __repr__(self): # 调试/打印显示
return f"Money({self.yuan}¥)"
def __eq__(self, other): # ==
return self.yuan == other.yuan
def __lt__(self, other): # < (配合 sorted)
return self.yuan < other.yuan
def __add__(self, other): # +
return Money(self.yuan + other.yuan)
def __len__(self): # len()
return self.yuan
a, b = Money(100), Money(50)
print(a) # Money(100¥)
print(a + b) # Money(150¥)
print(a > b) # True
sorted([b, a]) # [Money(50¥), Money(100¥)]
常用魔术方法速查:
| 方法 | 触发场景 |
|---|---|
__init__ |
实例化 |
__repr__ / __str__ |
打印、调试 |
__eq__ __lt__ |
比较、排序 |
__len__ __getitem__ |
len()、下标访问 |
__enter__ __exit__ |
with 上下文管理 |
__iter__ __next__ |
for 迭代 |
四、property:优雅的读写控制
class AppConfig:
def __init__(self):
self._port = 80
@property
def port(self): # 读 port 时触发
return self._port
@port.setter
def port(self, value): # 写 port 时触发校验
if not (1 <= value <= 65535):
raise ValueError(f"非法端口: {value}")
self._port = value
conf = AppConfig()
conf.port = 8080 # 走 setter 校验
print(conf.port) # 走 getter
# conf.port = 99999 # ValueError
好处:调用方代码不变(conf.port = x),校验逻辑却收进类里。
五、dataclass:消灭样板代码
Python 3.7+ 的 @dataclass 让"数据类"一行搞定:
from dataclasses import dataclass, field
@dataclass
class Host:
name: str
ip: str
port: int = 22
tags: list[str] = field(default_factory=list) # 可变默认值的正确姿势
# 自动获得: __init__ / __repr__ / __eq__
h1 = Host("web01", "10.0.1.11", tags=["web"])
h2 = Host("web01", "10.0.1.11", tags=["web"])
print(h1) # Host(name='web01', ip='10.0.1.11', port=22, tags=['web'])
print(h1 == h2) # True
再加一个方法就是完整业务对象:
@dataclass
class Host:
...
@property
def addr(self) -> str:
return f"{self.ip}:{self.port}"
六、什么时候不需要类
# ❌ 过度设计: 一个脚本也要 AbstractFactorySingleton
# ✅ 这些场景用函数/字典就够了:
# 场景1: 只是存数据 -> dict / dataclass
server = {"host": "a", "port": 80}
# 场景2: 一组相关函数 -> 模块本身就够
# deploy.py: install(), start(), stop()
# 场景3: 简单分发 -> 字典比类层次好
handlers = {"start": do_start, "stop": do_stop}
handlers[action]()
判断标准:有状态要封装、有多态要抽象、样板代码太多——满足其一才上类。
小结
| 概念 | 关键点 |
|---|---|
__init__ + self |
实例状态初始化 |
继承 + super() |
复用父类;Python 更爱组合与鸭子类型 |
| 魔术方法 | 让自定义对象融入语言原生操作 |
| property | 无感校验读写 |
| dataclass | 数据类首选,一行顶十行 |
| 克制 | 脚本级代码用函数 + dict 就够 |
本文是「Python」系列第 4 篇。