环境链条(Environment Chain)
环境中的绑定和求值(Binding and Evaluation)
1. 变量赋值(Assigning)
- 赋值(Assigning)将一个数值绑定到一个变量上。
- 赋值发生在当前环境的第一个框架中。
- 例如:
x = 5 # x 被绑定到 5
2. 函数定义(Def statements)
- def语句会将一个名称绑定到一个函数值上。
- 这个绑定发生在当前环境的第一个框架中。
- 这个新的函数值会保存一个指向当前环境的链接。
- 例如:
def foo(y): return x + y # foo 保持了定义时的环境链接
3. 函数调用(Calling a Function)
- 调用用户自定义函数时,会创建一个新的局部环境框架(local environment frame)。
- 这个新框架会将函数的**形式参数(formal parameters)绑定到调用时传入的实际参数(actual parameters)**上。
- 例如:
foo(3) # 实际参数 3 会绑定到 形式参数 y
4. 局部环境与父环境
- 这个新的局部框架会连接到现有的父环境,这个父环境来自于被调用的函数值的定义时环境。
- 这样就形成了一个新局部环境,在这个环境中求值函数体的内容。
总结
- 赋值:变量被绑定到当前环境的数值。
- def语句:函数定义时绑定名称,并记录定义时的环境。
- 函数调用:创建局部环境,将参数绑定到实参,并连接到父环境。
这种机制确保了程序可以正确地查找变量和参数值,形成一个清晰的环境链条。
局部环境
函数调用时会创建新的局部环境,这是为了确保函数执行时的数据独立性和局部作用域,从而实现函数的正确行为和可重复使用。下面详细解释为什么会创建局部环境:
1. 数据独立性(隔离变量)
函数的局部环境可以保证局部变量与外部变量隔离,不会相互影响。
示例:
def foo(x):
y = x + 2
return y
a = foo(3) # 调用时,x = 3,y 只在这个调用中存在
b = foo(5) # 调用时,x = 5,这是新的局部环境
- 在调用
foo(3)
时,局部环境绑定x = 3
,y = 5
。 - 在调用
foo(5)
时,新的局部环境绑定x = 5
,y = 7
。 - 局部变量
x
和y
只存在于每次函数调用的局部环境中,互不干扰。
原因:如果不创建局部环境,那么同名变量会在多个调用中互相覆盖,导致错误结果。
2. 函数的参数绑定
函数调用时,参数(形式参数)需要与传入的值(实际参数)进行绑定。这一绑定过程发生在新的局部环境中。
示例:
def add(a, b):
return a + b
add(2, 3) # 局部环境绑定:a = 2, b = 3
add(5, 7) # 局部环境绑定:a = 5, b = 7
- 每次调用
add
时,都会创建一个新的局部环境,分别绑定不同的参数值。 - 这些绑定保证了函数可以处理不同的输入值,而不会影响其他调用。
3. 递归调用的需要
如果没有新的局部环境,递归调用无法实现,因为每一层递归调用都需要独立的参数和中间状态。
示例:
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
factorial(3)
- 调用
factorial(3)
:创建局部环境,绑定n = 3
。 - 调用
factorial(2)
:再次创建新的局部环境,绑定n = 2
。 - 调用
factorial(1)
:再创建新的局部环境,绑定n = 1
。 - 每个局部环境独立存储参数
n
的值和计算过程,最终将结果返回。
原因:如果没有独立的局部环境,不同递归层次的 n
就会互相覆盖,导致无法正确求值。
4. 函数的可复用性
创建新的局部环境可以确保函数可以在不同上下文中调用,并返回正确的结果。
示例:
def square(x):
return x * x
a = square(2) # 创建局部环境,x = 2
b = square(4) # 创建新的局部环境,x = 4
- 局部环境的创建保证了
square(2)
和square(4)
可以独立执行,不会相互影响。
5. 总结:为什么会创建新的局部环境?
函数调用时创建新的局部环境的根本原因是:
- 隔离局部变量,保证数据独立,不影响其他函数调用。
- 参数绑定,将实际参数传递给形式参数。
- 支持递归,为每一层调用保存独立的状态。
- 提高可复用性,使函数能够独立运行。
局部环境使函数调用成为一个封闭的计算单元,有效管理变量作用域,保证程序的正确性和可维护性。
return f 和 return f(x) 的区别
return f 返回的是 函数对象 f。
return f(x) 返回的是 函数 f 执行后的结果,即 f(x) 的计算值。
报错举例:
你遇到的错误是:
NameError: name 'x' is not defined
这是因为在函数 incr(n)
中,你尝试直接调用 f(x)
,但是在此时 x
并没有定义。这导致 Python 无法找到 x
的值,产生了 NameError
错误。
具体原因:
def incr(n):
def f(x):
return n + x
return f(x) # 错误:x 在这里没有定义
在这个代码中:
f(x)
是一个内嵌函数,它需要一个x
作为参数。- 但是你在
incr(n)
函数内部直接调用了f(x)
,并且没有给x
传递任何值,这会导致 Python 无法理解x
是什么。
正确的做法:
如果你的目的是返回 f
函数本身(即将 f
作为闭包返回),而不是立即执行它,你应该改为 return f
,而不是 return f(x)
。然后,稍后你可以使用参数来调用 f
。
修改后的代码:
def incr(n):
def f(x):
return n + x
return f # 返回函数 f 本身,而不是调用 f(x)
result_function = incr(5) # 返回 f 函数
result = result_function(6) # 调用 f(6)
print(result) # 输出 5 + 6 = 11
解释:
incr(5)
返回的是f
函数,而不是f(x)
的执行结果。- 返回的
f
函数被赋值给result_function
,然后我们调用result_function(6)
,即实际调用的是f(6)
。 f(6)
会计算n + x
,其中n
是从incr(5)
中传递来的5
,所以f(6)
返回的是5 + 6 = 11
,最终打印结果是11
。
关键修改:
- 使用
return f
返回函数对象,而不是直接调用f(x)
。
这样就能正确地实现闭包的功能。
控制语句 Control
关于True Values
false value 包括:
False
None
0
Empty strings, sets, lists, tuples, and dictionaries
其他全是true value
A if B else C 举例代码分析
这段代码包含了两个条件表达式(Python 的三元运算符 A if B else C
),整体是一个比较表达式:
13 if 0 else 5 == 13 if [] else 5
-
左侧部分:
13 if 0 else 5
- 条件
0
是布尔值False
。 - 因此,选择
else
后的值5
。
结果:左侧为
5
。 - 条件
-
右侧部分:
13 if [] else 5
- 条件
[]
是空列表,在布尔上下文中,它也是False
。 - 因此,选择
else
后的值5
。
结果:右侧为
5
。 - 条件
- 比较运算:
5 == 5
- 左侧和右侧的结果都是
5
,所以5 == 5
为True
。
- 左侧和右侧的结果都是
最终结果
输出 True
。
left and right:先计算left,如果left为false,表达式的值为left,不计算right的值(不判断right是否有意义,如1/0);否则,表达式值为right。
left or right: 先计算left,如果left为true,表达式的值为left,不计算right的值(不判断right是否有意义,如1/0);否则,表达式值为right。