Skip to content

变量的作用域

变量声明的位置不同,其可以被访问的范围也不同。变量的可被访问范围称为变量的作用域。变量按其作用域大致可以分为全局变量、局部变量和类成员变量。

全局变量

在一个源代码文件中,在函数和类定义之外声明的变量称为全局变量。全局变量的作用域为其定义的模块,从定义的位置起,直到文件结束位置。

通过 import 语句导入模块,也可以通过全限定名称 “模块名.变量名” 访问;或者通过 from … import 语句导入模块中的变量并访问。

不同的模块都可以访问全局变量,这会导致全局变量的不可预知性。如果多个语句同时修改一个全局变量,则可能导致程序产生错误,且很难发现和更正。

全局变量降低了函数或模块之间的通用性,也降低了代码的可读性。在一般情况下,应该尽量避免使用全局变量。全局变量一般作为常量使用。

案例:全局变量定义示例

python
TAX1 = 0.17  # 税费常量 17 %
TAX2 = 0.2  # 税费常量 20 %
TAX3 = 0.05  # 税费常量 5 %
PI = 3.14  # 圆周率

注意:python 是一门动态类型语言,常量是约定俗成,本质是还是可以修改的。

局部变量

在函数体中声明的变量(包括函数参数)称为局部变量,其有效范围(作用域)为函数体。

全局代码不能引用一个函数的局部变量或形式参数变量;一个函数也不能引用在另一个函数中定义的局部变量或形式参数变量。如果在一个函数中定义的局部变量(或形式参数变量)与全局变量重名,则局部变量(或形式参数变量)优先,即函数中定义的变量是指局部变量(或形式参数变量),而不是全局变量。

案例:局部变量定义示例

python
num = 100


def func():
    num = 15
    return num


print(func())
print(num)

说明:函数 f() 中的 print(num) 语句引用的是局部变量 num,因此输出 105 。

全局语句 global

在函数体中可以引用全局变量,但如果函数内部的变量名是第一次出现且在赋值语句之前(变量赋值),则解释为定义局部变量。

案例:函数体错误引用全局变量的示例

python
m = 100
n = 200


def func():
    print(m + 5)
    n += 10


func()

运行之后报错

Traceback (most recent call last):
  File "D:\0015B-核心编程\06-函数(一)\上课代码2\demo1.py", line 125, in <module>
    func()
  File "D:\0015B-核心编程\06-函数(一)\上课代码2\demo1.py", line 123, in func
    n += 10
UnboundLocalError: local variable 'n' referenced before assignment

如果要为定义在函数外的全局变量赋值,可以使用 global 语句,表明变量是在外面定义的全局变量。global 语句可以指定多个全局变量,例如 global x, y, z 。一般应该尽量避免这样使用全局变量,全局变量会导致程序的可读性差。

案例:全局语句 global 示例

python
pi = 3.141592653589793
e = 2.718281828459045


def my_func():
    global pi
    pi = 3.14
    print('global pi = ', pi)
    e = 2.718
    print('local e = ', e)


print('module pi = ', pi)
print('module e = ', e)
my_func()
print('module pi = ', pi)
print('module e = ', e)

递归函数

递归函数即自调用函数,在函数体内部直接或间接地自己调用自己,即函数的嵌套调用是函数本身。递归函数常用来实现数值计算的方法。

例如,非负整数的阶乘定义为:

n!=n×(n-1)×(n-2)×…×2×1,当 n=1 时,n!=1 即 n! 是所有小于或等于 n 的正整数的乘积。很显然,使用 for 循环结构能很容易地计算 n! 。更简单的方法是采用递归函数实现:

n! = 1				# 当 n == 1
n! = n x (n-1)!		# 当 n > 1 时

使用递归函数实现阶乘

python
def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)

递归函数的原理

递归提供了建立数学模型的一种直接方法,与数学上的数学归纳法相对应。

每个递归函数必须包括以下两个主要部分。

(1)终止条件:表示递归的结束条件,用于返回函数值,不再递归调用。例如,factorial()函数的结束条件为“n等于1”。

(2)递归步骤:递归步骤把第n步的参数值的函数与第n-1步的参数值的函数关联。例如,对于 factorial() ,其递归步骤为 n*factorial(n-1) 。另外,一序列的参数值必须逐渐收敛到结束条件。例如,对于 factorial(),每次递归调用参数值n均递减1,所以一序列参数值逐渐收敛到结束条件(n=1)。例如,调和数的计算公式如下。

Hn=1+12+...+1n

故可以使用递归函数实现。

(1)终止条件:Hn=1n==1

(2)递归步骤:Hn=Hn1+1nn>1

每次递归,n 故逐渐收敛于1。

案例:使用递归函数实现调和数

python
def harmonic(n):
    if n == 1:
        return 1
    return harmonic(n - 1) + 1 / n


for i in range(1, 10):
    print('H', i, '=', harmonic(i))

编写递归函数时需要注意的问题

虽然使用递归函数可以实现简洁、优雅的程序,但在编写递归函数时应该注意如下几个问题。

(1)必须设置终止条件。

缺少终止条件的递归函数将导致无限递归函数调用,其最终结果是系统会耗尽内存。此时 Python 会抛出错误 RuntimeError ,并报告错误信息 “maximum recursion depth exceeded(超过最大递归深度)”。

在递归函数中一般需要设置终止条件。在 sys 模块中,函数 getrecursionlimit()setrecursionlimit() 用于获取和设置最大递归次数。例如:

>>> import sys
>>> sys.getrecursionlimit()
1000
>>> sys.setrecursionlimit(2000)
>>>

(2)必须保证收敛。

递归调用所解决子问题的规模必须小于原始问题的规模,否则会导致无限递归函数调用。

(3)必须保证内存和运算消耗控制在一定范围内。

递归函数代码虽然看起来简单,但往往会导致过量的递归函数调用,从而消耗过量的内存(导致内存溢出),或过量的运算能力(运行时间过长)