PyPython 的技术记录
A PYTHON NOTEBOOK

把问题解决,
把过程记下来。

关于 Python 的学习、尝试与日常实践。
从一小段代码开始,积累可以再次使用的经验。

阅读技术笔记 ↓
● ● ● notebook.py
# 一次学习,一点积累
from pathlib import Path

notebook = Path("今天的收获.txt")
notebook.write_text(
    "让代码更清晰一点。",
    encoding="utf-8"
)

技术笔记

11 篇记录 · 持续积累
基础机制

Python 迭代器协议与自定义遍历逻辑

深入解析 Python 中 iter() 和 next() 函数背后的迭代器协议,探讨如何通过实现 __iter__ 和 __next__ 方法来自定义对象的遍历行为,理解可迭代对象与迭代器的区别。

阅读全文

在 Python 的日常开发中,我们频繁使用 for 循环来遍历列表、字典或字符串。这种看似简单的语法背后,隐藏着 Python 核心设计哲学之一:迭代器协议。许多初学者容易混淆“可迭代对象”(Iterable)与“迭代器”(Iterator)这两个概念,但理解它们的差异对于编写高效且符合 Python 风格的代码至关重要。

首先,我们需要明确什么是可迭代对象。任何实现了 __iter__ 方法的对象都是可迭代的。这意味着该对象可以返回一个迭代器。常见的内置类型如 list、tuple、str 和 dict 都是可迭代对象。当你执行 for item in my_list: 时,Python 解释器实际上是在幕后调用 iter(my_list) 来获取一个迭代器对象,然后反复调用 next() 方法来获取下一个元素,直到引发 StopIteration 异常为止。

相比之下,迭代器是一个状态对象,它记住了当前遍历的位置。迭代器必须同时实现 __iter__ 和 __next__ 两个魔术方法。__iter__ 方法通常返回 self,这使得迭代器本身也是可迭代对象;而 __next__ 方法则负责返回序列中的下一个项目,并在没有更多项目时抛出 StopIteration 异常。这一机制被称为“拉取式”模型,因为消费端主动请求数据,而非生产端推送数据。

为什么要自定义迭代器?标准库提供了丰富的工具,但在某些特定场景下,自定义迭代器能带来极大的便利。例如,当你处理一个非线性的数据结构,或者需要延迟计算以节省内存时,自定义迭代器允许你封装复杂的遍历逻辑,对外提供简洁的接口。此外,通过实现迭代器协议,你的自定义类可以直接被用于 for 循环、解包操作以及传递给 sum()、list() 等内置函数,无需额外编写适配代码。

值得注意的是,迭代器是一次性使用的。一旦迭代完成,除非重新创建新的迭代器实例,否则无法再次从头开始遍历。这与可迭代对象不同,后者每次调用 iter() 都会生成一个新的独立迭代器。因此,在设计 API 时,如果希望用户能够多次遍历同一数据源,应确保 __iter__ 方法每次都能返回一个新的迭代器实例,而不是复用同一个内部状态。

掌握迭代器协议不仅有助于理解 Python 底层运作,还能提升代码的可读性和复用性。它体现了 Python “鸭子类型”的思想:只要对象表现得像迭代器,就可以被当作迭代器使用。在实际项目中,合理运用这一机制可以避免冗长的索引循环,使代码更加优雅和直观。

class ReverseIterator:
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index -= 1
        return self.data[self.index]

# 使用示例
custom_iter = ReverseIterator([1, 2, 3, 4])
for num in custom_iter:
    print(num)

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

基础语法

Python 字符串格式化进阶:f-string 的优雅表达

介绍 Python 3.6+ 引入的 f-string 特性,对比传统格式化方法,阐述其在代码可读性与执行效率上的优势,并展示常用格式化技巧。

阅读全文

在 Python 编程中,字符串格式化是处理文本输出的核心需求之一。早期开发者主要依赖百分号操作符和 str.format 方法。百分号操作符虽然简洁,但在处理复杂逻辑时容易出错且可读性较差。str.format 方法通过位置参数或关键字参数解决了部分问题,但代码往往显得冗长,尤其是在需要嵌入变量计算结果时。

随着 Python 3.6 的发布,f-string(格式化字符串字面值)成为官方推荐的字符串格式化方式。f-string 允许在字符串前缀加字母 f,并在花括号内直接嵌入表达式。这种机制不仅简化了语法,更显著提升了代码的可读性。例如,当需要将变量值与静态文本结合时,f-string 让开发者无需额外调用函数或传递参数,直接在字符串内部完成插值。

除了基本的变量插入,f-string 还支持复杂的格式化规范。通过在冒号后指定格式说明符,可以轻松控制数字精度、对齐方式、填充字符以及日期时间格式。这种声明式的写法使得输出布局更加直观。更重要的是,f-string 在编译阶段将表达式转换为字节码指令,相比运行时解析字符串的方法,其执行效率更高,特别是在高频循环场景中表现优异。

然而,使用 f-string 也需注意边界情况。由于花括号被解释为特殊字符,若需在字符串中显示普通的花括号,必须使用双花括号进行转义。此外,嵌套引号的处理需谨慎,建议外层使用单引号,内层使用双引号,以避免语法冲突。掌握 f-string 不仅能提升代码美感,更是编写现代化、高性能 Python 程序的重要基石。它代表了 Python 社区对“显式优于隐式”和“简单优于复杂”哲学的新诠释,极大地降低了新手入门门槛,同时满足了资深开发者对效率的追求。

import datetime

def format_demo():
    name = "Alice"
    score = 95.4567
    now = datetime.datetime.now()
    
    # 基本变量插入
    print(f"Hello, {name}!")
    
    # 数值格式化:保留两位小数,右对齐宽度10
    print(f"Score: {score:>10.2f}")
    
    # 日期格式化
    print(f"Current time: {now:%Y-%m-%d %H:%M:%S}")
    
    # 表达式求值
    print(f"Next year age: {25 + 1}")

if __name__ == "__main__":
    format_demo()

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

基础语法

Python 列表推导式:简洁与效率的平衡

本文深入解析 Python 列表推导式的语法结构、执行原理及其在数据处理中的优势,同时探讨其在可读性与性能之间的权衡。

阅读全文

列表推导式是 Python 中一种极具代表性的语法糖,它允许开发者用一行代码完成传统循环加条件判断的功能。其基本形式为 [expression for item in iterable if condition]。这种写法不仅减少了样板代码,还使意图更加清晰。例如,生成平方数列表时,[x*x for x in range(10)] 比显式初始化空列表再追加元素要直观得多。

从底层实现来看,列表推导式在字节码层面通常比普通 for 循环更高效。这是因为推导式内部直接调用 LIST_APPEND 操作,避免了属性查找和函数调用的开销。此外,推导式拥有独立的作用域,变量不会泄漏到外部命名空间,这在一定程度上降低了意外覆盖变量的风险。

然而,过度使用或复杂化推导式会损害代码的可读性。当嵌套层级超过两层,或者表达式中包含复杂的逻辑判断时,建议退回传统的 for 循环。Python 之禅强调“简单优于复杂”,推导式应服务于简洁表达,而非炫技。在处理大型数据集时,若内存受限,应考虑使用生成器表达式替代列表推导式,以惰性求值的方式节省资源。掌握何时使用、何时避免推导式,是编写 Pythonic 代码的关键技能之一。

squares = [x ** 2 for x in range(10) if x % 2 == 0]
print(squares)

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

基础语法

Python 上下文管理器与资源自动释放

介绍 with 语句的工作原理,讲解如何利用上下文管理器确保文件、网络连接等资源在操作结束后被正确关闭,避免内存泄漏和句柄耗尽。

阅读全文

在 Python 编程中,资源的获取与释放是保证程序稳定运行的关键环节。无论是打开文件读取数据、建立数据库连接,还是锁定线程互斥量,这些操作都遵循“获取-使用-释放”的模式。如果开发者忘记手动释放资源,可能导致文件描述符泄露、死锁或内存占用持续增加。为了解决这一痛点,Python 引入了上下文管理器机制,其核心语法是 with 语句。

with 语句的设计初衷是让代码更简洁且更安全。它依赖于两个特殊方法:__enter__ 和 __exit__。当程序进入 with 块时,会自动调用对象的 __enter__ 方法,并将返回值绑定到 as 后面的变量;当代码块执行完毕(无论是因为正常结束还是抛出异常),解释器都会自动调用 __exit__ 方法。这意味着,即使在使用资源的过程中发生了未捕获的异常,资源清理逻辑依然会被执行,从而保证了程序的健壮性。

对于初学者而言,理解 with 语句比手动编写 try-finally 结构更为直观。例如,在处理文件 IO 时,传统写法需要在 finally 块中显式调用 file.close(),这不仅增加了代码冗余,还容易因疏忽而遗漏。使用 with open(...) as f: 后,文件对象会在缩进块结束时自动关闭,无需额外指令。这种模式不仅适用于内置的文件对象,也广泛存在于标准库的其他模块中,如 threading.Lock、sqlite3.Connection 等。

此外,用户还可以自定义上下文管理器。通过实现一个包含 __enter__ 和 __exit__ 方法的类,或者利用 contextlib 模块中的 @contextmanager 装饰器将生成器函数转换为上下文管理器,可以封装复杂的资源管理逻辑。这种灵活性使得开发者能够将通用的清理规则抽象出来,复用于不同的业务场景。掌握上下文管理器不仅是写出 Pythonic 代码的标志,更是构建高可靠性应用程序的基础技能。在日常开发中,应养成优先使用 with 语句的习惯,以最大限度地减少资源管理错误。

import contextlib

class ManagedResource:
    def __enter__(self):
        print("资源已获取")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("资源已释放")
        return False

# 模拟复杂逻辑的资源管理
@contextlib.contextmanager
def managed_connection():
    print("建立连接")
    try:
        yield "connection_object"
    finally:
        print("断开连接")

if __name__ == "__main__":
    with ManagedResource() as res:
        print(f"正在使用: {res}")

    with managed_connection() as conn:
        print(f"正在处理数据: {conn}")

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

基础语法

Python 异常处理机制详解

本文深入解析 Python 中 try-except-else-finally 结构的执行逻辑与最佳实践,帮助开发者编写健壮且易于维护的代码。

阅读全文

在 Python 编程中,异常处理是确保程序稳定运行的核心机制之一。许多初学者往往忽视异常处理的重要性,或者仅仅使用最宽泛的 except Exception 来捕获所有错误,这种做法虽然能防止程序崩溃,却掩盖了潜在的逻辑错误,使得调试变得极其困难。正确的做法应当是精确地捕获预期内的特定异常类型。

Python 的异常处理结构主要由 try、except、else 和 finally 四个关键字组成。try 块包含可能引发异常的代码。如果 try 块中的代码正常执行完毕,没有抛出任何异常,那么 else 块中的代码将会被执行。这里有一个常见的误区:很多人将本应放在 else 块的代码直接写在 try 块之后。实际上,将不会引发该特定异常的后续逻辑放入 else 块,可以更清晰地界定哪些代码是受保护的,哪些是依赖保护成功的。例如,文件读取成功后进行的业务逻辑处理,更适合放在 else 中,因为如果这部分逻辑出错,不应该被前面的文件读取异常处理器所捕获。

finally 块则用于定义无论是否发生异常都必须执行的清理操作,比如关闭网络连接、释放资源或记录日志。需要注意的是,即使 try 或 except 块中包含 return 语句,finally 块依然会在函数返回之前执行。这种确定性对于资源管理至关重要。此外,raise 关键字允许程序员主动抛出异常,这在参数校验或自定义业务规则检查时非常有用。通过 raise ValueError("参数不能为空") 这样的语句,可以将具体的错误信息传递给调用者,增强代码的可读性和可维护性。

在实际开发中,遵循“显式优于隐式”的原则,尽量让异常处理粒度细化。不要为了省事而吞掉异常(即捕获后不做任何处理),这被称为“静默失败”,是生产环境中的重大隐患。相反,应当记录异常堆栈信息,以便后续排查问题。合理使用上下文管理器(with 语句)也可以简化部分资源管理的异常处理流程,使代码更加简洁优雅。掌握这些细节,能让你的 Python 代码从“能跑”提升到“稳健”。

def divide_numbers(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("错误:除数不能为零")
        return None
    except TypeError:
        print("错误:输入必须是数字")
        return None
    else:
        print(f"计算成功:{a} / {b} = {result}")
        return result
    finally:
        print("除法运算尝试结束")

divide_numbers(10, 2)
divide_numbers(10, 0)

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

基础语法

掌握 Python 集合与字典的高效操作

本文深入探讨 Python 中集合(set)与字典(dict)的核心特性及最佳实践。重点解析哈希原理、时间复杂度优势以及常见陷阱,帮助开发者写出更简洁高效的代码。

阅读全文

在 Python 的数据结构体系中,集合与字典因其基于哈希表的实现机制而具备极高的查询效率,是处理去重、映射关系和快速查找的首选工具。理解其底层逻辑对于编写高性能代码至关重要。

首先来看集合。集合是无序且元素唯一的容器,这一特性使其成为数据去重的天然利器。当我们需要从一个大型列表中剔除重复项时,将列表转换为集合再转回列表,往往比手动遍历判断要简洁得多。更重要的是,集合支持标准的数学运算,如并集、交集、差集和对称差集。这些运算不仅语义清晰,而且在底层由 C 语言优化实现,执行速度远超通过循环嵌套实现的类似逻辑。需要注意的是,集合中的元素必须是可哈希的,这意味着列表、字典等可变对象不能作为集合的元素,只有元组、字符串、数字等不可变类型才能加入其中。

接着是字典,它是键值对映射结构的典型代表。字典的核心优势在于平均情况下 O(1) 的时间复杂度进行键查找。然而,这种高效依赖于良好的哈希分布。在实际开发中,频繁出现的 KeyError 异常通常源于对键存在性的误判。虽然可以使用 try-except 块来捕获异常,但在大多数业务场景下,使用 in 关键字预先检查或采用 get 方法提供默认值,能使代码逻辑更加平滑且易于维护。此外,Python 3.7 之后,字典保证了插入顺序,这使得它在某些轻量级缓存或配置存储场景中,可以替代有序字典,简化依赖。

尽管两者性能优越,但也存在内存开销较大的缺点。由于需要预留空间以维持哈希表的低负载因子,集合和字典占用的内存通常高于同等数量的列表。因此,在处理海量简单数据且无需随机访问的场景下,列表可能仍是更经济的选择。同时,避免在迭代过程中修改字典或集合的大小,这会导致运行时错误。若需删除元素,应先收集待删除的键或元素,迭代结束后统一移除。

最后,字典推导式是构建复杂映射关系的优雅方式。结合条件过滤,它能在单行代码内完成数据清洗与转换,极大提升了代码的可读性与表达力。掌握这些细节,能让开发者在面对数据处理任务时,做出更合理的技术选型。

import sys

def demonstrate_set_dict():
    # 使用集合去重
    raw_data = [1, 2, 2, 3, 4, 4, 5]
    unique_data = set(raw_data)
    print(f"Unique elements: {unique_data}")
    
    # 集合运算:找出两个列表中共有的元素
    list_a = [1, 2, 3, 4]
    list_b = [3, 4, 5, 6]
    common = set(list_a) & set(list_b)
    print(f"Common elements: {common}")
    
    # 字典的安全访问
    scores = {"Alice": 90, "Bob": 85}
    # 使用 get 方法避免 KeyError,并提供默认值
    charlie_score = scores.get("Charlie", 0)
    print(f"Charlie's score: {charlie_score}")
    
    # 字典推导式:筛选出分数大于 88 的学生
    high_scorers = {name: score for name, score in scores.items() if score > 88}
    print(f"High scorers: {high_scorers}")

if __name__ == "__main__":
    demonstrate_set_dict()

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

核心机制

深入解析 Python 生成器与惰性求值

本文探讨 Python 生成器的底层原理及其在内存优化中的应用,重点介绍 yield 关键字的工作机制、迭代协议以及如何在处理大规模数据流时实现高效的惰性求值。

阅读全文

在 Python 编程中,生成器是一种极其强大且优雅的特性,它允许函数返回一个可迭代的对象,而不是直接计算并存储所有结果。理解生成器的关键在于掌握“惰性求值”这一概念。传统的列表或元组会在定义时一次性将所有元素加载到内存中,这在处理海量数据时往往会导致内存溢出或性能瓶颈。而生成器则不同,它仅在需要时才计算下一个值,这种按需计算的机制极大地降低了内存占用。

生成器的实现依赖于 yield 关键字。当函数体中包含 yield 语句时,该函数不再是一个普通函数,而是一个生成器函数。调用生成器函数并不会立即执行函数体内的代码,而是返回一个生成器对象。这个对象实现了迭代器协议,即包含 __iter__ 和 __next__ 方法。每当通过 for 循环或 next() 函数请求下一个值时,生成器会暂停在当前 yield 语句处,保存局部变量状态和执行上下文,并将产出的值返回给调用者。当下一次请求到来时,生成器从上次暂停的地方恢复执行,直到遇到下一个 yield 或函数结束。

除了节省内存,生成器还简化了代码结构。在处理文件读取、网络数据流或无限序列时,使用生成器可以避免编写复杂的索引管理和缓冲区逻辑。例如,逐行读取大文件时,传统方式可能需要将整个文件载入内存,而生成器可以确保每次只在内存中保留当前行。此外,Python 提供了生成器表达式,这是一种类似列表推导式的简洁语法,但返回的是生成器而非列表,进一步提升了代码的可读性和效率。

需要注意的是,生成器是一次性的迭代器。一旦遍历完成,其内部状态耗尽,无法再次从头开始遍历,除非重新创建生成器实例。因此,如果需要多次访问同一数据集,应谨慎选择是否使用生成器。在实际开发中,合理运用生成器结合 itertools 标准库中的工具,可以构建出高效、低内存占用的数据处理管道,这是 Pythonic 风格编程的重要组成部分。

def read_large_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            yield line.strip()

# 示例用法:假设有一个名为 data.txt 的文件
# generator = read_large_file('data.txt')
# for chunk in generator:
#     process(chunk)

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

Python 技术记录

理解 Python 的装饰器机制

本文深入解析 Python 装饰器的核心概念,探讨其基于闭包和高阶函数的实现原理。通过剖析 functools.wraps 的作用及带参数装饰器的构建方法,帮助开发者掌握代码复用与逻辑分离的技巧,提升程序的可维护性与优雅度。

阅读全文

在 Python 编程实践中,装饰器是一种强大且优雅的语法糖,它允许我们在不修改原有函数源代码的前提下,动态地扩展函数的功能。这一机制的核心在于 Python 将函数视为一等公民,即函数可以作为参数传递、作为返回值返回,以及被赋值给变量。装饰器本质上是一个高阶函数,它接收一个函数作为输入,并返回一个新的函数作为输出。这种设计模式极大地促进了代码的模块化,使得日志记录、权限校验、性能监控等横切关注点能够从业务逻辑中剥离出来,从而保持核心代码的简洁与专注。

要真正理解装饰器,必须掌握闭包的概念。当内部函数引用了外部作用域中的变量时,即使外部函数已经执行完毕,这些变量依然会被保留在内存中,供内部函数使用。装饰器正是利用这一特性,在包装函数中捕获原始函数,并在调用包装函数时插入额外的逻辑。然而,简单的装饰器会改变原始函数的元数据,如名称和文档字符串,这可能导致调试困难或依赖元数据的库无法正常工作。为了解决这个问题,标准库提供了 functools.wraps 装饰器,它能自动将原始函数的属性复制到包装后的函数上,确保行为的一致性。

此外,带参数的装饰器进一步增强了灵活性。这类装饰器通常由三层嵌套函数构成:最外层接收装饰器参数,中间层接收被装饰的函数,最内层则是实际执行的包装逻辑。这种结构虽然增加了复杂性,但赋予了开发者在应用装饰器时配置行为的能力,例如设置重试次数或缓存过期时间。在实际开发中,合理使用装饰器不仅能减少重复代码,还能使程序结构更加清晰。需要注意的是,过度使用多层装饰器可能会增加栈深度,影响性能或导致异常追踪变得复杂,因此应在可读性与功能性之间寻找平衡。掌握装饰器是进阶 Python 开发的必经之路,它体现了 Python 语言对简洁性和表达力的追求。

import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Function {func.__name__} executed in {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Done"

slow_function()

本文由 AI 自动生成,尚未经人工审校;请结合官方文档验证。

标准库

用 pathlib,把文件路径写清楚

从拼接字符串到操作路径对象,让日常文件处理更容易阅读和维护。

阅读全文

处理文件时,可以把路径当作对象,而不是自己拼接斜杠。pathlib 是 Python 标准库的一部分。Path 对象支持使用 / 运算符连接路径片段,也提供读取、写入和遍历目录的方法。

下面的例子在当前工作目录下创建 notes 文件夹,再写入并读取一段中文。显式指定 UTF-8 编码,可以减少不同系统默认编码带来的差异。

注意:write_text 会覆盖同名文件的原有内容。处理重要数据前,请先确认目标路径。相对路径以程序启动时的工作目录为基准,而不一定是脚本所在目录。

from pathlib import Path

folder = Path.cwd() / 'notes'
folder.mkdir(parents=True, exist_ok=True)
file = folder / 'hello.txt'
file.write_text('你好,Python!', encoding='utf-8')
print(file.read_text(encoding='utf-8'))
数据处理

用 Counter 统计重复出现的元素

几行代码完成计数,适合整理标签、日志类别和简单统计。

阅读全文

统计列表中每个元素出现了多少次,可以使用 collections.Counter。它接收可迭代对象,并把元素映射到对应的计数。

most_common 方法按计数从高到低返回元素及其数量。示例会得到 python 出现 3 次、sql 出现 2 次。Counter 适合可以完整遍历的数据;对于很大的文件,可以逐行读取并逐步更新计数,避免一次性读入全部原始内容。

from collections import Counter

tags = ['python', 'sql', 'python', 'web', 'sql', 'python']
counts = Counter(tags)
for tag, count in counts.most_common(2):
    print(tag, count)
日常实践

读写 JSON 时,保留中文的可读性

明确编码、格式化缩进,让小型配置文件也能整洁易读。

阅读全文

JSON 常用于保存配置和交换结构化数据。Python 的 json 模块能够在字典、列表等对象与 JSON 文本之间转换。

ensure_ascii=False 让中文直接显示在输出文本中;indent=2 为嵌套结构添加缩进。这两个参数改善可读性,不改变数据本身。文件读写仍然需要指定 UTF-8 编码。

读取外部 JSON 时,要处理文件不存在、格式错误等情况,并检查字段类型。不要把密码或 API 密钥写进对外提供访问的 JSON 文件。

import json

settings = {'名称': 'Python 的技术记录', '语言': '中文'}
text = json.dumps(settings, ensure_ascii=False, indent=2)
print(text)
restored = json.loads(text)
assert restored == settings