跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Python算法

Python 初级函数详解:参数传递与作用域规则

Python 函数是编程核心工具,支持代码复用与模块化。文章详解函数定义、默认参数、关键字参数及可变参数用法,并阐述 LEGB 作用域查找规则(局部、闭包、全局、内置)。通过示例展示参数传递机制、变量生命周期及 global/nonlocal 关键字应用,帮助开发者编写可维护且高效的 Python 代码。

FrontendX发布于 2026/3/24更新于 2026/5/57 浏览
Python 初级函数详解:参数传递与作用域规则

什么是函数

在编程中,函数(Function)是一段被命名、可重复使用的代码块,用于执行特定任务。它通过接收输入(参数),处理逻辑,并返回输出(结果),将复杂的程序拆分为模块化的组件,让代码更简洁、高效且易于维护。

函数的优势

在 Python 中,函数是编程的核心工具之一,它通过将代码逻辑封装为可重复使用的模块,显著提升了代码的可维护性、复用性和可读性。

避免代码重复:DRY 原则(Don't Repeat Yourself)

  • 问题场景:当同一段代码需要多次使用时,复制粘贴会导致代码臃肿,且后续修改需要逐一调整所有副本。
  • 函数解决方案:将重复代码封装为函数,通过一次定义、多次调用来实现逻辑复用。
# 重复代码示例(计算圆面积)
radius1 = 5
area1 = 3.14 * radius1 ** 2
radius2 = 7
area2 = 3.14 * radius2 ** 2

# 使用函数优化
def calculate_circle_area(radius):
    return 3.14 * radius ** 2

area1 = calculate_circle_area(5)
area2 = calculate_circle_area(7)
  • 修改逻辑只需调整函数内部代码(例如将 3.14 改为 math.pi)。
  • 减少代码量,避免冗余。

模块化编程:分解复杂问题

  • 问题场景:一个大型程序如果写成连续的代码块,会难以理解和维护。
  • 函数解决方案:将程序拆分为多个函数,每个函数负责单一职责。
# 未使用函数的复杂逻辑
data = [1, 2, 3, 4, 5]
sum_val = 0
for num in data:
    sum_val += num
average = sum_val / len(data)
print(f"平均值:{average}")

# 使用函数模块化
def calculate_average(data):
     (data) / (data)

data = [, , , , ]
()
return
sum
len
1
2
3
4
5
print
f"平均值:{calculate_average(data)}"
  • 代码逻辑清晰,每个函数聚焦一个子任务。
  • 便于团队协作开发。

提升代码可读性

  • 问题场景:长段代码缺乏注释时,阅读者需要逐行理解逻辑。
  • 函数解决方案:通过函数名称直接表明代码意图。
# 难读的代码
x = 5
y = 10
result = (x ** 2 + y ** 2) ** 0.5

# 使用函数自解释
def calculate_hypotenuse(a, b):
    return (a ** 2 + b ** 2) ** 0.5

result = calculate_hypotenuse(5, 10)
  • 函数名(如 calculate_hypotenuse)直接说明功能。
  • 减少对注释的依赖。

参数化与灵活性

  • 问题场景:相似逻辑需要处理不同输入数据时,硬编码值会限制灵活性。
  • 函数解决方案:通过参数动态接收输入,返回处理结果。
# 硬编码的欢迎语
print("欢迎,张三!")
print("欢迎,李四!")

# 参数化函数
def greet(name):
    print(f"欢迎,{name}!")

greet("张三")
greet("李四")
  • 同一函数适应不同输入。
  • 支持动态数据处理。

错误隔离与调试效率

  • 问题场景:代码错误可能出现在任意位置,调试时需全局排查。
  • 函数解决方案:将代码划分为函数后,错误通常能被隔离到特定函数内。
def load_data(filename):
    # 假设此处可能抛出文件不存在的错误
    with open(filename, 'r') as f:
        return f.read()

def process_data(data):
    # 此处可能处理数据时出错
    return data.upper()

try:
    data = load_data("data.txt")
    result = process_data(data)
except Exception as e:
    print(f"错误发生在:{e}")
  • 快速定位问题函数。
  • 通过单元测试单独验证每个函数。

支持代码复用与库开发

  • 问题场景:通用功能(如数据验证、数学计算)需要在多个项目中重复实现。
  • 函数解决方案:将通用函数封装为模块(.py 文件),供其他程序导入。
# utils.py
def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

# main.py
from utils import is_prime
print(is_prime(17))  # 输出:True
  • 积累个人代码库,提升开发效率。
  • 促进开源社区协作(如 numpy、requests 等库)。

实现递归算法

  • 问题场景:某些问题(如阶乘、斐波那契数列)天然适合递归解决。
  • 函数解决方案:函数可以调用自身,简化递归逻辑。
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

print(factorial(5))  # 输出:120
  • 以简洁方式解决复杂递归问题。
  • 代码更接近数学定义。
应用场景函数的核心作用
代码重复封装重复逻辑,实现 DRY 原则
复杂问题分解为模块化组件,降低认知负担
可读性差通过命名自解释代码意图
动态数据处理参数化输入,提升灵活性
错误调试隔离问题范围,简化排查流程
代码复用构建可共享的代码库
递归问题实现自我调用的算法逻辑

通过函数,Python 代码从'一次性脚本'升级为可维护、可扩展的工程化项目。它是构建大型应用、开源库和高效算法的基石。


定义函数

例如,我们写一段代码来求斐波那契数列:

def fib(n):
    """Print a Fibonacci series less than n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b

定义函数使用关键字 def,后跟函数名与括号内的形参列表。函数语句从下一行开始,并且必须缩进。

函数内的第一条语句是字符串时,该字符串就是文档字符串,也称为 docstring。Python 开发者最好养成在代码中加入文档字符串的好习惯。

函数在执行时使用函数局部变量符号表,所有函数变量赋值都存在局部符号表中;引用变量时,首先,在局部符号表里查找变量,然后,是外层函数局部符号表,再是全局符号表,最后是内置名称符号表。因此,尽管可以引用全局变量和外层函数的变量,但最好不要在函数内直接赋值(除非是 global 语句定义的全局变量,或 nonlocal 语句定义的外层函数变量)。

函数对象的概念

在调用函数时会将实际参数(实参)引入到被调用函数的局部符号表中;因此,实参是使用按值调用来传递的(其中的值始终是对象的引用而不是对象的值)。当一个函数调用另外一个函数时,会为该调用创建一个新的局部符号表。

在 Python 中遵循着一切皆对象的原则,无论是变量,函数,亦或是类,在 Python 看来都是对象,因此,函数定义在当前符号表中把函数名与函数对象关联在一起。解释器把函数名指向的对象作为用户自定义函数。

还可以使用其他名称指向同一个函数对象,并访问该函数,例如我们上面写的求斐波那契数列的函数:

def fib(n):
    """Print a Fibonacci series less than n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b

f = fib  # 将函数对象赋值给 f
f(100)  # 调用 f
fib(100)  # 调用 fib()

输出:

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 2971215073 4807526976 7778742049 12586269025 20365011074 32951280099 53316291173 86267571272 139583862445 225851433717 365435296162 591286729879 956722026041 1548008755920 2504730781961 4052739537881 6557470319842 10610209857723 17167680177565 27777890035288 44945570212853 72723460248141 117669030460994 190392490709135 308061521170129 498454011879264 806515533049393 1304969544928657 2111485077978050 3416454622906707 5527939700884757 8944394323791464 14472334024676221 23416728348467685 37889062373143906 61305790721611591 99194853094755497 160500643816367088 259695496911122585 420196140727489673 679891637638612258 1100087778366101931 1779979415994714189 2880067194360816120 4660046610355530309 7540113804716346429 12200160415071876738 19740274219788223167 31940434634860099905 51680708854648323072 83621143489508422977 135301852344156746049 218922995833665169026 354224848177821915075 573147844011487084101 927372692189309000000

如果你学过其他语言,比如 C 语言或者 C++,你可能会认为 fib 不是函数而是一个过程语句,因为它没有返回值。事实上,即使没有 return 语句的函数也有返回值,这个值被称为 None (是一个内置名称,Python 中函数的默认返回值)。通常解释器会屏蔽单独的返回值 None。如果你确有需要可以使用 print() 查看它:

print(fib(0))  # 输出 None

编写不直接输出斐波那契数列运算结果,而是返回运算结果列表的函数也非常简单:只需要在函数中定义列表,将斐波那契数使用 append 接口加入到列表中,最后返回列表对象即可:

def fib2(n):
    """Return a list containing the Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a + b
    return result

f100 = fib2(100)  # 调用它
print(f100)  # 输出结果 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

函数定义详解

函数定义支持可变数量的参数。这里列出三种可以组合使用的形式。

默认值参数

为参数指定默认值是非常有用的方式。调用函数时,可以使用比定义时更少的参数,例如:

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        reply = input(prompt)
        if reply in {'y', 'ye', 'yes'}:
            return True
        if reply in {'n', 'no', 'nop', 'nope'}:
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

该函数有三种调用方式:

  • 只给出必选实参:ask_ok('Do you really want to quit?')
  • 给出一个可选实参:ask_ok('OK to overwrite the file?', 2)
  • 给出所有实参:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

retries 和 reminder 是默认参数。默认参数在函数定义时指定了默认值,调用函数时如果没有为这些参数提供值,就会使用默认值。而 prompt 是位置参数,调用函数时必须按照顺序传递参数,否则就会导致错误!

使用示例:

try:
    # 异常捕获
    result = ask_ok("Do you want to continue? ")
    if result:
        print("You agreed!")
    else:
        print("You disagreed!")
except ValueError as e:
    print(e)

默认值在定义作用域里的函数定义中求值所以:

i = 5
def f(arg=i):
    print(arg)

i = 6
f()

上例输出的是 5。为什么呢??? 这里创建了一个全局变量 i,并将其赋值为 5。在定义函数 f 时,默认参数 arg 的值被设定为当前全局变量 i 的值,也就是 5。需要注意的是,Python 函数的默认参数值是在函数定义时就确定下来的,而不是在函数调用时确定。

将全局变量 i 的值修改为 6,但这并不会影响函数 f 中默认参数 arg 的值,因为 arg 的默认值在函数定义时就已经确定为 5 了。

值得注意的是:默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果。例如,下面的函数会累积后续调用时传递的参数:

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

输出结果如下:

[1]
[1, 2]
[1, 2, 3]

这里定义了函数 f,其中参数 L 的默认值是一个空列表 []。需要特别注意的是,在 Python 中,函数的默认参数是在函数定义时就被创建的,并且只创建一次。也就是说,无论函数被调用多少次,这个默认的列表 L 始终是同一个对象。

如果不想在后续调用之间共享默认值时,应以如下方式编写函数:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

这样修改后,每次调用函数时,如果没有传入 L 参数,都会创建一个新的空列表,输出结果将分别为:

[1]
[2]
[3]

关键字参数

key=value 形式的 关键字参数 也可以用于调用函数。函数示例如下:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

该函数接受一个必选参数(voltage)和三个可选参数(state, action 和 type)。该函数可用下列方式调用:

parrot(1000)  # 1 个位置参数
parrot(voltage=1000)  # 1 个关键字参数
parrot(voltage=1000000, action='VOOOOOM')  # 2 个关键字参数
parrot(action='VOOOOOM', voltage=1000000)  # 2 个关键字参数
parrot('a million', 'bereft of life', 'jump')  # 3 个位置参数
parrot('a thousand', state='pushing up the daisies')  # 1 个位置参数,1 个关键字参数

以下调用函数的方式都无效:

parrot()  # 缺失必需的参数
parrot(voltage=5.0, 'dead')  # 关键字参数后存在非关键字参数
parrot(110, voltage=220)  # 同一个参数重复的值
parrot(actor='John Cleese')  # 未知的关键字参数

函数调用时,关键字参数必须跟在位置参数后面。

例如,以下调用方式是错误的:

# 错误示例,位置参数应在关键字参数之前
parrot(state='dead', 300)

所有传递的关键字参数都必须匹配一个函数接受的参数(比如,actor 不是函数 parrot 的有效参数),关键字参数的顺序并不重要。这也包括必选参数,(比如,parrot(voltage=1000) 也有效)。不能对同一个参数多次赋值,下面就是一个因此限制而失败的例子:

def function(a):
    pass

function(0, a=0)

最后一个形参为 **name 形式时,接收一个字典(映射类型 — dict),该字典包含与函数中已定义形参对应之外的所有关键字参数。**name 形参可以与 *name 形参(下一小节介绍)组合使用(*name 必须在 **name 前面),*name 形参接收一个元组,该元组包含形参列表之外的位置参数。例如,可以定义下面这样的函数:

def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    for kw in keywords:
        print(kw, ":", keywords[kw])

参数解释:

  • kind:这是一个普通的位置参数,调用函数时必须提供该参数的值,用于指定奶酪的种类。
  • *arguments:这是一个可变位置参数,它会将函数调用时传入的除了第一个位置参数(即 kind)之外的所有位置参数收集到一个元组中。在函数内部,可以通过遍历这个元组来处理这些参数。
  • **keywords:这是一个可变关键字参数,它会将函数调用时传入的所有未在函数定义中明确指定的关键字参数收集到一个字典中。字典的键是关键字参数的名称,值是对应的参数值。

该函数可以用如下方式调用:

cheeseshop("Limburger", "It's very runny, sir.", "It's really very, VERY runny, sir.", shopkeeper="Michael Palin", client="John Cleese", sketch="Cheese Shop Sketch")

第一个参数 "Limburger" 被赋值给 kind。 接下来的两个字符串 "It's very runny, sir." 和 "It's really very, VERY runny, sir." 被收集到 arguments 元组中。 最后三个关键字参数 shopkeeper="Michael Palin"、client="John Cleese" 和 sketch="Cheese Shop Sketch" 被收集到 keywords 字典中。

输出结果如下:

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
customer : John Cleese
sketch : Cheese Shop Sketch

在 Python 中,*arguments(可变位置参数)必须放在 **keywords(可变关键字参数)之前,否则会导致语法错误,为什么呢???

Python 函数在调用时,会按照特定的顺序解析传入的参数,具体顺序为:

位置参数 -> 可变位置参数 -> 关键字参数 -> 可变关键字参数。
  • 位置参数:是按照参数定义的顺序依次接收传入的值。
  • 可变位置参数:会收集所有剩余的位置参数(即除了已被普通位置参数接收的部分)到一个元组中。
  • 关键字参数:通过参数名来指定传入的值。
  • 可变关键字参数:会收集所有未在函数定义中明确指定的关键字参数到一个字典中。

如果 **keywords 放在 *arguments 之前,当函数调用时,Python 解释器在解析参数时就会遇到问题。因为 **keywords 会优先收集所有的关键字参数,而此时如果还有剩余的位置参数,就无法正确地将它们收集到 *arguments 中,因为 *arguments 是用来收集位置参数的,而在 **keywords 之后,解释器已经无法区分哪些是位置参数了。

注意,关键字参数在输出结果中的顺序与调用函数时的顺序一致。

特殊参数

默认情况下,参数可以按位置或显式关键字传递给 Python 函数。为了让代码易读、高效,最好限制参数的传递方式,这样,开发者只需查看函数定义,即可确定参数项是仅按位置、按位置或关键字,还是仅按关键字传递。

函数定义如下:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
    # -------------------------------
    # |||| 位置或关键字
    # - 仅限关键字
    # -- 仅限位置

/ 和 * 是可选的。这些符号表明形参如何把参数值传递给函数:位置、位置或关键字、关键字。关键字形参也叫作命名形参。

位置或关键字参数

函数定义中未使用 / 和 * 时,参数可以按位置或关键字传递给函数。

仅位置参数

此处再介绍一些细节,特定形参可以标记为仅限位置。仅限位置 时,形参的顺序很重要,且这些形参不能用关键字传递。仅限位置形参应放在 / (正斜杠)前。/ 用于在逻辑上分割仅限位置形参与其它形参。如果函数定义中没有 /,则表示没有仅限位置形参。 / 后可以是 位置或关键字 或 仅限关键字 形参。

仅限关键字参数

把形参标记为 仅限关键字,表明必须以关键字参数形式传递该形参,应在参数列表中第一个 仅限关键字 形参前添加 *。

简单的函数示例

注意 / 和 * 标记:

def standard_arg(arg):
    print(arg)

def pos_only_arg(arg, /):
    print(arg)

def kwd_only_arg(*, arg):
    print(arg)

def combined_example(pos_only, /, standard, *, kwd_only):
    print(pos_only, standard, kwd_only)

第一个函数定义 standard_arg 是最常见的形式,对调用方式没有任何限制,可以按位置也可以按关键字传递参数:

位置参数:

standard_arg(2)  # 输出 2

关键字参数:

standard_arg(arg=2)  # 输出 2

第二个函数 pos_only_arg 的函数定义中有 /,仅限使用位置形参:

位置形参:

pos_only_arg(1)  # 输出 1

关键字参数:报错!

pos_only_arg(arg=1)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments:'arg'

第三个函数 kwd_only_arg 如在函数定义中通过 * 所指明的那样只允许关键字参数。

位置参数:报错

kwd_only_arg(3)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

关键字参数:

kwd_only_arg(arg=3)  # 输出 3

最后一个函数在同一个函数定义中,使用了全部三种调用惯例:

全部位置参数:报错!

combined_example(1, 2, 3)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: combined_example() takes 2 positional arguments but 3 were given

位置参数和关键字参数:

combined_example(1, 2, kwd_only=3)  # 输出 1 2 3
combined_example(1, standard=2, kwd_only=3)  # 输出 1 2 3

全部关键字参数:报错!

combined_example(pos_only=1, standard=2, kwd_only=3)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: combined_example() got some positional-only arguments passed as keyword arguments:'pos_only'

下面的函数定义中,kwds 把 name 当作键,因此,可能与位置参数 name 产生潜在冲突:

def foo(name, **kwds):
    return 'name' in kwds

调用该函数不可能返回 True,因为关键字 'name' 总与第一个形参绑定。例如:

foo(1, **{'name': 2})
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: foo() got multiple values for argument 'name'

代码报错原因:

当执行 foo(1, **{'name': 2}) 时: 首先,Python 会把位置参数 1 赋值给 name,这是按照位置参数的传递规则来的。 接着,要把字典 {'name': 2} 解包传递给函数。但是这时 Python 发现,name 这个参数已经被位置参数 1 赋值过了,现在又有一个关键字参数 name=2 要进来,就会产生冲突。因为在 Python 里,一个参数不能被多次赋值,所以就会抛出 TypeError: foo() got multiple values for argument 'name' 异常。

正确做法:

  • 加上 /(仅限位置参数)后,就可以了。此时,函数定义把 name 当作位置参数,'name' 也可以作为关键字参数的键:
def foo(name, /, **kwds):
    return 'name' in kwds

foo(1, **{'name': 2})  # 输出 True
  • 这里的 / 起到了关键作用,它把 name 变成了仅限位置参数。这就规定了 name 只能通过位置的方式来传值,不能用关键字参数的方式传值。

调用过程分析

当调用 foo(1, **{'name': 2}) 时: 位置参数 1 会被正常赋值给仅限位置参数 name,这是符合仅限位置参数的传递规则的。 对于字典 {'name': 2},由于 name 是仅限位置参数,字典里的 'name' 键就不会和函数定义里的 name 参数冲突了。**kwds 会把这个 {'name': 2} 字典收集起来,成为一个包含 'name': 2 的字典。 函数内部的 'name' in kwds 语句会去检查 'name' 这个键是否在 kwds 字典里,因为 kwds 字典里确实有 'name' 键,所以函数就会返回 True。

可以把这种情况想象成有两个不同的'区域',一个区域(仅限位置参数 name)只能通过位置传递的方式放东西进去;另一个区域(**kwds 字典)可以接收关键字参数。这样就避免了参数名的冲突。

换句话说,仅限位置形参的名称可以在 **kwds 中使用,而不产生歧义。

小结

以下用例决定哪些形参可以用于函数定义:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

说明:

  • 使用仅限位置形参,可以让用户无法使用形参名。形参名没有实际意义时,强制调用函数的实参顺序时,或同时接收位置形参和关键字时,这种方式很有用。
  • 当形参名有实际意义,且显式名称可以让函数定义更易理解时,阻止用户依赖传递实参的位置时,才使用关键字。
  • 对于 API,使用仅限位置形参,可以防止未来修改形参名时造成破坏性的 API 变动。

函数的变量

变量的作用域

在 Python 中,作用域定义了变量和函数的可见性与生命周期,它决定了在程序的哪些部分可以访问特定的变量或函数。Python 中有四种主要的作用域,按照搜索变量的顺序依次为:局部作用域(Local)、闭包作用域(Enclosing)、全局作用域(Global)和内置作用域(Built-in),通常用 LEGB 规则来描述这个搜索顺序。下面为你详细介绍这四种作用域。

内置作用域(Built-in)

定义:内置作用域是 Python 预先定义好的,包含了所有内置的函数、异常和对象,如 print()、len()、int 等。这些名称在整个 Python 程序中都是可用的,无需额外导入。 示例:

# 直接使用内置函数 print 和 len
print(len([1, 2, 3]))

在这个例子中,print 和 len 都是内置作用域中的函数,可以直接使用。

全局作用域(Global)

定义:全局作用域是在整个模块(即 .py 文件)中定义的作用域。在模块顶层定义的变量和函数都属于全局作用域,它们可以在模块的任何地方被访问,但在函数内部如果要修改全局变量,需要使用 global 关键字。 示例:

# 定义全局变量
global_variable = 10

def print_global_variable():
    # 可以在函数内部访问全局变量
    print(global_variable)

print_global_variable()  # 输出:10

def modify_global_variable():
    global global_variable
    global_variable = 20

modify_global_variable()
print(global_variable)  # 输出:20

在上述代码中,global_variable 是全局变量,可以在函数 print_global_variable 中直接访问。而在 modify_global_variable 函数中,如果要修改全局变量的值,需要使用 global 关键字声明。

闭包作用域(Enclosing)

定义:闭包作用域也称为嵌套作用域,它出现在嵌套函数中。当一个函数内部定义了另一个函数时,内部函数可以访问外部函数的局部变量,这些外部函数的局部变量就处于闭包作用域中。在 Python 中,闭包是一种特殊的对象,它可以捕获并保存外部函数的局部变量状态。 示例:

def outer_function():
    enclosing_variable = 5
    def inner_function():
        # 内部函数可以访问外部函数的局部变量
        print(enclosing_variable)
    return inner_function

closure = outer_function()
closure()  # 输出:5

在这个例子中,inner_function 是嵌套在 outer_function 内部的函数,enclosing_variable 是 outer_function 的局部变量,对于 inner_function 来说,它处于闭包作用域中,因此 inner_function 可以访问这个变量。

局部作用域(Local)

定义:局部作用域是在函数内部定义的作用域。函数内部定义的变量和参数都属于局部作用域,它们只能在函数内部被访问,函数执行结束后,局部变量会被销毁。 示例:

def local_scope_example():
    local_variable = 3
    print(local_variable)

local_scope_example()  # 输出:3
# print(local_variable)  # 会报错,因为 local_variable 是局部变量,外部无法访问

在 local_scope_example 函数内部定义的 local_variable 是局部变量,只能在函数内部访问,在函数外部尝试访问会引发 NameError 异常。

函数中变量的查找顺序

在 Python 中,函数内部的变量查找遵循 LEGB 规则,即按照以下顺序逐层查找变量: L → E → G → B

Local(局部作用域) → Enclosing(闭包外层) → Global(全局) → Built-in(内置)
  1. Local(局部作用域)

优先级最高:首先在函数内部查找变量

示例:

def func():
    x = 10  # 局部变量
    print(x)  # 输出 10(优先查找局部)

func()
  1. Enclosing(闭包外层)

适用于嵌套函数:在外层函数的作用域中查找

示例:

def outer():
    y = 20
    def inner():
        print(y)  # 查找闭包外层的 y → 20
    inner()

outer()
  1. Global(全局作用域)

模块级变量:在函数外部定义的变量

示例:

z = 30

def func():
    print(z)  # 查找全局的 z → 30

func()
  1. Built-in(内置作用域)

Python 内置函数/变量:如 len, str, True 等

示例:

def func():
    print(len)  # 找到内置函数 len → <built-in function len>

func()
关键注意
  • 赋值操作的影响

若在函数内部对变量赋值,Python 会默认将其视为局部变量(即使全局存在同名变量)

x = 100

def func():
    x = 200  # 创建新的局部变量
    print(x)  # 输出 200

func()
print(x)  # 输出 100(全局变量未变)
  • global 关键字 强制使用全局变量
x = 100

def func():
    global x
    x = 200  # 修改全局变量
    print(x)  # 输出 200

func()
print(x)  # 输出 200
  • nonlocal 关键字 在嵌套函数中使用外层非全局变量
def outer():
    x = 10
    def inner():
        nonlocal x
        x = 20  # 修改闭包外层的 x
    inner()
    print(x)  # 输出 20

outer()
  • 变量未定义时的报错,若所有作用域均未找到变量:
def func():
    print(undefined_var)  # 报错 NameError

func()

是否是否是否是否函数内访问变量是局部变量?使用局部变量是闭包外层变量?使用外层变量是全局变量?使用全局变量是内置变量?使用内置变量抛出 NameError

目录

  1. 什么是函数
  2. 函数的优势
  3. 重复代码示例(计算圆面积)
  4. 使用函数优化
  5. 未使用函数的复杂逻辑
  6. 使用函数模块化
  7. 难读的代码
  8. 使用函数自解释
  9. 硬编码的欢迎语
  10. 参数化函数
  11. utils.py
  12. main.py
  13. 定义函数
  14. 函数对象的概念
  15. 函数定义详解
  16. 默认值参数
  17. 关键字参数
  18. 错误示例,位置参数应在关键字参数之前
  19. 特殊参数
  20. 位置或关键字参数
  21. 仅位置参数
  22. 仅限关键字参数
  23. 简单的函数示例
  24. Traceback (most recent call last):
  25. File "<stdin>", line 1, in <module>
  26. TypeError: posonlyarg() got some positional-only arguments passed as keyword arguments:'arg'
  27. Traceback (most recent call last):
  28. File "<stdin>", line 1, in <module>
  29. TypeError: kwdonlyarg() takes 0 positional arguments but 1 was given
  30. Traceback (most recent call last):
  31. File "<stdin>", line 1, in <module>
  32. TypeError: combined_example() takes 2 positional arguments but 3 were given
  33. Traceback (most recent call last):
  34. File "<stdin>", line 1, in <module>
  35. TypeError: combinedexample() got some positional-only arguments passed as keyword arguments:'posonly'
  36. Traceback (most recent call last):
  37. File "<stdin>", line 1, in <module>
  38. TypeError: foo() got multiple values for argument 'name'
  39. 小结
  40. 函数的变量
  41. 变量的作用域
  42. 内置作用域(Built-in)
  43. 直接使用内置函数 print 和 len
  44. 全局作用域(Global)
  45. 定义全局变量
  46. 闭包作用域(Enclosing)
  47. 局部作用域(Local)
  48. print(localvariable) # 会报错,因为 localvariable 是局部变量,外部无法访问
  49. 函数中变量的查找顺序
  50. 关键注意
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • nanobot AI Agent 框架原理与架构深度解析
  • Win11 本地部署 OpenClaw 并通过 WSL 配置飞书机器人
  • AI Agent 执行管控与安全沙箱:原理与最佳实践
  • WhisperX 语音识别实战:从安装到说话人分离
  • 基于 SpringBoot2 与 Vue3 的旅游网站系统设计与实现
  • 前端代码可读性优化核心原则
  • Python 和 PyTorch 的核心区别
  • 哈希算法详解:原理、实现与安全应用
  • Git 原理与使用进阶:远程协作及企业级开发模型
  • ClaudeCode 跨平台安装指南:Windows、Linux 与 macOS
  • Trae 与 Vizro 集成:低代码构建数据可视化仪表板
  • Python+ROS2 通用智能系统工业级通信框架
  • C++ 二叉搜索树:概念、性能分析、增删查及实现
  • 双指针算法实战:移动零与复写零
  • 万方 AIGC 检测未通过?多款降 AI 工具实测效果分析
  • 电商系统商品管理模块设计与实现
  • 单核 CPU 可抢占优先级任务调度算法解析
  • DeepSeek 各版本说明与优缺点分析
  • MySQL 数据库核心操作:创建、编码、备份与连接管理
  • 嵌入式CAN通信:C++与SocketCAN的现代封装实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online