变量作用域
- Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
- 定义在函数内部的变量拥有局部作用域,该变量称为局部变量。
- 定义在函数外部的变量拥有全局作用域,该变量称为全局变量。
- 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。
1
【例子】
def discounts(price, rate):
final_price = price * rate
return final_price
old_price = float(input('请输入原价:')) # 98
rate = float(input('请输入折扣率:')) # 0.9
new_price = discounts(old_price, rate)
print('打折后价格是:%.2f' % new_price) # 88.20
这段代码是一个简单的计算打折后价格的函数:
def discounts(price, rate):
final_price = price * rate
return final_price
这是一个名为discounts
的函数定义,它接受两个参数price
和rate
。函数体内部,将原价price
与折扣率rate
相乘得到最终价格final_price
,然后将其返回。
old_price = float(input('请输入原价:')) # 98
rate = float(input('请输入折扣率:')) # 0.9
这里使用input
函数分别提示用户输入原价和折扣率,并通过float
函数将输入的字符串转换为浮点数类型。用户输入的原价存储在变量old_price
中,折扣率存储在变量rate
中。
new_price = discounts(old_price, rate)
调用函数discounts
,传入old_price
和rate
作为参数,返回的结果被赋值给变量new_price
,即为计算出的打折后价格。
print('打折后价格是:%.2f' % new_price) # 88.20
使用print
函数将打折后的价格格式化输出,保留两位小数,并在字符串中使用占位符%.2f
来表示浮点数类型。这样就会将变量new_price
的值插入到字符串中进行输出,得到打折后价格的结果。
2
- 当内部作用域想修改外部作用域的变量时,就要用到
global
和nonlocal
关键字了。
【例子】
num = 1
def fun1():
global num # 需要使用 global 关键字声明
print(num) # 1
num = 123
print(num) # 123
fun1()
print(num) # 123
这段代码演示了如何在函数内部修改全局变量的值。让我们逐行解析代码:
num = 1
在全局作用域中定义了一个变量num
,初始值为1
。
def fun1():
global num # 需要使用 global 关键字声明
print(num) # 1
num = 123
print(num) # 123
定义了一个名为fun1
的函数。在函数内部,通过global
关键字声明num
为全局变量,这样就可以在函数中修改全局变量的值。首先,使用print
函数输出全局变量num
的值,得到结果1
。然后,将num
的值修改为123
。再次使用print
函数输出改变后的num
的值,得到结果123
。
fun1()
调用函数fun1
,执行函数内的代码。
print(num) # 123
使用print
函数输出全局变量num
的值,得到结果123
,即函数内部对全局变量的修改在函数外仍然有效。
3
内嵌函数
【例子】
def outer():
print('outer函数在这被调用')
def inner():
print('inner函数在这被调用')
inner() # 该函数只能在outer函数内部被调用
outer()
# outer函数在这被调用
# inner函数在这被调用
这段代码展示了一个内部函数的例子:
def outer():
print('outer函数在这被调用')
def inner():
print('inner函数在这被调用')
inner() # 该函数只能在outer函数内部被调用
定义了一个名为outer
的函数。在outer
函数的函数体内,定义了一个名为inner
的内部函数。内部函数inner
只能在外部函数outer
内部被调用。
outer()
调用函数outer
,执行函数内的代码。
输出结果为:
outer函数在这被调用
inner函数在这被调用
首先,打印出字符串"outer函数在这被调用"。然后,在outer
函数内部调用了inner
函数,因此会打印出字符串"inner函数在这被调用"。
4
闭包
- 是函数式编程的一个重要的语法结构,是一种特殊的内嵌函数。
- 如果在一个内部函数里对外层非全局作用域的变量进行引用,那么内部函数就被认为是闭包。
- 通过闭包可以访问外层非全局作用域的变量,这个作用域称为 闭包作用域。
【例子】
def funX(x):
def funY(y):
return x * y
return funY
i = funX(8)
print(type(i)) # <class 'function'>
print(i(5)) # 40
这段代码展示了一个闭包的例子:
def funX(x):
def funY(y):
return x * y
return funY
定义了一个名为funX
的函数,它接受一个参数x
。在funX
函数的函数体内部,定义了一个名为funY
的内部函数,该内部函数接受一个参数y
,并返回x * y
的结果。
i = funX(8)
调用函数funX
,将参数8
传递给x
,返回的结果被赋值给变量i
。此时,i
成为一个闭包,它包含了函数funY
和x
的值8
。
print(type(i)) # <class 'function'>
使用type
函数输出变量i
的类型。结果显示为<class 'function'>
,表示i
是一个函数。
print(i(5)) # 40
调用变量i
,实际上是调用了内部函数funY
。将参数5
传递给funY
的参数y
,最终返回的结果是8 * 5 = 40
。使用print
函数输出结果40
。
5
【例子】闭包的返回值通常是函数。
def make_counter(init):
counter = [init]
def inc(): counter[0] += 1
def dec(): counter[0] -= 1
def get(): return counter[0]
def reset(): counter[0] = init
return inc, dec, get, reset
inc, dec, get, reset = make_counter(0)
inc()
inc()
inc()
print(get()) # 3
dec()
print(get()) # 2
reset()
print(get()) # 0
这段代码展示了一个使用闭包实现计数器的例子。以下逐行解析代码:
def make_counter(init):
counter = [init]
def inc(): counter[0] += 1
def dec(): counter[0] -= 1
def get(): return counter[0]
def reset(): counter[0] = init
return inc, dec, get, reset
定义了一个名为make_counter
的函数,它接受一个参数init
。在make_counter
函数的函数体内部,首先创建了一个列表counter
,将参数init
作为列表的第一个元素。然后,定义了四个内部函数inc
、dec
、get
和reset
,分别用于增加计数器、减少计数器、获取当前计数器值和重置计数器。
inc, dec, get, reset = make_counter(0)
调用函数make_counter
,将参数0
传递给init
,返回的四个函数分别被赋值给变量inc
、dec
、get
和reset
。此时,这些变量成为了闭包,其中包含了计数器列表counter
和初始化值init
。
inc()
inc()
inc()
print(get()) # 3
连续三次调用变量inc
,即调用了内部函数inc
,每次都会将计数器列表中的第一个元素加1。使用print
函数输出调用get()
函数的结果,即获取当前计数器的值。结果为3
,表示计数器经过三次增加后的值。
dec()
print(get()) # 2
调用变量dec
,即调用了内部函数dec
,将计数器列表中的第一个元素减1。再次使用print
函数输出当前计数器的值。结果为2
,表示计数器经过一次减少后的值。
reset()
print(get()) # 0
调用变量reset
,即调用了内部函数reset
,将计数器列表中的第一个元素重置为初始值。最后,再次使用print
函数输出当前计数器的值。结果为0
,表示计数器被重置为初始值。
6
【例子】 如果要修改闭包作用域中的变量则需要 nonlocal
关键字
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
# 100
# 100
这段代码展示了在函数内部通过使用nonlocal
关键字来修改外部嵌套函数中的变量。以下逐行解析代码:
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
定义了一个名为outer
的函数。在outer
函数的函数体内部,首先定义了一个变量num
并赋值为10
。接着,定义了一个内部函数inner
。在inner
函数内部,使用nonlocal
关键字声明了变量num
,表示num
是外部函数outer
中的变量,而不是在inner
函数内部重新创建的局部变量。然后,将num
赋值为100
,并使用print
函数输出结果100
。
接下来,调用inner
函数,输出结果为100
。再次使用print
函数输出外部函数outer
中的变量num
的值,结果依然为100
。
outer()
# 100
# 100
调用外部函数outer
,执行整个代码块。结果输出为:
100
100
可以看到,通过使用nonlocal
关键字,内部函数inner
对外部函数outer
中的变量num
进行了修改,并且这个修改是在外部函数中生效的。
7
递归
- 如果一个函数在内部调用自身本身,这个函数就是递归函数。
【例子】n! = 1 x 2 x 3 x ... x n
# 利用循环
n = 5
for k in range(1, 5):
n = n * k
print(n) # 120
# 利用递归
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
这段代码展示了计算阶乘的两种方式:使用循环和使用递归。以下逐行解析代码:
n = 5
for k in range(1, 5):
n = n * k
print(n) # 120
使用循环的方式计算阶乘。首先,将变量n
初始化为5
。然后,通过循环遍历从1
到4
的范围(不包括5
),在每次迭代中,将n
与当前迭代变量k
相乘,并将结果赋值给n
。最后,使用print
函数输出最终的结果120
,即5!
的值。
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
使用递归的方式计算阶乘。定义了一个名为factorial
的函数,接受一个参数n
。在函数内部,首先判断n
是否等于1
,如果是,则返回1
作为递归终止条件。如果n
不等于1
,则执行return n * factorial(n - 1)
语句,即将n
与factorial(n-1)
的返回值相乘,并返回结果。这样就形成了递归调用,每一层递归都会将n
减1,直到达到递归终止条件。最后,调用factorial
函数,并将参数5
传入,使用print
函数输出最终的结果120
,即5!
的值。
这两种方式都可以用来计算阶乘,但是使用循环的方式通常更高效,因为递归需要反复调用函数,并在每一层递归中保存函数调用的状态。在处理大规模的计算时,递归可能会导致栈溢出的问题。而使用循环的方式则没有这个限制,更适合处理大规模的计算。
8
【例子】斐波那契数列 f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1
# 利用循环
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
k = i + j
lst.append(k)
i = j
j = k
print(lst)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
# 利用递归
def recur_fibo(n):
if n <= 1:
return n
return recur_fibo(n - 1) + recur_fibo(n - 2)
lst = list()
for k in range(11):
lst.append(recur_fibo(k))
print(lst)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
这段代码展示了生成斐波那契数列的两种方式:使用循环和使用递归。以下逐行解析代码:
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
k = i + j
lst.append(k)
i = j
j = k
print(lst)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
使用循环的方式生成斐波那契数列。首先,定义变量i
和j
分别赋值为0
和1
,然后定义一个空列表lst
用于存储斐波那契数列。接着,通过循环遍历从2
到10
的范围(不包括11
),在每次迭代中,计算当前斐波那契数列的值k
,即前两个数的和。然后,将k
添加到列表lst
中,更新变量i
为上一个数j
,更新变量j
为当前计算得到的数k
。最后,使用print
函数输出最终的斐波那契数列结果。
def recur_fibo(n):
if n <= 1:
return n
return recur_fibo(n - 1) + recur_fibo(n - 2)
lst = list()
for k in range(11):
lst.append(recur_fibo(k))
print(lst)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
使用递归的方式生成斐波那契数列。定义了一个名为recur_fibo
的函数,接受一个参数n
表示要计算的斐波那契数列的第几个数。在函数内部,首先判断n
是否小于等于1
,如果是,则返回n
作为递归终止条件。如果n
大于1
,则执行return recur_fibo(n - 1) + recur_fibo(n - 2)
语句,即将使用递归调用来计算第n-1
和第n-2
个斐波那契数的值,并返回它们的和。这样就形成了递归调用,每一层递归都会继续递归调用前两个数。最后,通过循环遍历从0
到10
的范围,将每个计算得到的斐波那契数添加到列表lst
中。使用print
函数输出最终的斐波那契数列结果。
这两种方式分别使用循环和递归实现了斐波那契数列的生成,而且得到的结果是相同的。选择哪种方式取决于特定的情况。循环方式通常更高效,尤其在处理大规模的计算时。而递归方式更简洁直观,更容易理解,但可能在处理大规模的计算时导致栈溢出的问题。因此,在实际应用中,需要根据具体需求和场景综合考虑选择合适的方法。
9
【例子】设置递归的层数,Python默认递归层数为 100
import sys
sys.setrecursionlimit(1000)
这段代码使用sys.setrecursionlimit()
函数设置了递归的最大深度限制为1000。sys.setrecursionlimit()
是Python标准库sys
中的一个函数,用于设置递归调用的最大深度。
在递归算法中,每次递归调用都会占用一定的内存空间,递归的次数过多可能导致栈溢出错误。为了避免栈溢出错误,可以通过设置递归深度的限制来限制递归的最大层数。sys.setrecursionlimit(1000)
语句将递归调用的最大深度限制设置为1000,意味着在递归调用超过1000层之后,Python解释器会抛出RecursionError: maximum recursion depth exceeded in comparison
异常。
需要注意的是,修改递归深度的设置可能会影响到程序的性能和稳定性。增加递归深度会增加内存的消耗,并且过高的递归深度可能导致程序崩溃。因此,建议在使用sys.setrecursionlimit()
函数设置递归深度之前,先仔细评估程序的需求和性能,并确保设置的递归深度不会导致不必要的问题。