一. 变量及作用域
局部变量:局部变量是指定义在函数内部的变量,函数的形参也属于局部变量。局部变量只能在函数内部使用,并在函数调用时被创建,调用结束后自动销毁。
全局变量:全局变量是指定义在函数外部,模块内部的变量。所有的函数都可以直接 访问 全局变量,而只有可变数据类型的全局变量才可以在函数中被修改。
Python的作用域:Python 中的作用域可以简称为 LEGB
,也叫命名空间,是访问变量时查找变量名的范围空间。变量名的查找顺序即:L --> E --> G --> B
。
- L(local):局部作用域,即函数内部作用域;
- E(Enclosing):外部嵌套函数作用域;
- G(Globals):全局作用域,即模块内部作用域;
- B(Builtin):Python 内置模块的作用域。
global 语句:即全局声明,将函数内部赋值的变量映射到全局作用域。
在函数内部修改绑定不可变数据类型的全局变量时,必须用 global
声明,否则将被视为局部变量:
GLOBAL_STR = 'HELLO PYTHON'
def my_print():
GLOBAL_STR = 'hello python'
print(GLOBAL_STR)
不使用 global
时,在 my_print
中对 GLOBAL_STR
赋值,其实是在函数内部作用域创建了一个新的变量 GLOBAL_STR
,原 GLOBAL_STR
并不会发生变化:
>> my_print()
hello python
>> GLOBAL_STR
'HELLO PYTHON'
全局变量在函数内部可以不经过声明直接访问:
GLOBAL_STR = 'HELLO PYTHON'
def my_print():
print(GLOBAL_STR)
运行结果:
>> my_print()
HELLO PYTHON
禁止先创建局部变量,再用 global
声明为全局变量:
nonlocal 语句:即 nonlocal
声明,指出变量不是局部变量,而是外部嵌套函数作用域(Enclosing)的变量。
nonlocal
语句只能在内嵌函数内使用;访问 nonlocal
变量将对外部嵌套函数作用域内的变量产生影响。当有 2 层或 2 层以上的函数嵌套时,访问 nonlocal
变量只对最近一层的变量进行操作。
二. 闭包
闭包满足的三个条件:
- 必须有内嵌函数
- 内嵌函数必须引用外部嵌套函数(Enclosing)作用域中的变量
- 外部函数返回值必须是 内嵌函数
通常情况下,在函数调用结束后,函数内部变量会自动被释放。但闭包不同,如果外部嵌套函数在调用结束后,发现自己的变量在内嵌函数中有使用,则外部函数在调用结束后,在返回内嵌函数的同时,也会将这些变量传递给内嵌函数,一起绑定起来。
因此,外部函数调用结束后,内嵌函数被调用时仍可以访问外部嵌套函数作用域内的变量。
闭包演示:
def outer(start, step1=0, step2=None):
i = 0
if step2 is None:
step2 = [step1]
def iner():
nonlocal step1, i
print('{} + {} = {}'.format(start, step1, start + step1))
print('{} + {} = {}'.format(start, step2[i], start + step2[i]))
step1 = step1 * 2
step2.append(step2[i] * 2)
i += 1
return iner
上述代码中,i
和 step1
都是 Enclosing 作用域的变量,并且绑定不可变数据类型。因此想要在 inner
函数中修改他们(重新绑定新的对象),必须使用 nolocal
语句将 i
step1
声明名 nolocal 变量。
step2
虽然也是 Enclosing 作用域的变量,但绑定的列表,属于可变数据类型。因此,可以在 innner
中修改 step2
绑定的对象内容。但注意,如果要为 step2
变量绑定全新的对象,仍需要 nolocal
语句声明。
运行结果:
>> my_sum = outer(10, step1=10)
>> my_sum()
10 + 10 = 20
10 + 10 = 20
>> my_sum()
10 + 20 = 30
10 + 20 = 30
>> my_sum()
10 + 40 = 50
10 + 40 = 50
每次的调用 my_sum
打印的值都不同,因此闭包可以用作记录程序的内部状态。
三. 递归函数 Recursion
递归函数只函数直接或间接地调用自身。如:
def func():
'''直接调用自己,进入递归'''
func()
def func1():
'''函数间接调用自身'''
func2()
def func2():
func1()
调用上述函数时,将在达到系统最大的递归深度时抛出异常:
>> func()
...
RecursionError: maximum recursion depth exceeded
>> func1()
...
RecursionError: maximum recursion depth exceeded
使用 sys.getrecursionlimit()
/ sys.setrecursionlimit(n)
即可查看或修改系统默认的递归深度:
>> import sys
>> sys.getrecursionlimit()
3000
递归分为以下两个阶段:
- 递推阶段:由问题出发,从未知到已知,按递归公式递推,直到满足终止条件。
- 回归阶段:由递归终止条件得到的结果出发,逆向逐步代入递归公式,回归问题。
在适合使用递归的问题上,使用递归,可以简化问题,使得思路清晰,代码简洁。比如,下面几个递归的应用就很恰到好处~
计算等差数列:1, 2, 3 ... n 的和:
def mysum(n):
if n == 1:
return 1
return n + mysum(n-1)
运行结果:
>> mysum(100)
5050
写一个函数求 n 的阶乘:
def myfac(n):
if n==1:
return 1
return n * myfac(n-1)
运行结果:
>> print('5的阶乘是:', myfac(5))
5的阶乘是: 120
已知嵌套列表:L = [[3, 5, 8], 10, [[13, 14], 15, 18], 20]
,递归打印列表内的所有元素,并计算列表中所有元素的和。
def print_list(L):
for item in L:
if type(item) is not list:
print(item, end=' ')
else:
print_list(item) # 递归的思想
def sum_list(L):
sum_number = 0
for item in L:
if type(item) is not list:
sum_number += item
else:
sum_number += sum_list(item)
return sum_number
运行结果:
>> L = [[3, 5, 8], 10, [[13, 14], 15, 18], 20]
>> print_list(L)
3 5 8 10 13 14 15 18 20
>> print("返回这个列表中所有元素的和:", sum_list(L))
返回这个列表中所有元素的和: 106
小结:递归的实现方法,即先假设函数已经实现,找出递归的终止条件。