当前位置: 代码网 > it编程>前端脚本>Python > 系统讲解Python函数中global与nonlocal关键字的使用

系统讲解Python函数中global与nonlocal关键字的使用

2026年07月22日 Python 我要评论
一、开篇:变量的"活动范围"想象每个变量都有一张"通行证",规定了它能在哪些代码区域被访问。有些变量是整个程序通用的(全局变量),有些只在某个函数内部有效(局部

一、开篇:变量的"活动范围"

想象每个变量都有一张"通行证",规定了它能在哪些代码区域被访问。有些变量是整个程序通用的(全局变量),有些只在某个函数内部有效(局部变量)。这种"通行范围",就是作用域(scope)

先看一个经典的困惑:

# 这段代码会输出什么?
x = 10  # 全局变量

def my_function():
    print(x)   # 访问全局变量x
    x = 20     # 嗯?这行在print后面,会影响前面的print吗?
    print(x)

# my_function()  # 如果运行这行,会报错还是正常输出?
# 答案是:unboundlocalerror!
# 因为x=20让python认为x是局部变量,但print(x)时x还没被赋值

这段代码揭示了一个关键事实:python在编译函数时,会根据赋值语句来决定变量的作用域。如果函数内部有对某个变量的赋值,python就认为它是局部变量——即使赋值语句在使用语句的后面。

这篇文章,我们就来彻底搞懂python的变量作用域——legb规则、global关键字、nonlocal关键字,以及它们在实际开发中的应用。

二、legb规则:python的变量查找法则

2.1 四层作用域

# python变量查找遵循legb规则(由内向外):
# l - local(局部作用域):函数内部
# e - enclosing(嵌套作用域):外层函数的作用域
# g - global(全局作用域):模块级别
# b - built-in(内置作用域):python内置的名字

# ========== 内置作用域 ==========
# python启动时就存在,如print、len、range等

# ========== 全局作用域 ==========
# 模块级别的变量和函数
global_var = "我是全局变量"

def outer_function():
    # ========== 嵌套作用域 ==========
    enclosing_var = "我是嵌套变量(属于outer_function)"

    def inner_function():
        # ========== 局部作用域 ==========
        local_var = "我是局部变量(属于inner_function)"

        print(f"局部: {local_var}")
        print(f"嵌套: {enclosing_var}")
        print(f"全局: {global_var}")
        print(f"内置: {len}")  # python内置函数

    inner_function()

outer_function()
# 局部: 我是局部变量(属于inner_function)
# 嵌套: 我是嵌套变量(属于outer_function)
# 全局: 我是全局变量
# 内置: <built-in function len>

2.2 legb查找过程的直观演示

# ⌨️ 详细演示:python按顺序查找变量
# 找到就停,不到最后一层不报错

name = "global: 张三"  # g层

def outer():
    name = "enclosing: 李四"  # e层

    def inner():
        name = "local: 王五"  # l层
        print(f"inner中的name: {name}")
        # 输出 → local: 王五 (找到了,停止)

    def inner_no_local():
        # 这层没有name
        print(f"inner_no_local中的name: {name}")
        # 输出 → enclosing: 李四 (l层没有,找到了e层)

    def inner_use_global():
        # l层和e层都没有
        print(f"inner_use_global中的name: {name}")
        # 等等——e层有name="李四",所以输出→enclosing: 李四

    inner()
    inner_no_local()
    inner_use_global()

outer()

# 在全局作用域中访问
print(f"全局中的name: {name}")
# 输出 → global: 张三

2.3 赋值修改了作用域

# ⚠️ 核心规则:在函数内部,如果对变量进行了赋值
# python就认为这个变量是该函数的局部变量
# 即使外面有同名的全局变量!

x = 100  # 全局变量

def read_only():
    """只读取,不赋值——可以访问全局变量"""
    print(f"read_only: x = {x}")  # 正常,访问全局x

def has_assignment():
    """有赋值语句——x被视为局部变量"""
    # print(f"赋值前: x = {x}")  # unboundlocalerror!
    x = 200  # 这个赋值让python将x当作局部变量
    print(f"赋值后: x = {x}")

read_only()   # read_only: x = 100
has_assignment()
# 如果取消注释print(f"赋值前: x = {x}"),会报错:
# unboundlocalerror: local variable 'x' referenced before assignment

# 为什么?python在编译函数时扫描了整个函数体
# 发现了 x = 200 这行赋值语句
# 于是决定:x是这个函数的局部变量
# 所以,赋值之前的print(x)也在找局部变量x——但x还没被赋值!

三、global关键字:在函数内部修改全局变量

3.1 global的基本用法

# global关键字:声明一个变量是全局变量
# 这样即使在函数内部对它赋值,也是修改全局的那个

count = 0  # 全局计数器

def increment():
    """使用global声明,修改全局变量"""
    global count
    count += 1
    print(f"计数器: {count}")

def increment_bad():
    """没有global——创建了局部变量"""
    count = 999  # 这是局部变量,不影响全局的count
    print(f"局部count: {count}")

increment()      # 计数器: 1
print(f"全局count: {count}")  # 全局count: 1

increment_bad()  # 局部count: 999
print(f"全局count: {count}")  # 全局count: 1  —— 没变!

increment()      # 计数器: 2
print(f"全局count: {count}")  # 全局count: 2  —— 继续累加

3.2 global的多种使用场景

# 场景一:同时声明多个全局变量
a = 1
b = 2
c = 3

def modify_all():
    global a, b, c  # 一次声明多个
    a = 10
    b = 20
    c = 30

modify_all()
print(a, b, c)  # 10 20 30

# 场景二:在嵌套函数中使用global
total = 0

def outer():
    def inner():
        global total    # 声明使用的是全局的total
        total += 1      # 修改全局变量
    inner()

outer()
print(total)  # 1

# ⚠️ 注意:global声明的是全局变量,不是外层函数的变量
# 对于嵌套函数中外层函数的变量,需要用nonlocal(下一节讲)

# 场景三:在函数内部创建全局变量
def create_global():
    global new_variable
    new_variable = "我在函数内部被创建为全局变量!"
    print("全局变量已创建")

create_global()
print(new_variable)  # 我在函数内部被创建为全局变量!
# 💡 虽然可以这样做,但强烈不推荐——让代码难以追踪

3.3 什么时候应该(和不应该)使用global

# ✅ 适用场景:全局状态管理(但要谨慎)
# 例如:应用配置、全局计数器、缓存

app_config = {
    "debug": false,
    "log_level": "info",
    "max_connections": 100,
}

def set_debug_mode(enabled):
    """修改全局配置"""
    global app_config
    app_config["debug"] = enabled
    print(f"调试模式: {'开启' if enabled else '关闭'}")

# ⚠️ 但要注意:如果你只是修改字典的内容(而不是重新赋值字典变量)
# 可以不需要global!

def set_debug_mode_no_global(enabled):
    """不需要global——因为我们没有给app_config重新赋值"""
    app_config["debug"] = enabled  # 修改的是字典的内容,不是变量本身
    print(f"调试模式: {'开启' if enabled else '关闭'}")

# 💡 关键区别:
# app_config["key"] = value  → 不需要global(修改对象的内容)
# app_config = new_dict      → 需要global(给变量重新赋值)

# ❌ 不推荐:滥用global
# 这种代码让人抓狂——不知道变量从哪里冒出来的
def do_work():
    global result     # 突然出现一个全局变量
    result = 42       # 调用者完全不知道result被设置了
    global status
    status = "done"
    # ... 更多代码 ...

# ✅ 更好的做法:用返回值
def do_work_better():
    result = 42
    status = "done"
    return result, status  # 明确地返回

# 或者用类封装
class worker:
    def __init__(self):
        self.result = none
        self.status = "idle"

    def do_work(self):
        self.result = 42
        self.status = "done"
        return self.result, self.status

四、nonlocal关键字:修改外层函数的变量

4.1 nonlocal的基本概念

# nonlocal:在嵌套函数中,声明变量来自外层函数(enclosing作用域)
# 它不是全局的(不是g层),也不是局部的(不是l层),而是中间的e层

def outer():
    message = "hello"  # 外层函数的局部变量

    def inner():
        nonlocal message  # 声明:我要用外层的message,不是局部的
        message = "你好"  # 修改的是外层的message
        print(f"inner中: {message}")

    print(f"调用inner前: {message}")
    inner()
    print(f"调用inner后: {message}")
    # 输出:
    # 调用inner前: hello
    # inner中: 你好
    # 调用inner后: 你好    ← message被inner修改了!

outer()

4.2 nonlocal vs global 的区别

# ⌨️ 对比实验:global vs nonlocal

x = "全局x"

def outer():
    x = "外层x"

    def inner_global():
        global x           # 声明使用全局变量x
        x = "被global修改"
        print(f"inner_global中: x={x}")

    def inner_nonlocal():
        nonlocal x         # 声明使用外层变量x
        x = "被nonlocal修改"
        print(f"inner_nonlocal中: x={x}")

    print(f"outer开始: x={x}")
    inner_global()
    print(f"inner_global后, outer中的x: {x}")   # 还是"外层x"——没变!
    inner_nonlocal()
    print(f"inner_nonlocal后, outer中的x: {x}")  # 变成"被nonlocal修改"
    print(f"全局x: {x}")                          # 全局x变成了"被global修改"

outer()
print(f"最终全局x: {x}")
# 最终全局x: 被global修改

# 💡 总结:
# global → 跳转到g层(全局作用域)
# nonlocal → 跳转到e层(嵌套作用域,最近的外层函数)

4.3 nonlocal的典型应用

# 场景一:计数器(闭包)
def make_counter(start=0):
    """创建一个计数器函数"""
    count = start  # 外层变量

    def counter():
        nonlocal count
        count += 1  # 修改外层变量
        return count

    return counter

counter1 = make_counter()
print(counter1())  # 1
print(counter1())  # 2
print(counter1())  # 3

counter2 = make_counter(100)
print(counter2())  # 101
print(counter2())  # 102
print(counter1())  # 4  —— counter1独立运行

# 场景二:求平均值(不断累加)
def make_averager():
    """创建一个求平均值的函数,持续接收新数据"""
    total = 0
    count = 0

    def averager(new_value):
        nonlocal total, count
        total += new_value
        count += 1
        return total / count

    return averager

avg = make_averager()
print(avg(10))  # 10.0
print(avg(20))  # 15.0
print(avg(30))  # 20.0
print(avg(40))  # 25.0

# 💡 如果没有nonlocal,这段代码会直接报错
# 因为 averager 中的 total += new_value 相当于 total = total + new_value
# python会认为total是局部变量,但它还没被赋值!

4.4 nonlocal的查找规则

# nonlocal查找最近的外层作用域(不包括全局作用域)

def level1():
    x = "level1"

    def level2():
        x = "level2"

        def level3():
            nonlocal x  # 找最近的包含x的外层——是level2的x
            x = "被level3修改"
            print(f"level3中: x={x}")

        print(f"level3调用前, level2的x: {x}")
        level3()
        print(f"level3调用后, level2的x: {x}")

    level2()

level1()
# level3调用前, level2的x: level2
# level3中: x=被level3修改
# level3调用后, level2的x: 被level3修改

# ⚠️ 如果外层所有函数都没有这个变量,nonlocal会报错
def func():
    def inner():
        # nonlocal nonexistent_var  # syntaxerror! 外层没有这个变量
        pass
    inner()

五、闭包中的变量绑定

5.1 python的变量绑定是"引用"而非"拷贝"

# 💡 闭包中的变量绑定是"晚期绑定"
# 捕获的是变量的引用,而不是变量的值

# ⚠️ 经典陷阱:lambda在循环中
functions = []
for i in range(5):
    functions.append(lambda: i)  # 每个lambda都引用同一个i

# 循环结束后i=4,所有lambda看到的i都是4
for f in functions:
    print(f(), end=" ")  # 4 4 4 4 4  —— 不是0 1 2 3 4!
print()

# ✅ 解决方案:使用默认参数"冻结"当前值
functions_v2 = []
for i in range(5):
    functions_v2.append(lambda x=i: x)  # 默认参数在定义时求值

for f in functions_v2:
    print(f(), end=" ")  # 0 1 2 3 4
print()

# ✅ 或者用函数工厂
def make_printer(n):
    return lambda: n  # n是make_printer的参数,每次调用都创建新的作用域

functions_v3 = [make_printer(i) for i in range(5)]
for f in functions_v3:
    print(f(), end=" ")  # 0 1 2 3 4

5.2 闭包中修改可变对象

# 如果闭包中的变量是可变对象(list, dict等)
# 修改其内容不需要nonlocal
# 只有给变量名重新赋值才需要nonlocal

def make_basket():
    """购物篮——存放可变列表对象"""
    items = []  # 可变列表

    def add_item(item):
        # 不需要nonlocal!因为我们在修改列表的内容,不是给items重新赋值
        items.append(item)
        return len(items)

    def get_items():
        return list(items)  # 返回副本

    return add_item, get_items

add, get = make_basket()
print(add("苹果"))    # 1
print(add("香蕉"))    # 2
print(add("橙子"))    # 3
print(get())          # ['苹果', '香蕉', '橙子']

# 💡 关键区别:
# items.append(x)  → 不需要nonlocal(修改对象内容,items指向同一个对象)
# items = []       → 需要nonlocal(给变量重新赋值,items指向新对象)

六、globals()和locals()查看作用域

# ⌨️ 使用内置函数查看当前作用域中的所有变量

global_x = 10
global_y = 20

def demo():
    local_a = 100
    local_b = 200

    print("=== 局部作用域 (locals()) ===")
    for name, value in locals().items():
        print(f"  {name} = {value}")

    print("\n=== 全局作用域 (globals()) ===")
    # 只显示我们定义的变量
    for name, value in globals().items():
        if not name.startswith("_"):
            print(f"  {name} = {value}")

demo()

# 在模块级别,locals()和globals()返回同一个字典
print("\n=== 模块级别 ===")
print(f"locals() is globals(): {locals() is globals()}")  # true

七、实战案例

7.1 实现函数调用计数器

def make_tracked_function(func):
    """装饰器:统计函数被调用的次数"""
    call_count = 0

    def wrapper(*args, **kwargs):
        nonlocal call_count
        call_count += 1
        print(f"→ {func.__name__} 第{call_count}次被调用")
        result = func(*args, **kwargs)
        return result

    def get_count():
        """查看调用次数"""
        return call_count

    def reset_count():
        """重置计数器"""
        nonlocal call_count
        call_count = 0

    wrapper.get_count = get_count
    wrapper.reset_count = reset_count
    return wrapper

@make_tracked_function
def calculate(a, b):
    return a + b

print(calculate(1, 2))  # → calculate 第1次被调用  → 3
print(calculate(3, 4))  # → calculate 第2次被调用  → 7
print(calculate(5, 6))  # → calculate 第3次被调用  → 11
print(f"总调用次数: {calculate.get_count()}")  # 3
calculate.reset_count()
print(f"重置后: {calculate.get_count()}")  # 0

7.2 配置管理器

# 使用作用域管理配置——避免全局变量污染

def create_config_manager(initial_config=none):
    """创建配置管理器(使用闭包封装配置数据)"""
    if initial_config is none:
        config = {}
    else:
        config = dict(initial_config)  # 复制一份,避免外部修改

    def get(key, default=none):
        """获取配置项"""
        return config.get(key, default)

    def set(key, value):
        """设置配置项"""
        nonlocal config  # 如果set需要替换整个config,就需要nonlocal
        config[key] = value
        print(f"配置已更新: {key} = {value}")

    def update(new_config):
        """批量更新配置"""
        config.update(new_config)

    def get_all():
        """获取所有配置"""
        return dict(config)  # 返回副本

    def reset(initial_config=none):
        """重置配置"""
        nonlocal config
        if initial_config is none:
            config = {}
        else:
            config = dict(initial_config)

    return get, set, update, get_all, reset

# 使用配置管理器
get_config, set_config, update_config, get_all_config, reset_config = create_config_manager({
    "debug": false,
    "host": "localhost",
    "port": 8080,
})

print(get_config("host"))          # localhost
set_config("debug", true)          # 配置已更新: debug = true
update_config({"timeout": 30, "retries": 3})
print(get_all_config())
# {'debug': true, 'host': 'localhost', 'port': 8080, 'timeout': 30, 'retries': 3}

八、最佳实践

# ✅ 最佳实践一:优先使用局部变量
# 局部变量访问最快,且不污染全局作用域

# ✅ 最佳实践二:避免使用global
# 用函数参数和返回值来传递数据,而不是修改全局状态
# ❌ 不推荐
total = 0
def add_to_total(x):
    global total
    total += x
# ✅ 推荐
def add(x, y):
    return x + y

# ✅ 最佳实践三:模块级别的常量用全大写
# pep 8规范:全局常量用大写字母+下划线
max_connections = 100
default_timeout = 30
database_url = "mysql://localhost/mydb"

# ✅ 最佳实践四:用类代替闭包管理复杂状态
# 当闭包变得复杂时(多个函数、多个状态变量),用类更清晰
# 闭包适合简单场景(1-2个状态)
# 类适合复杂场景(多个方法和属性)

# ✅ 最佳实践五:理解"修改"和"重新赋值"的区别
# my_list.append(x)   → 修改对象,不需要nonlocal/global
# my_list = [x]       → 重新赋值,需要nonlocal/global
# my_dict["key"] = v  → 修改对象,不需要nonlocal/global
# my_dict = {"k": v}  → 重新赋值,需要nonlocal/global

九、总结

python的作用域规则是legb——从local到enclosing到global到built-in,逐层向外查找。理解这个规则是写出正确python代码的基础。

核心要点:

关键字作用跳转层级使用场景
(无需声明)读取变量legb逐层查找只读访问外部变量
global修改全局变量跳到g层修改模块级变量
nonlocal修改外层变量跳到最近的e层闭包中修改捕获的变量

使用建议:

  1. 优先使用局部变量——最快、最安全
  2. 谨慎使用global——能不用就不用,用参数和返回值代替
  3. nonlocal是闭包的好伙伴——但复杂场景用类替代
  4. 理解"修改对象"vs"重新赋值"——前者不需要关键字声明

掌握了作用域规则,你就真正理解了python的变量世界。下一篇文章,我们将深入探讨全局变量与局部变量的优先级规则——以及那些让你意想不到的边界情况。

以上就是系统讲解python函数中global与nonlocal关键字的使用的详细内容,更多关于python global与nonlocal关键字的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com