Day 2 学习主题「面向对象 + Pythonic 风格」
导语:为什么你必须掌握这些技能?
写 Python,不只是会用语法,更关键在于写出优雅、可维护、具备“Python味儿”的代码。今天这篇,我们将聚焦在 Python 面向对象的核心特性 + Pythonic 风格写法,掌握这些,代码瞬间高级起来,职场加分必备!
知识点预览
本篇你将学到:
-
如何使用类、继承、多态构建可拓展的结构
-
魔法方法:
__init__
、__str__
、__repr__
的使用场景 -
@staticmethod
与@classmethod
的实际用途 -
常用 Pythonic 内置函数:
map()
、filter()
、sorted()
-
with
和上下文管理器,轻松优雅资源管理
1️⃣ 背景介绍:为什么要学面向对象 + Pythonic 写法?
Python 既是面向对象语言,也是功能强大的脚本语言。但真正能写出“高级感”的 Python 代码,往往离不开:
-
清晰的类结构设计(面向对象思想)
-
简洁的语法表达能力(Pythonic 写法)
这两者结合,能让你的代码可复用、易维护、开发效率高,还能让团队成员读得懂、用得爽。
2️⃣ 技术方案拆解:关键概念与语法详解
✅ 类与对象基础
class Animal:def __init__(self, name):self.name = namedef speak(self):return f"{self.name} makes a sound"dog = Animal("Buddy")
print(dog.speak()) # Buddy makes a sound
✅ 继承、多态、组合
class Dog(Animal):def speak(self):return f"{self.name} says Woof!"class Cat(Animal):def speak(self):return f"{self.name} says Meow!"def make_sound(animal):print(animal.speak()) # 多态:传不同对象,行为不同make_sound(Dog("Max"))
make_sound(Cat("Luna"))
组合示例:
class Engine:def run(self):print("Engine running")class Car:def __init__(self):self.engine = Engine()def start(self):self.engine.run()
✅ 魔法方法精讲
class User:def __init__(self, name):self.name = namedef __str__(self): # 面向用户return f"User: {self.name}"def __repr__(self): # 面向开发者/调试return f"User(name='{self.name}')"print(str(User("Alice")))
print(repr(User("Bob")))
class User:
定义了一个名为 User
的类,表示“用户”对象。
def __init__(self, name):
这是 Python 中的 构造方法(构造函数),当我们创建对象时会自动调用它。
User("Alice")
这行代码会触发:
__init__(self="实例本身", name="Alice")
它的作用是给对象绑定一个属性 self.name = name
,即将 "Alice"
存入该实例的 name
属性中。
def __str__(self):
这是 Python 的 魔法方法之一,用于定义对象被 str()
函数或 print()
打印时的“用户友好”展示形式。
str(User("Alice"))
# 调用的是 __str__,输出是:
# User: Alice
📌 设计意图:
-
给终端用户看的(比如打印日志)
-
强调可读性
def __repr__(self):
同样是魔法方法,用于定义对象被 repr()
或直接在解释器中回显时的表现形式。
repr(User("Bob"))
# 调用的是 __repr__,输出是:
# User(name='Bob')
📌 设计意图:
-
面向程序员和调试者
-
目标是“清晰明确”且尽可能“可复现”这个对象(repr 的本意就是“representation”)
一个优秀的 __repr__
通常写成能被 eval()
复现的格式,比如:
user = eval(repr(User("Bob"))) # 理论上能还原一个等价对象
两者区别对比
特性 | __str__ | __repr__ |
---|---|---|
触发方式 | print(obj) or str(obj) | repr(obj) or 直接在解释器中输出 |
面向谁 | 用户 | 开发者 |
目的 | 可读性 | 明确性 / 可复现性 |
优先级 | 有 __str__ 则 print 用它 | 没有 __str__ 时 print 会 fallback 到 __repr__ |
输出结果解析
print(str(User("Alice")))
# 输出:User: Aliceprint(repr(User("Bob")))
# 输出:User(name='Bob')
✔️ str()
调用用户友好的展示形式
✔️ repr()
调用程序员友好的调试形式
建议与实践
-
如果你只定义了
__repr__
,print(obj)
也会自动 fallback 使用它; -
日常写类时,建议至少实现
__repr__
; -
如果你的对象有日志或打印需求,同时实现两者更好,分别服务不同人群。
✅ @staticmethod
vs @classmethod
class MathTool:@staticmethoddef add(a, b):return a + b # 与类无关@classmethoddef from_string(cls, data):a, b = map(int, data.split(','))return cls(a + b) # 依赖类信息def __init__(self, value):self.value = valueprint(MathTool.add(3, 5)) # 8
obj = MathTool.from_string("10,20")
print(obj.value) # 30
类定义与构造方法
class MathTool:def __init__(self, value):self.value = value
- 这个类用于封装一个数值(value)。
- 实例化时传入一个数值,存到
self.value
中。
@staticmethod
:静态方法
@staticmethod
def add(a, b):return a + b
特点:
- 不需要访问类(
cls
)或实例(self
) - 就是一个“和类放在一起的普通函数”,逻辑上属于类,但技术上和类无关
使用方式:
MathTool.add(3, 5) # 输出:8
你也可以通过对象调用,但本质一样:
obj = MathTool(0)
obj.add(1, 2) # 也是 3
📌 适用场景: 工具类、纯函数逻辑封装,不依赖对象或类状态。
@classmethod
:类方法
@classmethod
def from_string(cls, data):a, b = map(int, data.split(','))return cls(a + b)
特点:
- 第一个参数是
cls
(当前类),不是实例 - 可用于“构造器方法”:根据特定格式构建实例
cls(...)
可以调用构造方法创建实例(和__init__
绑定)
使用方式:
obj = MathTool.from_string("10,20")
等价于:
a, b = 10, 20
value = a + b # 30
obj = MathTool(30)
📌 适用场景:
- 多个“构造入口”需求(常见于数据解析、工厂模式)
- 要兼容子类返回对象
总结对比:@staticmethod
vs @classmethod
特性 | @staticmethod | @classmethod |
---|---|---|
参数 | 无默认参数 | 第一个参数是 cls |
是否可创建对象 | ❌ 无法访问类信息 | ✅ 可返回类的实例 |
是否访问类状态 | 否 | 可以 |
场景 | 工具方法、无状态逻辑 | 多构造方法、工厂方法 |
调用方式 | ClassName.method() | ClassName.method() |
最后两行输出解析
print(MathTool.add(3, 5)) # 输出:8
- 调用静态方法,简单加法运算
obj = MathTool.from_string("10,20")
print(obj.value) # 输出:30
- 调用类方法,将字符串
"10,20"
拆解为 10 和 20,加和后传入构造器 - 返回一个
MathTool(30)
对象,obj.value == 30
实战建议
- 若你只是想组织函数,不依赖类:用
@staticmethod
- 若你要构造对象(甚至考虑子类化扩展):用
@classmethod
- 实际项目中,
from_xxx
类方法很常见(如:from_dict
、from_json
)
3️⃣ Pythonic 写法提升代码气质
内置函数妙用
# map:批量转换
names = ['alice', 'bob', 'charlie']
print(list(map(str.title, names))) # ['Alice', 'Bob', 'Charlie']# filter:条件筛选
scores = [60, 75, 82, 40]
passed = list(filter(lambda x: x >= 60, scores))
print(passed) # [60, 75, 82]# sorted:支持 key 自定义
students = [{'name': 'Tom', 'score': 88}, {'name': 'Jerry', 'score': 95}]
print(sorted(students, key=lambda s: s['score'], reverse=True)) #[{'name': 'Jerry', 'score': 95}, {'name': 'Tom', 'score': 88}]
4️⃣ with 语句 & 上下文管理器:优雅资源控制
with open("data.txt", "r") as f:content = f.read()
# 自动关闭文件,无需 f.close()
#写入文件
with open("output.txt", "w") as f:f.write("Hello, World!")
#逐行读取
with open("data.txt", "r") as f:for line in f:print(line.strip())
自定义上下文管理器:
class Timer:def __enter__(self):import timeself.start = time.time()return selfdef __exit__(self, *args):print(f"Time used: {time.time() - self.start:.2f}s")with Timer():sum(range(1000000))
什么是上下文管理器?
-
上下文管理器是指实现了
__enter__()
和__exit__()
方法的对象。 -
它可以与
with
语句一起使用,用于自动处理资源获取和释放,比如打开/关闭文件、连接数据库、计时器等。
1. __enter__(self)
def __enter__(self):import timeself.start = time.time()return self
-
在
with Timer()
执行时,会首先调用__enter__()
。 -
这里记录了当前时间
self.start
,作为起始时间戳。 -
return self
表示可以通过as
关键字获得这个对象(本例没用as
但依然返回了)。
2. __exit__(self, *args)
def __exit__(self, *args):print(f"Time used: {time.time() - self.start:.2f}s")
-
在
with
代码块执行完之后(无论是否报错),都会自动调用__exit__()
方法。 -
它会再次获取当前时间,与开始时间
self.start
相减,输出耗时。
3. with Timer(): ...
这行执行过程如下:
步骤 | 发生了什么 |
---|---|
1 | 调用 Timer().__enter__() ,记录开始时间 |
2 | 执行 sum(range(1000000)) |
3 | 调用 Timer().__exit__() ,计算并输出耗时 |
输出示例(根据运行速度不同略有差异):
Time used: 0.04s
表示
sum(range(1000000))
执行用了大约 0.04 秒。
实战扩展:使用 with Timer() as t
你可以拓展它,让用户使用 as
获得 Timer 实例,从而访问时间值:
class Timer:def __enter__(self):import timeself.start = time.time()return selfdef __exit__(self, *args):self.end = time.time()self.interval = self.end - self.startprint(f"耗时:{self.interval:.4f} 秒")with Timer() as t:total = sum(range(1000000))# 你也可以访问 t.interval
小结:自定义计时器的常见用途
用途 | 示例 |
---|---|
性能测试 | 检查某段代码执行是否过慢 |
自动记录脚本运行耗时 | CLI 工具、批处理脚本 |
替代手动打 log 时间戳 | 简化日志输出逻辑 |
5️⃣ 常见问题与踩坑提醒
问题 | 原因 | 解决方案 |
---|---|---|
@staticmethod 无法访问类属性 | 它不绑定类 | 用 @classmethod |
__str__ 显示乱码 | 没有实现 | 添加 __str__ 方法 |
with open() 报错 | 文件不存在或编码问题 | 加入异常处理,检查路径 |
6️⃣ 拓展建议与进阶路径
-
探索 Python 的数据模型(Data Model)完整魔法方法表
-
阅读优秀开源项目源码(Flask, FastAPI)看他们如何组织类
-
深入理解组合 vs 继承设计模式
总结 & 实用建议
掌握面向对象,不仅仅是会写类,更重要的是如何设计结构和行为;掌握 Pythonic 写法,不只是记语法,而是理解 Python 的「表达哲学」。这两者结合,将让你写出既专业又易读的 Python 程序,迈向高级工程师之路。