Python 以其简洁、优雅和高可读性赢得了全球开发者的青睐。然而,“能运行的代码”与“地道的 Python 代码(Pythonic Code)”之间,往往隔着一条由习惯、经验和社区共识构筑的鸿沟。所谓 Pythonic,并非指某种神秘技巧,而是指遵循 Python 设计哲学、充分利用语言特性、符合社区最佳实践的编码风格。它让代码不仅正确,而且清晰、高效、易于协作——正如《Python 之禅》所言:“优美胜于丑陋,明了胜于隐晦”。

本文档旨在系统梳理编写 Pythonic 代码的核心准则。我们将从 PEP 8 风格规范出发,深入探讨命名约定、表达式优化、异常处理、上下文管理等关键主题,并通过大量正反示例,揭示常见反模式(Anti-Pattern)的陷阱与替代方案。无论你是初学者还是资深开发者,都能从中获得提升代码质量的实用指南。


基础规范(杂项)

注释规范

所有的注释应仅在必要时使用,避免每行都加。注释应解释 “为什么”,而不是 “是什么”。

行内注释 (Inline Comments)

行内注释用 # 开头,前面至少留两个空格

x = x + 1  # 边界补偿 (OK)

块注释 (Block Comments)

块注释用于解释一段代码的逻辑。每行以 # 开头,与代码同级缩进

def count_score():
    # 计算用户活跃度得分:
    # - 登录次数 × 0.3
    # - 发帖数 × 0.5
    # - 点赞数 × 0.2
    score = logins * 0.3 + posts * 0.5 + likes * 0.2
    return score

文档字符串 (Docstrings, PEP-257)

文档字符串是模块、类、函数/方法的第一条语句,用三引号 """ 包裹,不是注释,而是可被 help() 或 Sphinx 等工具提取的元数据。文档字符串必须使用 """(双引号),即使单行。第一行是简短摘要(首字母大写,结尾不加句号,除非是完整句子)。空一行后写详细描述(如有)。模块、类、公共函数都应有 docstring。

简单函数示例(最小要求)

def add(a, b):
    """Return the sum of a and b."""
    return a + b

推荐:Google 风格(清晰易读)

def calculate_score(logins: int, posts: int) -> float:
    """计算用户活跃度得分。

    使用加权公式:logins * 0.3 + posts * 0.5

    Args:
        logins (int): 用户登录次数
        posts (int): 用户发帖数量

    Returns:
        float: 活跃度得分,范围 [0, ∞)

    Raises:
        ValueError: 如果输入为负数
    """
    if logins < 0 or posts < 0:
        raise ValueError("输入值不能为负")
    return logins * 0.3 + posts * 0.5

这其实就是 help() 函数的原理,我们也可以直接调用 <函数名>.__doc__ 来调用 Documentation Strings。


空格与空行

空格的使用

使用空格来表示缩进而不要用制表符 (Tab),和语法相关的每一层缩进都用 4 个空格来表示。这一点对习惯了其他编程语言的人来说简直觉得不可理喻,因为绝大多数的程序员都会用 Tab 来表示缩进,但是要知道 Python 并没有像 C/C++ 或 Java 那样的用花括号来构造一个代码块的语法,在 Python 中分支和循环结构都使用缩进来表示哪些代码属于同一个级别,鉴于此 Python 代码对缩进以及缩进宽度的依赖比其他很多语言都强得多。在不同的编辑器中,Tab 的宽度可能是 2、4 或 8 个字符,甚至是其他更离谱的值,用 Tab 来表示缩进对 Python 代码来说可能是一场灾难。

二元运算符的左右两侧应该保留一个空格,而且只要一个空格就好。

# 好的实践
sum = 1 + 1
# 不好的实践
sum = 1+1
sum = 1      +      1

空行的使用

函数和类的定义,代码前后都要用两个空行进行分隔。在同一个类中,各个方法之间应该用一个空行进行分隔。

import xxx


# 使用两个空行进行分隔
def add(a, b):
    return a + b


# 使用两个空行进行分隔
class Person:
    # 初始化方法,用于创建对象时设置初始属性
    def __init__(self, name, age):
        self.name = name  # 实例变量
        self.age = age    # 实例变量
    
    # Hello 方法
    def say_hello(self):
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")

单行代码换行处理

通常来说,python 每行的字符数不要超过 79 个字符。如果表达式因太长而占据了多行,除了首行之外的其余各行都应该在正常的缩进宽度上再加上 4 个空格。

括号/引号换行

一般本身就能界定范围的代码部分,都能够直接换行处理,譬如引号,括号。

# 这两种写法就完全等价
l = [1, 2, 3, 4, 5, 6]
l = [
    1, 2, 3,
    4, 5, 6
]

字符串换行

如果出现了需要换行的字符串,可以考虑使用转义符 \

# 对于字符串而言,以下两种写法完全等价
PI = "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211"

PI = "3.1415926535897932384626433832795028841\
971693993751058209749445923078164062862089986\
280348253421170679821480865132823066470938446\
095505822317253594081284811174502841027019385\
211" #换行要顶格

代码语句换行

如果是普通代码过长,也可以使用转义符 \

# 这两种写法等价
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 1
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = 3
c = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
print(c)

# 需要注意的是,除了首行之外的其余各行都应该在正常的缩进宽度上再加上 4 个空格
c = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + \
	bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
print(c)

但这种方式要避免 \ 将变量、引用方法等完整的单位切开。譬如以下写法都是错误的:

pri\
nt("Hello, world!")

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 1
print(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
      aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)

import 规范

在 Python 中,一个 .py 文件就是一个模块 (module),包含 .py 文件的文件夹就是一个包 (package)

\
+-- apple/               // package
|   +-- __init__.py
|   +-- apple1.py        // module
|   `-- apple2.py        // module
|
`-- banana               // package
    +-- __init__.py
    +-- banana1.py       // module
    `-- banana2.py       // module

如上文件树所示,一共有两个包 (apple、banana),包中需要有 __init__.py 文件来声明这个文件夹是一个包,只要文件夹里存在该文件,Python 就会认为这个文件夹是一个包(package),无论 __init__.py 文件中有没有内容。并且 __init__.py 文件在导入时会被执行一次,例如:我们在 __init__.py 中写一个 print 语句,在 import 该包的时候,print 语句会被执行一次(且仅执行一次),所以要尽量将 __init__.py 尽可能的进行封装成函数或类。

# Python User\admin\desktop\test_package\__init__.py
# -*- coding: utf-8 -*-
print('Hello,world!')

''' PS C:\Users\admin\desktop> python
>>> import test_package
Hello,world!
>>> import test_package
>>> import test_package
'''

现在已经知道了什么是 module 和 package。那么如何从 module 中 import 功能呢?首先,import 语句总是放在文件开头的地方。其次,在引入模块的时候,需要明确导入所需对象,使用最小化引入。

# Good Practice
from math import sqrt
# Bad Practice
import math
from math import *

如果有多个 import 语句,应该将其分为三部分,从上到下分别是:

  1. Python 标准模块
  2. 第三方模块
  3. 自定义模块

每个部分内部应该按照模块名称的字母表顺序来排列。

# Good Practice
# 1. 标准库 imports(按字母顺序)
import os
import sys
from datetime import datetime

# 2. 第三方库 imports(按字母顺序)
import requests
from flask import Flask

# 3. 自定义模块 imports(按字母顺序)
from myproject.models import User
from myproject.utils import helper


# Bad Practice
from myproject.utils import helper
import requests
from datetime import datetime
import os
from flask import Flask
from myproject.models import User
import sys

附:模块的特殊属性

  1. __name__:模块名。模块直接运行时为 __main__ 被导入时为模块名 (如 my_module)
  2. __file__:模块的绝对路径 (如 /home/user/my_module.py)
  3. __doc__:模块的文档字符串 (需在模块开头用 """ 定义)

字符串格式化

使用 f-string 格式化字符串,更清晰简洁。如 print(f"{variable}")

# Good Practice
# 简洁、直观、高效
print(f"Hello, {name}! You are {age} years old and live in {city}.")
print(f"Next year you'll be {age + 1}.")  # → 31
print(f"{name=}, {age=}")  # → name='Alice', age=30

# Bad Practice
print("Hello, {}! You are {} years old and live in {}.".format(name, age, city))
print("Hello, {name}! You are {age} years old and live in {city}.".format(
    name=name, age=age, city=city
))
next_year = age + 1
print("Next year you'll be {}.".format(next_year))

Python 在 3.14 版本引入了 t-string。作为 f-string 的 “进化版”,t-string 不仅解决了 f-string 的安全隐患,还为开发者提供了统一的模板处理范式,赋予了字符串处理前所未有的灵活性。


直接在代码中使用硬编码值

硬编码 (Hardcoding) 是指将‌任何具体的值、路径、参数或配置信息‌直接写死在源代码中,而不是通过变量、常量、配置文件或外部资源来管理。魔法数字 (Magic Numbers) 是硬编码的一种典型表现。魔法数字 (Magic Numbers) 是指的是‌在代码中直接出现、缺乏语义化解释的特定数值‌。这些数字本身无法直观地反映其在业务逻辑中的含义,必须通过阅读上下文才能推断,从而降低了代码的可读性和可维护性。

在代码中随意使用没有解释的硬编码数字,会让他人(甚至未来的自己)难以理解其含义,也难以修改。

# 典型使用场景如:状态码
if (status == 1):
    pass

这里有两种优化方式:

方法一:在文件顶部进行常量定义

from enum import Enum


class Status(Enum):
    REJECTED = 0     # 被拒绝(替代 BLUE 这种不具语义的名称)
    APPROVED = 1     # 已批准 / 可访问(对应你原来的 Access)
    PENDING = 2      # 待处理

if (status == Status.APPROVED.value):
    pass

方法二:调用配置文件

# config.ini
[Status]
REJECTED = 0     # 被拒绝(替代 BLUE 这种不具语义的名称)
APPROVED = 1     # 已批准 / 可访问(对应你原来的 Access)
PENDING = 2      # 待处理

.py 文件中调用 config.ini

# main.py
import configparser


config = configparser.ConfigParser()
config.read('config.ini', encoding='utf-8')
# 示例:获取一个值
approved = config.get('Status', 'APPROVED')
if (status == approved):
    pass

标识符命名规范

命名的第一原则:采用有意义的命名,见名知义(如 job_list 而非 jl),尤其要尽量避免使用单字母的变量名,除非用在小范围循环中。

PEP 8 倡导用不同的命名风格来命名 Python 中不同的标识符,以便在阅读代码时能够通过标识符的名称来确定该标识符在 Python 中扮演了怎样的角色(在这一点上,Python 自己的内置模块以及某些第三方模块都做得并不是很好)。

  1. 变量、函数和属性应该使用小写字母来拼写,如果有多个单词就使用下划线进行连接。

    # 变量
    user_name = "Alice"
    max_retry_count = 3
    # 函数
    def calculate_total_price(item_price, tax_rate):
        return item_price * (1 + tax_rate)
    # 普通属性(在类中)
    class Product:
        def __init__(self, name, price):
            self.product_name = name   # 属性也用 snake_case
            self.unit_price = price
    
  2. 类中受保护的实例属性,应该以一个下划线开头。(单下划线是一种约定,表示 “不建议外部直接访问”,但 Python 不会阻止)

    class BankAccount:
        def __init__(self, balance):
            self._balance = balance  # 受保护:提示“内部使用”,但外部仍可访问
    
        def _update_balance(self, amount):
            self._balance += amount
    
  3. 类中私有的实例属性,应该以两个下划线开头。(双下划线触发 name mangling,防止子类意外覆盖,实现 “强私有” 语义)

    class BankAccount:
        def __init__(self, pin):
            self.__pin = pin  # 真正的“私有”:会被名称修饰(name mangling)
    
        def verify_pin(self, input_pin):
            return self.__pin == input_pin
    
    # 使用
    acc = BankAccount("1234")
    # print(acc.__pin)      # ❌ AttributeError!
    print(acc.verify_pin("1234"))  # ✅ True
    # 实际存储为 _BankAccount__pin(可通过 acc._BankAccount__pin 访问,但不推荐)
    
  4. 类和异常的命名,应该每个单词首字母大写。

    # 类
    class UserProfile:
        pass
    
    class HTTPClient:
        pass
    
    # 自定义异常(也属于类)
    class InvalidEmailError(Exception):
        """当邮箱格式无效时抛出"""
        pass
    
    # 使用
    try:
        raise InvalidEmailError("邮箱格式错误")
    except InvalidEmailError as e:
        print(e)
    
  5. 模块级别的常量,应该采用全大写字母,如果有多个单词就用下划线进行连接。

    # config.py 或主模块顶部
    import math
    
    MAX_CONNECTIONS = 100
    DEFAULT_TIMEOUT_SEC = 30
    PI = math.pi  # 虽然 math.pi 已存在,但自定义常量也这样写
    
    # 使用
    if timeout > DEFAULT_TIMEOUT_SEC:
        print("超时时间过长")
    

    仅限模块级别(全局)常量。类内部的常量通常也遵循此规则,但放在类内:

    class Config:
        MAX_RETRY = 5
    
  6. 类的实例方法,应该把第一个参数命名为 self 以表示对象自身。(self 不是关键字,但必须作为第一个参数,且强烈建议命名为 self,这是 PEP 8 的强制要求)

    class Dog:
        def __init__(self, name):
            self.name = name
    
        def bark(self):  # 第一个参数必须是 self
            print(f"{self.name} says woof!")
    
    # 使用
    my_dog = Dog("Buddy")
    my_dog.bark()  # 输出: Buddy says woof!
    
  7. 类的类方法,应该把第一个参数命名为 cls 以表示该类自身。( cls 表示当前类或子类,用于访问类属性或调用构造函数)

    class Person:
        species = "Homo sapiens"
    
        def __init__(self, name):
            self.name = name
    
        @classmethod
        def get_species(cls):  # 第一个参数是 cls
            return cls.species
    
        @classmethod
        def create_anonymous(cls):
            return cls("Anonymous")  # 用 cls 调用构造函数,支持继承
    
    # 使用
    print(Person.get_species())  # Homo sapiens
    anon = Person.create_anonymous()
    print(anon.name)  # Anonymous
    
  8. 不使用系统保留字作为变量名。(可使用 keyword.iskeyword(s)keyword.issoftkeyword(s) 进行查询)

    False          await          else           import         pass
    None           break          except         in             raise
    True           class          finally        is             return
    and            continue       for            lambda         try
    as             def            from           nonlocal       while
    assert         del            global         not            with
    async          elif           if             or             yield
    

不同命名表示不同的变量类型,总结如下:

  1. 下划线命名(_):用于内部或私有变量。
  2. 大写字母开头的单词(PascalCase):用于类名和异常类型。
  3. 小写字母开头的单词(snake_case):用于函数名和全局变量。
  4. 单下划线(_)开头:表示私有变量或方法。
  5. 双下划线(__):系统定义的特殊变量或方法。
  6. 大写字母(CAPS):常量。
  7. 不使用系统保留字作为变量名

变量规范

变量类型的声明

Python 是一种动态类型语言,你不需要显式声明变量的类型。但是,你可以使用类型注解 (Type Hints) 来提供给解释器或 IDE 关于变量预期类型的提示。使用类型注解也有助于提前发现潜在错误,提升代码健壮性。

def greet(name: str) -> str:
    return f"Hello, {name}!"

message = greet("Alice")

在这个例子中,name: str 是一个类型注解,表明 name 参数应该是一个字符串类型,而 -> str 表示这个函数返回一个字符串。


多重赋值

给多个变量赋值的方法。

a, b = 1, 2
# 完全等价于
a = 1
b = 2

变量值互换

两个变量互换值的简写:

a, b = b, a

使用下划线 _ 作为占位变量名

示例一:忽略 for 循环中的索引或值。当你只关心循环次数,不关心具体元素时:

# 只想循环 5 次,不关心 i 的值
for _ in range(5):
    print("Hello")

示例二:解包 (unpacking) 时忽略部分值

# 解包元组,只关心第一个和最后一个
first, _, last = (10, 20, 30)
print(first, last)  # 输出: 10 30

逻辑判断

采用内联形式的否定词

采用内联形式的否定词,而不要把否定词放在整个表达式的前面。

if not a is b  # Bad Practice
if a is not b  # Good Practice

条件判断为 Ture

python 中只要判断条件的部分不为 0,在做条件判断时就会被判断为 Ture。因此,不要用检查长度的方式来判断字符串、列表等是否为 None 或者没有元素,应该用 if not x 这样的写法来检查它。

def minus_one(x):
    x -= 1
    return x

# Good Practice
while num:
    num = num if num else minus_one(num)
    print(num)

# Bad Practice
while num is not None:
    num = num if num else minus_one(num)
    print(num)

当 num 取值为 3, 2, 1 时 while 循环以及 if 都为 Ture,程序继续运行。利用这个原理,我们可以用文件是否获取等条件作为逻辑判断的依据。


if 深度嵌套

多层 if 嵌套会使代码难以阅读和维护,增加引入 bug 的风险。应尽量使用逻辑运算符(如 and)将条件扁平化

# Good Practice
if condition1 and condition2 and condition3:
    # 执行操作

# Bad Practice
if condition1:
    if condition2:
        if condition3:
            # 执行操作

三元运算符

三元运算符能避免冗长的 if-else 代码块,在逻辑简单、无副作用的场景下能提升代码的简洁性、可读性和表达力。譬如用于赋值或返回值 (纯表达式):

# Good Practice
def sum(item):
    head, *tail = items
    return head + sum(tail) if tail else head

# Bad Practice
def sum(item):
    head, *tail = items
    if tail:
        return head + sum(tail)
    else:
        return head

三元运算符的语法为:成立时的返回值 if 判断条件 else 不成立时的返回值


类型检查方式不当

使用 type(variable) == type 进行类型检查是一种反模式,它无法正确处理子类。相比之下,isinstance(variable, int) 更灵活,能兼容继承关系。

# Good Practice
if isinstance(variable, int):
    pass
# Bad Practice
if type(variable) == int:
    pass

始终将控制结构体独立成块

即使 iffortry/except 等控制结构的代码块中只有一行代码,也不要把它和关键字写在同一行,而应该换行并缩进,以提高代码的可读性和可维护性。

# Good Practice
if x > 0:
    print("Positive")
for i in range(5):
    print(i)
try:
    risky_operation()
except ValueError:
    handle_error()

# Bad Practice
if x > 0: print("Positive")
for i in range(5): print(i)
try: risky_operation() 
except ValueError: handle_error()

函数规范

避免函数返回不同类型

一个函数应尽可能始终返回相同的数据类型(如总是返回 int、总是返回 list,或总是返回 None),而不是有时返回字符串、有时返回数字、有时返回布尔值或 None。这样可以提高代码的可预测性、可读性和健壮性,减少调用方出错的风险。

# Good Practice
def get_user_age(user_id) -> int:
    if not user_id or user_id not in DATABASE:
        return -1
    return DATABASE[user_id]["age"]  # 总是 int

# Bad Practice
def get_user_age(user_id):
    if not user_id:
        return None          # ← 返回 None
    if user_id == 123:
        return 25            # ← 返回 int
    return "unknown"         # ← 返回 str!

唯一允许的特例是,允许返回 None 用于错误处理。

def get_user_age(user_id) -> Optional[int]:
    if not user_id or user_id not in DATABASE:
        return None   # 允许 None,但其他情况一定是 int
    return DATABASE[user_id]["age"]  # 总是 int

函数变量类型注释

在 Python 中,可以使用类型注释来指定函数参数和返回值的预期类型。这可以帮助代码编辑器提供更好的支持,并可以作为文档。

def process_data(input_list: list[str]) -> dict[str, int]:
    """接收字符串列表,返回每个字符串及其长度的字典。"""
    output_dict = {}
    for item in input_list:
        output_dict[item] = len(item)
    return output_dict

在这个例子中,process_data 函数的参数 input_list 被注释为 List[str] 表示它应该是一个字符串元素的列表,而函数的返回值被注释为 dict[str, int] 表示它返回的是一个字典。

请注意,类型注释并不会在运行时强制执行类型,它仅用于静态类型检查。如果需要在运行时强制类型,你应该使用类如 mypy 这样的静态类型检查器来检查代码的类型安全性。


不要修改函数的传入参数(尤其是可变对象)

在函数内部直接修改传入的参数 (比如 Pandas DataFrame) 是非常糟糕的做法,因为它会产生意外的副作用,破坏数据完整性,且难以追踪和调试。正确做法是复制一份数据,修改后返回新对象。从而实现更可控、模块化的代码结构。

import pandas as pd

# Good Practice
def modify_database(data):
    # 复制后再修改
    modified_data = data.copy()
    modified_data['age'] = modified_data['age'] + 1
    return modified_data

# Bad Practice
def modify_database(data):
    # 直接修改原始 DataFrame
    data['age'] = data['age'] + 1

提示:Pandas 中的 inplace=True 参数同样存在此问题,应谨慎使用。


一个函数只负责一个职责

将多个职责塞进一个庞大函数中是典型的反模式,会导致代码难以测试、维护和理解。应将功能拆分为多个小函数 (单体函数, Monolithic Functions),每个只负责单一任务。

# Good Practice
def process_payment(user_id, amount):
    """仅处理支付逻辑"""
    if not is_valid_user(user_id):
        return "Invalid user."
    if not deduct_amount_from_account(user_id, amount):
        return "Insufficient funds."
    if not process_payment_gateway(amount):
        return "Payment processing failed."
    return "Payment successful."

def send_payment_notification(user_id, amount):
    """单独负责发送通知"""
    send_notification(user_id, f"Payment of {amount} processed successfully.")

def make_payment(user_id, amount):
    """主流程:处理支付并通知"""
    result = process_payment(user_id, amount)
    if result == "Payment successful.":
        send_payment_notification(user_id, amount)
    return result

# Bad Practice
def process_payment(user_id, amount):
    """处理用户支付并发送通知"""
    # 步骤1:验证用户
    if not is_valid_user(user_id):
        return "Invalid user."
    # 步骤2:扣款
    if not deduct_amount_from_account(user_id, amount):
        return "Insufficient funds."
    # 步骤3:调用支付网关
    if not process_payment_gateway(amount):
        return "Payment processing failed."
    # 步骤4:发送通知
    send_payment_notification(user_id, amount)
    return "Payment successful."

异常处理

写出 Pythonic 的异常处理,核心在于:只捕获你明确知道如何处理的异常,避免“吞掉”错误,优先使用 EAFP (Easier to Ask for Forgiveness than Permission) 风格。

#  Good Practice
def get_user_email_good(user_data):
    try:
        return user_data["profile"]["email"]
    except KeyError as e:
        # 只捕获我们预期的 KeyError
        missing_key = e.args[0]
        raise ValueError(f"Missing required field: {missing_key}") from e
    except TypeError:
        # 处理 user_data 不是 dict 的情况
        raise TypeError("Expected user_data to be a dictionary") from None

# Bad Practice
def get_user_email_bad(user_data):
    try:
        return user_data["profile"]["email"]
    except:  # 捕获所有异常(包括 KeyboardInterrupt、SystemExit!)
        return None  # 静默返回 None,不记录、不提示

高级用法

使用 in 在列表(list)中搜索元素

in 来判断某个词是否存在于一个 list 中本身其实没有太大的问题,但如果改用 set(集合),速度会快得多。举个例子:

# Good Practice
list_of_jobs = {"Machine learning", "Data Science", "Developer", "CTO", "Writer", "Product Manager"}
found = "CEO" in list_of_jobs
print(found)

# Bad Practice
list_of_jobs = ["Machine learning", "Data Science", "Developer", "CTO", "Writer", "Product Manager"]
found = "CEO" in list_of_jobs
print(found)

在集合 (set) 中搜索比在列表 (list) 中更快,因为集合基于哈希表实现,平均时间复杂度为 O(1)


列表推导式

Python 中的推导式 (Comprehensions) 是一种简洁、高效且 Pythonic 的方式,用于创建列表、字典、集合,甚至生成器。它们将循环和条件逻辑压缩成一行表达式,既提升性能又增强可读性 (在合理使用时)。其中最常用的是列表推导式。

语法:[expression for item in iterable if condition]

使用列表推导式 (list comprehension) 初始化列表,不仅更简洁,而且运行更快。

# Good Practice
result = [i * 2 for i in range(10)]

# Bad Practice
result = []
for i in range(10):
    result.append(i * 2)

但有时候会牺牲一定的可读性。尤其是在迭代逻辑比较复杂的时候(可读性吊差)

result = [x * y for x in range(10) if x % 2 == 0 for y in range(5) if y > 1 and x + y < 8]

除了列表推导式以外,还有其他推导式,他们的优缺点几乎是完全一样的。

  1. 字典推导式 (Dict Comprehension):{key_expr: value_expr for item in iterable if condition}
  2. 集合推导式 (Set Comprehension):{expression for item in iterable if condition}
  3. 生成器表达式 (Generator Expression):(expression for item in iterable if condition)

装饰器

装饰器 (Decorator) 在不修改原函数代码的前提下,实现了关注点分离、代码复用和功能增强,符合“开闭原则”(Open/Closed Principle)。

使用装饰器封装日志逻辑

# Good Practice
import functools
from datetime import datetime

def log_calls(func):
    """装饰器:自动记录函数调用信息"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 统一日志逻辑
        start = datetime.now()
        print(f"[LOG] {start}: Calling {func.__name__} with args={args}, kwargs={kwargs}")
        
        try:
            result = func(*args, **kwargs)
            end = datetime.now()
            print(f"[LOG] {end}: {func.__name__} completed successfully")
            return result
        except Exception as e:
            end = datetime.now()
            print(f"[LOG] {end}: {func.__name__} failed with error: {e}")
            raise
    return wrapper

# 应用装饰器 —— 业务函数干净简洁!
@log_calls
def add_user(name: str, email: str):
    if not name or not email:
        raise ValueError("Name and email required")
    print(f"User {name} added.")

@log_calls
def delete_post(post_id: int):
    if post_id <= 0:
        raise ValueError("Invalid post ID")
    print(f"Post {post_id} deleted.")

在每个函数内部重复写日志逻辑

# Bad Practice
import time
from datetime import datetime

def add_user(name: str, email: str):
    # 1. 手动写日志(重复代码)
    print(f"[LOG] {datetime.now()}: Calling add_user with args=({name}, {email})")
    
    # 2. 核心业务逻辑(被日志干扰)
    if not name or not email:
        raise ValueError("Name and email required")
    print(f"User {name} added.")
    
    # 3. 又要写返回日志?(更乱)
    print(f"[LOG] {datetime.now()}: add_user completed")

def delete_post(post_id: int):
    # 4. 再次重复日志代码!
    print(f"[LOG] {datetime.now()}: Calling delete_post with args=({post_id})")
    
    if post_id <= 0:
        raise ValueError("Invalid post ID")
    print(f"Post {post_id} deleted.")
    
    print(f"[LOG] {datetime.now()}: delete_post completed")

使用上下文管理器(with)

Python 的 with 语句实现了上下文管理器 (Context Managers),能安全、可靠地管理资源(如文件句柄、网络连接、锁等)。手动打开/关闭文件容易导致资源泄漏或异常未处理。

# Good Practice
with open("myfile.txt", "r") as file:
    'handle the file'
    pass  # 文件会自动关闭

# Bad Practice
file = open("myfile.txt", "r")
'handle the file'
file.close()

参考文档