上下文管理器
with ctx: 是 Python 中的一种上下文管理语法,使用的是 上下文管理器(Context Manager) 的功能。
with 语句 通过上下文管理器简化了资源的管理,在代码块执行前后自动执行一些操作,如资源的分配和清理。
ctx 是一个上下文管理器对象,它实现了 enter 和 exit 方法。
例如,with open(‘file.txt’, ‘r’) as f: content = f.read() %# 文件自动关闭,无需显式调用 f.close()
还可以使用 contextlib 库中的 contextmanager 装饰器来简化上下文管理器的定义。

作用域
作用域和变量的可见性:
Python 中有多个作用域,它们定义了变量的可见范围。
Python 按照 LEGB 规则(Local, Enclosing, Global, Built-in)来查找变量的值,先查找局部作用域,再查找嵌套作用域、全局作用域,最后查找内建作用域。
局部变量和全局变量同名时,局部变量会遮蔽全局变量。这可能导致程序出错或难以理解。
局部作用域(Local Scope):函数内部定义的变量,只能在该函数内部访问。局部变量会在函数调用结束后消失。
嵌套作用域(Enclosing Scope):在函数内部定义的函数所形成的作用域,可以访问外部函数的变量,但不能修改它们。
在嵌套函数内,用nonlocal 关键字声明一个变量是来自于外部函数(不是全局作用域)的变量,而不是局部变量。
全局作用域(Global Scope):模块级别的变量,整个模块内都可以访问。
当一个变量在函数外部定义(例如在模块的最外层),它就成为了全局变量。在函数内部,你可以直接访问这个全局变量,而不需要通过参数传递。但不能对其赋值。
在函数内用global 关键字声明变量是全局变量而不是局部变量,并且能够在函数内部修改该全局变量。
内建作用域(Built-in Scope):Python 内建的名称(如 print, len 等),这些在任何地方都可以访问。
闭包
闭包的关键在于函数内部引用了函数外部的局部变量,并且它在调用时可以使用这些外部变量。闭包(closure)是函数的一个重要特性,它能“记住”并访问定义时的作用域,即使这个函数在定义之后被调用。通常用于回调函数和工厂函数等场景。
依赖于嵌套作用域(或词法作用域)的机制。
关键点在于生命周期:内部函数脱离了外部函数的生命周期,并在外部作用域结束后仍然保持对外部变量的访问。
闭包捕获的是引用(Reference): 当内部函数形成闭包时,它记住的不是 x = 5 这个值,而是存储着 x 这个变量的内存地址。
变量是共享的: 只要外部变量的作用域因为闭包的存在而没有被销毁,那么外部函数作用域中的那个变量就一直存在,并且可以被改变。
实时状态: 无论内部函数何时被调用,它访问的都是该变量在当前调用时刻的最新、实时状态。
闭包的作用:工厂函数
工厂函数 (Factory Function) - 灵活创建定制化函数 用途: 闭包允许我们创建一个能生成定制化函数的函数,生成的每个函数都带有自己“记住”的配置参数。
假设您需要创建多个打折函数,每个函数适用不同的折扣率,但您不想为每个折扣率都写一个独立的函数。


我们用一个函数(工厂)创建了两个功能相似但参数不同的函数,实现了代码的复用和定制化。
闭包的作用:装饰器
装饰器 (Decorator) - 在不修改原函数的前提下增强功能
装饰器本质上就是闭包,它允许您在不改变目标函数代码的情况下,给目标函数添加额外的逻辑(如日志记录、计时、权限检查等)。
此时捕获的嵌套变量是函数


记住并调用: 闭包(wrapper 函数)“记住了” func 这个变量。当您调用 complex_calculation(10) 时,实际上运行的是 wrapper 函数,而 wrapper 通过它记住的 func 变量,成功地调用了原始的计算逻辑,并在前后添加了计时代码。
非侵入式增强: 我们在没有修改 complex_calculation 函数体的情况下,增强了它的功能。
python函数参数
python函数参数定义顺序:位置参数 默认参数 可变位置参数 命名关键字 可变关键字
按照参数定义顺序赋值: 例如def func(a, b=2, *args): print(f"a = {a}, b = {b}, args = {args}"); func(1) # 输出: a = 1, b = 2, args = () func(1, 3) # 输出: a = 1, b = 3, args = () func(1, 3, 4, 5) # 输出: a = 1, b = 3, args = (4, 5)
定义时候,默认参数必须要放在位置参数后面,并且一定有默认值.之后需要定义命名关键字参数。
定义时候不会出现 可变位置参数(这个没名字) 可变关键字参数(这个有关键字但只作为key) 的具体名字,只是用星号+任意名字(一般记为args), 两个星号+任意名字(一般记为kwargs)表示 。
调用时候,都可以用关键字的方式写出。
调用时候,多出来的参数,有关键字的按照可变关键字收集成字典,没有关键字的按照可变位置参数收集成元组。





可变默认参数只初始化一次,因此默认参数最好指向不变对象,不然容易出问题,所有对该默认参数的修改都会直接修改那个唯一共享的默认对象。 def risky_func(x, L=[]): # [] 是可变对象 (List) L.append(x) print(f"List: {L}")
risky_func(1) # List: [1] risky_func(2) # List: [1, 2] <– 错误!L保留了上一次的状态 risky_func(3) # List: [1, 2, 3]
再次调用时,如果发现需要使用默认值,会使用前一次调用时创建的对象,因为它认为这个对象不会发生改变,指向不变对象。
正确的做法是使用 None 作为默认值替代[],并在函数体内部检查它是否被传入:if L is None: L = []
