核心理念:拥抱 Python 的哲学
在深入具体技巧前,理解 Python 的设计哲学至关重要,这体现在《The Zen of Python`import this``) 中:

- 优美胜于丑陋: 代码要易于阅读和欣赏。
- 明了胜于晦涩: 代码的意图应该清晰明了。
- 简洁胜于复杂: 用最简单的方式解决问题。
- 扁平胜于嵌套: 避免过深的代码嵌套。
- 可读性很重要: 这是 Python 的核心信条。
- 虽然实用性很重要,但不应损害可读性。
- 错误不应被静默忽略: 明确的错误处理机制。
- 除非你明确地想这样做。
- 面对模棱两可的请求,不要猜测: 澄清需求。
- 任何问题应该有一个(最好是只有一个)显而易见的解决方案。
- 虽然那起初可能并不明显,除非你是个荷兰人。 (指 Guido van Rossum)
- 现在总比没有好。
- 虽然永远比现在晚。
- 如果实现很难解释,那它是个坏点子。
- 如果实现很容易解释,那它可能是个好点子。
- 命名空间是个绝妙的想法,我们应该多加利用!
用 Pythonic 的方式思考
帮助你从其他语言(如 C++/Java)的思维转向 Python 的思维模式。
用列表推导和生成器表达式代替 map 和 filter
列表推导和生成器表达式更 Pythonic,可读性更强。
-
不推荐:
map和filter需要配合lambda使用,代码冗长。# 使用 map squares = list(map(lambda x: x**2, range(10))) # 使用 filter even_squares = list(filter(lambda x: x % 2 == 0, squares))
-
推荐: 列表推导式更直观。
# 使用列表推导 squares = [x**2 for x in range(10)] even_squares = [x for x in squares if x % 2 == 0]
-
注意: 当数据量很大时,应使用生成器表达式(用圆括号 ),它按需生成数据,节省内存。
# 生成器表达式 sum_of_squares = sum(x**2 for x in range(1000000))
用 enumerate 替代 range(len())
enumerate 能同时获取索引和值,代码更清晰。
-
不推荐:
for i in range(len(flavors)): print(f"{i + 1}: {flavors[i]}") -
推荐:
for i, flavor in enumerate(flavors, start=1): # start=1 可指定起始索引 print(f"{i}: {flavor}")
用 zip 同时迭代多个序列
zip 可以将多个序列“拉链”在一起,并行迭代。
-
不推荐:
names = ['Alice', 'Bob', 'Charlie'] ages = [30, 25, 35] for i in range(len(names)): print(names[i], ages[i]) -
推荐:
for name, age in zip(names, ages): print(name, age) -
注意: 在 Python 2 中,
zip会返回一个列表,消耗内存,在 Python 3 中,zip返回的是一个迭代器,按需生成,非常高效。
用 itemgetter 或 operator.attrgetter 排序复杂序列
当需要根据对象的某个属性或字典的某个键进行排序时,使用 sorted 的 key 参数。
-
不推荐: 使用
lambda,虽然简单但对于复杂属性访问可能效率稍低。class User: def __init__(self, user_id): self.user_id = user_id users = [User(23), User(3), User(99)] sorted_users = sorted(users, key=lambda u: u.user_id) -
推荐: 使用
operator.itemgetter或attrgetter,速度更快,意图更明确。from operator import itemgetter, attrgetter # 对于字典列表 rows = [ {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, ] sorted_rows = sorted(rows, key=itemgetter('fname', 'lname')) # 可按多个键排序 # 对于对象列表 sorted_users = sorted(users, key=attrgetter('user_id'))
函数
函数是 Python 的核心构建块。
用位置参数进行函数操作,关键字参数进行函数扩展
- 位置参数: 调用时必须按顺序提供,它们定义了函数的基本操作。
- 关键字参数: 调用时通过参数名指定,它们提供了灵活性,并使代码更易读,它们通常用于设置默认值。
def compare(a, b, *, key=None, reverse=False): # * 之后的参数必须为关键字参数
# key 参数用于指定比较的依据
# reverse 参数用于指定是否逆序
# ...
pass
compare(a, b) # 正确
compare(a, b, key=str.lower) # 正确
compare(a, b, True) # 错误 TypeError
不要使用可变默认参数
可变对象(如列表、字典)作为默认参数时,会在函数定义时被创建一次,而不是每次调用时都创建,这会导致意想不到的副作用。
-
不推荐:
def append_to_bad_list(item, bad_list=[]): bad_list.append(item) return bad_list print(append_to_bad_list(1)) # [1] print(append_to_bad_list(2)) # [1, 2] 意外的副作用! -
推荐: 使用
None作为默认值,在函数内部检查并初始化。def append_to_good_list(item, good_list=None): if good_list is None: good_list = [] good_list.append(item) return good_list print(append_to_good_list(1)) # [1] print(append_to_good_list(2)) # [2]
用 *args 和 **kwargs 处理可变参数
*args: 将位置参数收集到一个元组中。**kwargs: 将关键字参数收集到一个字典中。
这可以让你编写更通用的函数,特别是包装器或装饰器。
def log(message, *values, **kwargs):
if not values:
print(message)
else:
values_str = ', '.join(str(v) for v in values)
print(f"{message}: {values_str}")
# 可以处理其他关键字参数
if 'prefix' in kwargs:
print(f"Prefix: {kwargs['prefix']}")
log("My numbers are", 1, 2, 3)
log("Hi there")
log("Error", code=500, severity="high")
类与继承
Python 的面向对象设计有其独特之处。
用简单的公开属性代替复杂的 getter 和 setter
Python 鼓励直接访问对象的属性,而不是通过 get() 和 set() 方法,这被称为“我们都是成年人”的原则。
-
不推荐(Java/C++ 风格):
class OldResistor: def __init__(self, ohms): self._ohms = ohms def get_ohms(self): return self._ohms def set_ohms(self, ohms): self._ohms = ohms -
推荐(Pythonic 风格):
class Resistor: def __init__(self, ohms): self.ohms = ohms # 直接访问 self.voltage = 0 self.current = 0 r1 = Resistor(50e3) r1.ohms = 10e3 # 直接赋值 -
何时需要
@property? 当属性的获取或设置需要执行额外逻辑时(如验证、计算、日志记录)。class VoltageResistor(Resistor): def __init__(self, ohms): super().__init__(ohms) self._voltage = 0 @property def voltage(self): return self._voltage @voltage.setter def voltage(self, voltage): self._voltage = voltage self.current = self.voltage / self.ohms
用 __slots__ 优化内存占用
默认情况下,Python 的每个实例都有一个 __dict__ 字典来存储其属性,这很灵活但消耗内存。
__slots__ 告诉 Python 不要为实例创建 __dict__,而是预先定义好固定的属性列表,这可以:
- 大幅减少内存占用。
- 稍微加快属性访问速度。
- 防止用户动态添加新属性。
缺点:
- 无法为实例添加
__slots__中未定义的属性。 - 无法为类定义子类(除非子类也定义
__slots__)。
class Player:
__slots__ = ['name', 'score'] # 只允许这两个属性
def __init__(self, name, score):
self.name = name
self.score = score
重写 __eq__ 时也要重写 __hash__
__hash__ 方法用于将对象转换为哈希值,通常用于字典的键或集合的成员。
__eq__ 方法用于比较两个对象是否相等。
Python 的一个基本规则是:如果两个对象相等,它们的哈希值也必须相等。
-
不推荐: 如果只重写了
__eq__而没有重写__hash__,实例将无法被正确地放入集合或用作字典键。class Card: def __init__(self, rank, suit): self.rank = rank self.suit = suit def __eq__(self, other): return self.rank == other.rank and self.suit == other.suit # 未定义 __hash__,Python 会将其设为 None c1 = Card('7', 'hearts') c2 = Card('7', 'hearts') print(c1 == c2) # True print(hash(c1) == hash(c2)) # AttributeError: 'Card' object has no attribute '__hash__' -
推荐:
class Card: # ... __init__ 和 __eq__ 同上 ... def __hash__(self): return hash((self.rank, self.suit)) # 对一个元组求哈希
元类及更多高级特性
比较深入,但在某些特定场景下非常有用。
用 @classmethod 和 @staticmethod 区分类方法与静态方法
- 实例方法: 第一个参数是
self,用于操作实例状态。 @classmethod: 第一个参数是cls(类本身),用于操作类状态或创建实例的工厂方法。@staticmethod: 不接收self或cls参数,它只是一个在类中定义的普通函数,与类没有绑定关系。
class MyClass:
def instance_method(self):
print(f"Called instance method of {self}")
@classmethod
def class_method(cls):
print(f"Called class method of {cls}")
# 常用作工厂方法
# return cls()
@staticmethod
def static_method():
print("Called static method")
obj = MyClass()
obj.instance_method()
MyClass.class_method()
MyClass.static_method()
用 __getattr__, __getattribute__ 和 __setattr__ 实现属性访问的代理
这些是 Python 的“描述符协议”的一部分,用于精细控制属性的访问。
__getattr__(self, name): 当通过常规方式(如obj.name)找不到属性时被调用。__getattribute__(self, name): 每次通过常规方式访问属性时都会被调用(要小心无限递归)。__setattr__(self, name, value): 每次给属性赋值时被调用(也要小心无限递归)。
一个经典例子是代理模式:
class Delegator:
def __init__(self, delegate):
self.delegate = delegate
def __getattr__(self, name):
# 将属性访问转发给被代理的对象
return getattr(self.delegate, name)
def __setattr__(self, name, value):
# 将属性赋值转发给被代理的对象
if name == 'delegate':
super().__setattr__(name, value)
else:
setattr(self.delegate, name, value)
# 原始对象
original = {'a': 1, 'b': 2}
# 代理对象
proxy = Delegator(original)
print(proxy.a) # 输出 1
proxy.c = 3
print(original) # 输出 {'a': 1, 'b': 2, 'c': 3}
并发与并行
用 concurrent.futures 管理线程池和进程池
直接使用 threading 和 multiprocessing 模块比较底层。concurrent.futures 提供了更高层次的抽象,更易于使用。
- ThreadPoolExecutor: 适用于 I/O 密集型任务(如网络请求、文件读写)。
- ProcessPoolExecutor: 适用于 CPU 密集型任务(如数值计算)。
import concurrent.futures
import time
def do_task(n):
time.sleep(n)
return f"Task {n} done"
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务,返回一个 Future 对象
futures = [executor.submit(do_task, i) for i in (1, 2, 3)]
# 可以等待所有任务完成
concurrent.futures.wait(futures)
# 获取结果
for future in concurrent.futures.as_completed(futures):
print(future.result())
用 asyncio 处理高并发 I/O
asyncio 是 Python 的异步 I/O 框架,它使用单线程和事件循环来实现高并发,非常适合处理大量网络连接。
核心概念:
- 协程: 用
async def定义的函数。 - 事件循环:
asyncio的核心,负责运行和调度协程。 await: 在协程中使用,用于暂停当前协程的执行,并等待另一个协程完成,同时事件循环可以去执行其他任务。
import asyncio
async def fetch_data(url):
print(f"Fetching {url}...")
await asyncio.sleep(2) # 模拟网络 I/O 延迟
return f"Data from {url}"
async def main():
urls = ['url1', 'url2', 'url3']
tasks = [fetch_data(url) for url in urls]
# 并发执行所有任务
results = await asyncio.gather(*tasks)
for result in results:
print(result)
# 运行事件循环
asyncio.run(main())
元编程
用 __enter__ 和 __exit__ 实现上下文管理器
上下文管理器(通常与 with 语句一起使用)可以确保资源的正确获取和释放,即使发生异常也不例外。
-
手动实现:
class ManagedFile: def __init__(self, filename): self.filename = filename def __enter__(self): print("Opening file...") self.file = open(self.filename, 'w') return self.file # 返回给 as 关键字 def __exit__(self, exc_type, exc_val, exc_tb): if self.file: print("Closing file...") self.file.close() # 如果返回 True,则会抑制异常 return False with ManagedFile('hello.txt') as f: f.write('Hello, world!') -
推荐: 使用
contextlib.contextmanager装饰器,它可以用生成器更简单地实现上下文管理器。from contextlib import contextmanager @contextmanager def managed_file(filename): print("Opening file...") f = open(filename, 'w') try: yield f # yield 的值会赋给 as 关键字 finally: print("Closing file...") f.close() with managed_file('hello.txt') as f: f.write('Hello, world!')
《Effective Python》的核心思想是:
- 优先选择 Pythonic 的写法:列表推导、
enumerate、zip等,它们更清晰、更高效。 - 警惕语言的“陷阱”:如可变默认参数、
__eq__与__hash__的关系。 - 拥抱 Python 的动态特性:利用
*args,**kwargs,@property, 描述符等写出灵活的代码。 - 为正确性、健壮性和可维护性而设计:使用上下文管理器、考虑并发模型、编写清晰的文档字符串。
- 性能优化要适度:先保证代码清晰正确,再用性能分析工具找到瓶颈,然后进行针对性优化(如
__slots__、生成器表达式)。
这本书的价值在于它不仅告诉你“怎么做”,更解释了“为什么这么做”,帮助你真正理解 Python 的设计哲学和最佳实践。
