最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

系統(tǒng)講解Python函數(shù)中g(shù)lobal與nonlocal關(guān)鍵字的使用

 更新時(shí)間:2026年07月22日 09:04:14   作者:星河耀銀海  
Python變量作用域總是搞混,這篇文章就用最通俗的語言為你拆解LEGB規(guī)則,從Local到Built-in逐層查找,還會詳細(xì)講解global和nonlocal關(guān)鍵字的用法、閉包中的變量綁定,以及實(shí)戰(zhàn)案例,讀完你就能徹底掌握Python的變量世界,寫出更安全的代碼

一、開篇:變量的"活動范圍"

想象每個(gè)變量都有一張"通行證",規(guī)定了它能在哪些代碼區(qū)域被訪問。有些變量是整個(gè)程序通用的(全局變量),有些只在某個(gè)函數(shù)內(nèi)部有效(局部變量)。這種"通行范圍",就是作用域(Scope)。

先看一個(gè)經(jīng)典的困惑:

# 這段代碼會輸出什么?
x = 10  # 全局變量

def my_function():
    print(x)   # 訪問全局變量x
    x = 20     # 嗯?這行在print后面,會影響前面的print嗎?
    print(x)

# my_function()  # 如果運(yùn)行這行,會報(bào)錯(cuò)還是正常輸出?
# 答案是:UnboundLocalError!
# 因?yàn)閤=20讓Python認(rèn)為x是局部變量,但print(x)時(shí)x還沒被賦值

這段代碼揭示了一個(gè)關(guān)鍵事實(shí):Python在編譯函數(shù)時(shí),會根據(jù)賦值語句來決定變量的作用域。如果函數(shù)內(nèi)部有對某個(gè)變量的賦值,Python就認(rèn)為它是局部變量——即使賦值語句在使用語句的后面。

這篇文章,我們就來徹底搞懂Python的變量作用域——LEGB規(guī)則、global關(guān)鍵字、nonlocal關(guān)鍵字,以及它們在實(shí)際開發(fā)中的應(yīng)用。

二、LEGB規(guī)則:Python的變量查找法則

2.1 四層作用域

# Python變量查找遵循LEGB規(guī)則(由內(nèi)向外):
# L - Local(局部作用域):函數(shù)內(nèi)部
# E - Enclosing(嵌套作用域):外層函數(shù)的作用域
# G - Global(全局作用域):模塊級別
# B - Built-in(內(nèi)置作用域):Python內(nèi)置的名字

# ========== 內(nèi)置作用域 ==========
# Python啟動時(shí)就存在,如print、len、range等

# ========== 全局作用域 ==========
# 模塊級別的變量和函數(shù)
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"內(nèi)置: {len}")  # Python內(nèi)置函數(shù)

    inner_function()

outer_function()
# 局部: 我是局部變量(屬于inner_function)
# 嵌套: 我是嵌套變量(屬于outer_function)
# 全局: 我是全局變量
# 內(nèi)置: <built-in function len>

2.2 LEGB查找過程的直觀演示

# ?? 詳細(xì)演示:Python按順序查找變量
# 找到就停,不到最后一層不報(bào)錯(cuò)

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 賦值修改了作用域

# ?? 核心規(guī)則:在函數(shù)內(nèi)部,如果對變量進(jìn)行了賦值
# Python就認(rèn)為這個(gè)變量是該函數(shù)的局部變量
# 即使外面有同名的全局變量!

x = 100  # 全局變量

def read_only():
    """只讀取,不賦值——可以訪問全局變量"""
    print(f"read_only: x = {x}")  # 正常,訪問全局x

def has_assignment():
    """有賦值語句——x被視為局部變量"""
    # print(f"賦值前: x = {x}")  # UnboundLocalError!
    x = 200  # 這個(gè)賦值讓Python將x當(dāng)作局部變量
    print(f"賦值后: x = {x}")

read_only()   # read_only: x = 100
has_assignment()
# 如果取消注釋print(f"賦值前: x = {x}"),會報(bào)錯(cuò):
# UnboundLocalError: local variable 'x' referenced before assignment

# 為什么?Python在編譯函數(shù)時(shí)掃描了整個(gè)函數(shù)體
# 發(fā)現(xiàn)了 x = 200 這行賦值語句
# 于是決定:x是這個(gè)函數(shù)的局部變量
# 所以,賦值之前的print(x)也在找局部變量x——但x還沒被賦值!

三、global關(guān)鍵字:在函數(shù)內(nèi)部修改全局變量

3.1 global的基本用法

# global關(guān)鍵字:聲明一個(gè)變量是全局變量
# 這樣即使在函數(shù)內(nèi)部對它賦值,也是修改全局的那個(gè)

count = 0  # 全局計(jì)數(shù)器

def increment():
    """使用global聲明,修改全局變量"""
    global count
    count += 1
    print(f"計(jì)數(shù)器: {count}")

def increment_bad():
    """沒有g(shù)lobal——?jiǎng)?chuàng)建了局部變量"""
    count = 999  # 這是局部變量,不影響全局的count
    print(f"局部count: {count}")

increment()      # 計(jì)數(shù)器: 1
print(f"全局count: {count}")  # 全局count: 1

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

increment()      # 計(jì)數(shù)器: 2
print(f"全局count: {count}")  # 全局count: 2  —— 繼續(xù)累加

3.2 global的多種使用場景

# 場景一:同時(shí)聲明多個(gè)全局變量
a = 1
b = 2
c = 3

def modify_all():
    global a, b, c  # 一次聲明多個(gè)
    a = 10
    b = 20
    c = 30

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

# 場景二:在嵌套函數(shù)中使用global
total = 0

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

outer()
print(total)  # 1

# ?? 注意:global聲明的是全局變量,不是外層函數(shù)的變量
# 對于嵌套函數(shù)中外層函數(shù)的變量,需要用nonlocal(下一節(jié)講)

# 場景三:在函數(shù)內(nèi)部創(chuàng)建全局變量
def create_global():
    global new_variable
    new_variable = "我在函數(shù)內(nèi)部被創(chuàng)建為全局變量!"
    print("全局變量已創(chuàng)建")

create_global()
print(new_variable)  # 我在函數(shù)內(nèi)部被創(chuàng)建為全局變量!
# ?? 雖然可以這樣做,但強(qiáng)烈不推薦——讓代碼難以追蹤

3.3 什么時(shí)候應(yīng)該(和不應(yīng)該)使用global

# ? 適用場景:全局狀態(tài)管理(但要謹(jǐn)慎)
# 例如:應(yīng)用配置、全局計(jì)數(shù)器、緩存

app_config = {
    "debug": False,
    "log_level": "INFO",
    "max_connections": 100,
}

def set_debug_mode(enabled):
    """修改全局配置"""
    global app_config
    app_config["debug"] = enabled
    print(f"調(diào)試模式: {'開啟' if enabled else '關(guān)閉'}")

# ?? 但要注意:如果你只是修改字典的內(nèi)容(而不是重新賦值字典變量)
# 可以不需要global!

def set_debug_mode_no_global(enabled):
    """不需要global——因?yàn)槲覀儧]有給app_config重新賦值"""
    app_config["debug"] = enabled  # 修改的是字典的內(nèi)容,不是變量本身
    print(f"調(diào)試模式: {'開啟' if enabled else '關(guān)閉'}")

# ?? 關(guān)鍵區(qū)別:
# app_config["key"] = value  → 不需要global(修改對象的內(nèi)容)
# app_config = new_dict      → 需要global(給變量重新賦值)

# ? 不推薦:濫用global
# 這種代碼讓人抓狂——不知道變量從哪里冒出來的
def do_work():
    global result     # 突然出現(xiàn)一個(gè)全局變量
    result = 42       # 調(diào)用者完全不知道result被設(shè)置了
    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關(guān)鍵字:修改外層函數(shù)的變量

4.1 nonlocal的基本概念

# nonlocal:在嵌套函數(shù)中,聲明變量來自外層函數(shù)(Enclosing作用域)
# 它不是全局的(不是G層),也不是局部的(不是L層),而是中間的E層

def outer():
    message = "Hello"  # 外層函數(shù)的局部變量

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

    print(f"調(diào)用inner前: {message}")
    inner()
    print(f"調(diào)用inner后: {message}")
    # 輸出:
    # 調(diào)用inner前: Hello
    # inner中: 你好
    # 調(diào)用inner后: 你好    ← message被inner修改了!

outer()

4.2 nonlocal vs global 的區(qū)別

# ?? 對比實(shí)驗(yàn):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修改

# ?? 總結(jié):
# global → 跳轉(zhuǎn)到G層(全局作用域)
# nonlocal → 跳轉(zhuǎn)到E層(嵌套作用域,最近的外層函數(shù))

4.3 nonlocal的典型應(yīng)用

# 場景一:計(jì)數(shù)器(閉包)
def make_counter(start=0):
    """創(chuàng)建一個(gè)計(jì)數(shù)器函數(shù)"""
    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獨(dú)立運(yùn)行

# 場景二:求平均值(不斷累加)
def make_averager():
    """創(chuàng)建一個(gè)求平均值的函數(shù),持續(xù)接收新數(shù)據(jù)"""
    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,這段代碼會直接報(bào)錯(cuò)
# 因?yàn)?averager 中的 total += new_value 相當(dāng)于 total = total + new_value
# Python會認(rèn)為total是局部變量,但它還沒被賦值!

4.4 nonlocal的查找規(guī)則

# 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調(diào)用前, level2的x: {x}")
        level3()
        print(f"level3調(diào)用后, level2的x: {x}")

    level2()

level1()
# level3調(diào)用前, level2的x: level2
# level3中: x=被level3修改
# level3調(diào)用后, level2的x: 被level3修改

# ?? 如果外層所有函數(shù)都沒有這個(gè)變量,nonlocal會報(bào)錯(cuò)
def func():
    def inner():
        # nonlocal nonexistent_var  # SyntaxError! 外層沒有這個(gè)變量
        pass
    inner()

五、閉包中的變量綁定

5.1 Python的變量綁定是"引用"而非"拷貝"

# ?? 閉包中的變量綁定是"晚期綁定"
# 捕獲的是變量的引用,而不是變量的值

# ?? 經(jīng)典陷阱:lambda在循環(huán)中
functions = []
for i in range(5):
    functions.append(lambda: i)  # 每個(gè)lambda都引用同一個(gè)i

# 循環(huán)結(jié)束后i=4,所有l(wèi)ambda看到的i都是4
for f in functions:
    print(f(), end=" ")  # 4 4 4 4 4  —— 不是0 1 2 3 4!
print()

# ? 解決方案:使用默認(rèn)參數(shù)"凍結(jié)"當(dāng)前值
functions_v2 = []
for i in range(5):
    functions_v2.append(lambda x=i: x)  # 默認(rèn)參數(shù)在定義時(shí)求值

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

# ? 或者用函數(shù)工廠
def make_printer(n):
    return lambda: n  # n是make_printer的參數(shù),每次調(diào)用都創(chuàng)建新的作用域

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等)
# 修改其內(nèi)容不需要nonlocal
# 只有給變量名重新賦值才需要nonlocal

def make_basket():
    """購物籃——存放可變列表對象"""
    items = []  # 可變列表

    def add_item(item):
        # 不需要nonlocal!因?yàn)槲覀冊谛薷牧斜淼膬?nèi)容,不是給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())          # ['蘋果', '香蕉', '橙子']

# ?? 關(guān)鍵區(qū)別:
# items.append(x)  → 不需要nonlocal(修改對象內(nèi)容,items指向同一個(gè)對象)
# items = []       → 需要nonlocal(給變量重新賦值,items指向新對象)

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

# ?? 使用內(nèi)置函數(shù)查看當(dāng)前作用域中的所有變量

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()返回同一個(gè)字典
print("\n=== 模塊級別 ===")
print(f"locals() is globals(): {locals() is globals()}")  # True

七、實(shí)戰(zhàn)案例

7.1 實(shí)現(xiàn)函數(shù)調(diào)用計(jì)數(shù)器

def make_tracked_function(func):
    """裝飾器:統(tǒng)計(jì)函數(shù)被調(diào)用的次數(shù)"""
    call_count = 0

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

    def get_count():
        """查看調(diào)用次數(shù)"""
        return call_count

    def reset_count():
        """重置計(jì)數(shù)器"""
        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次被調(diào)用  → 3
print(calculate(3, 4))  # → calculate 第2次被調(diào)用  → 7
print(calculate(5, 6))  # → calculate 第3次被調(diào)用  → 11
print(f"總調(diào)用次數(shù): {calculate.get_count()}")  # 3
calculate.reset_count()
print(f"重置后: {calculate.get_count()}")  # 0

7.2 配置管理器

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

def create_config_manager(initial_config=None):
    """創(chuàng)建配置管理器(使用閉包封裝配置數(shù)據(jù))"""
    if initial_config is None:
        config = {}
    else:
        config = dict(initial_config)  # 復(fù)制一份,避免外部修改

    def get(key, default=None):
        """獲取配置項(xiàng)"""
        return config.get(key, default)

    def set(key, value):
        """設(shè)置配置項(xiàng)"""
        nonlocal config  # 如果set需要替換整個(gè)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}

八、最佳實(shí)踐

# ? 最佳實(shí)踐一:優(yōu)先使用局部變量
# 局部變量訪問最快,且不污染全局作用域

# ? 最佳實(shí)踐二:避免使用global
# 用函數(shù)參數(shù)和返回值來傳遞數(shù)據(jù),而不是修改全局狀態(tài)
# ? 不推薦
total = 0
def add_to_total(x):
    global total
    total += x
# ? 推薦
def add(x, y):
    return x + y

# ? 最佳實(shí)踐三:模塊級別的常量用全大寫
# PEP 8規(guī)范:全局常量用大寫字母+下劃線
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30
DATABASE_URL = "mysql://localhost/mydb"

# ? 最佳實(shí)踐四:用類代替閉包管理復(fù)雜狀態(tài)
# 當(dāng)閉包變得復(fù)雜時(shí)(多個(gè)函數(shù)、多個(gè)狀態(tài)變量),用類更清晰
# 閉包適合簡單場景(1-2個(gè)狀態(tài))
# 類適合復(fù)雜場景(多個(gè)方法和屬性)

# ? 最佳實(shí)踐五:理解"修改"和"重新賦值"的區(qū)別
# my_list.append(x)   → 修改對象,不需要nonlocal/global
# my_list = [x]       → 重新賦值,需要nonlocal/global
# my_dict["key"] = v  → 修改對象,不需要nonlocal/global
# my_dict = {"k": v}  → 重新賦值,需要nonlocal/global

九、總結(jié)

Python的作用域規(guī)則是LEGB——從Local到Enclosing到Global到Built-in,逐層向外查找。理解這個(gè)規(guī)則是寫出正確Python代碼的基礎(chǔ)。

核心要點(diǎn):

關(guān)鍵字作用跳轉(zhuǎn)層級使用場景
(無需聲明)讀取變量LEGB逐層查找只讀訪問外部變量
global修改全局變量跳到G層修改模塊級變量
nonlocal修改外層變量跳到最近的E層閉包中修改捕獲的變量

使用建議:

  1. 優(yōu)先使用局部變量——最快、最安全
  2. 謹(jǐn)慎使用global——能不用就不用,用參數(shù)和返回值代替
  3. nonlocal是閉包的好伙伴——但復(fù)雜場景用類替代
  4. 理解"修改對象"vs"重新賦值"——前者不需要關(guān)鍵字聲明

掌握了作用域規(guī)則,你就真正理解了Python的變量世界。下一篇文章,我們將深入探討全局變量與局部變量的優(yōu)先級規(guī)則——以及那些讓你意想不到的邊界情況。

以上就是系統(tǒng)講解Python函數(shù)中g(shù)lobal與nonlocal關(guān)鍵字的使用的詳細(xì)內(nèi)容,更多關(guān)于Python global與nonlocal關(guān)鍵字的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

碌曲县| 汝城县| 和田县| 泽库县| 华宁县| 简阳市| 荃湾区| 九江市| 贵州省| 迁西县| 城口县| 启东市| 祁连县| 昌黎县| 双城市| 天柱县| 库尔勒市| 辽中县| 鹤壁市| 民勤县| 特克斯县| 积石山| 赣榆县| 四平市| 尉氏县| 长武县| 郧西县| 乐都县| 宾阳县| 临潭县| 朝阳县| 哈巴河县| 盐池县| 苍溪县| 博湖县| 长宁县| 五河县| 建平县| 赤水市| 东山县| 东丽区|