Python 入門指南 5.0

單元 19 - 作用域問題

~~學習進度表~~

全域變數 (global variable) 是指定義在所有定義之外的變數 (variable) ,區域變數 (local variable) 則是定義在程式區塊 (block) 內的變數

global variable <-> local variable

這裡的程式區塊指的是關鍵字 (keyword) def 之下的縮排區域,所以是函數 (function) 或方法 (method) 的內容。

Python 中全域變數跟區域變數是涇渭分明的,舉一個簡單例子如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 定義全域變數 a
a = 1
# 定義函數
def do_something():
    # 定義函數內的區域變數
    a = 2
    # 印出區域變數
    print(a)
# 呼叫函數印出函數中定義的區域變數
do_something()
# 印出全域變數 a
print(a)

# 檔名: scope_demo01.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

第 2 行設定了全域變數 a ,然後設定為整數 1

 2
a = 1

do_something() 定義了相同名稱的區域變數 a ,並把區域變數 a 設定為整數 2 ,並且在 do_something() 的最後印出區域變數 a

 4
 6
 8
def do_something():
    a = 2
    print(a)

原本函數可以直接使用全域變數,但是這裡在函數內定義了跟全域變數相同名稱的區域變數,那函數內識別字 (identifier) a 跟函數外的識別字 a 無關,函數內區域變數 a 的效力範圍僅在函數中。

第 10 行,呼叫 do_something() 方法

10
do_something()

第 12 行,印出全域變數 a

12
print(a)

執行結果如下

$ python scope_demo01.py
2
1
$

以上先印出整數 2 ,這是 do_something() 內的區域變數,然後印出整數 1 這是最後印出全域變數值。

如果想要在函數內修改區域變數,那就需要用關鍵字 global 宣告全域變數的識別字名稱,例如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 定義全域變數 a
a = 3
# 定義函數
def do_something2():
    # 宣告全域變數 a
    global a
    # 印出全域變數 a
    print(a)
    # 重新設定全域變數
    a = 4
# 呼叫函數
do_something2()
# 印出全域變數 a
print(a)

# 檔名: scope_demo02.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

這裡在 do_something2() 中做三件事,第一件事用關鍵字 global 宣告全域變數 a ,第二件事印出全域變數 a ,第三件事重新設定全域變數 a 為整數 4

 4
 6
 8
10
def do_something2():
    global a
    print(a)
    a = 4

下面第 12 行呼叫函數 do_somehting2()

12
do_something2()

然後第 14 行印出全域變數 a

14
print(a)

執行結果如下

$ python scope_demo02.py
3
4
$

因為 do_somehting2() 最後修改了全域變數 a 的值為整數 4 ,所以最後印出的就是整數 4

另外一種情況是在函數中,如果巢狀函數 (nested function) 想要修改上層函數的區域變數,就要用關鍵字 nonlocal 宣告。注意跟 global 一樣,必須先宣告才在巢狀函數中修改上層區域變數,舉例如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 定義全域變數 a
a = 5
# 定義函數
def do_something3():
    # 定義 do_something3 的區域變數
    a = 6
    # 定義第一個巢狀函數
    def do_something4():
        # 宣告上層 do_something3 的區域變數
        nonlocal a
        # 印出上層區域變數
        print(a)
        # 重新設定上層區域變數
        a = 7
    # 定義第二個巢狀函數
    def do_something5():
        # 宣告全域變數
        global a
        # 印出全域變數
        print(a)
    # 定義第三個巢狀函數
    def do_something6():
        # 設定區域變數
        a = 8
        # 印出區域變數
        print(a)
    # 呼叫巢狀函數
    do_something4()
    do_something5()
    do_something6()
    # 印出 do_something3 的區域變數
    print(a)
# 呼叫 do_something3() 函數
do_something3()

# 檔名: scope_demo03.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

這裡指函數,其實方法也適用,因為方法是類別專屬的函數。

第 2 行先定義全域變數,因此這個例子也會看到全域變數在巢狀函數中的情況

 2
a = 5

do_somehting3() 中先定義屬於 do_somehting3 的區域變數 a ,並設定成整數 6

 4
 6
def do_something3():
    a = 6

然後底下依次定義三個巢狀函數,先看到 do_something4()

 8
10
12
14
    def do_something4():
        nonlocal a
        print(a)
        a = 7

第 10 行用關鍵字 nonlocal 宣告上層區域變數 a

10
        nonlocal a

do_something4() 的後續是印出這個變數 a ,然後重新設定成整數 7

繼續看到第二個巢狀函數 do_something5() ,這裡宣告全域變數,然後印出全域變數

16
18
20
    def do_something5():
        global a
        print(a)

然後第三個巢狀函數 do_something6() ,設定屬於 do_something6 的區域變數 a ,然後印出 a

22
24
26
    def do_something6():
        a = 8
        print(a)

do_something3() 的最底下是依次呼叫三個巢狀函數,然後印出 do_somehting3 的區域變數 a

28
29
30
32
    do_something4()
    do_something5()
    do_something6()
    print(a)

最底下執行部分則是呼叫 do_something3()

34
do_something3()

執行結果如下

$ python scope_demo03.py
6
5
8
7
$

這裡我們重新檢視結果,第一個印出的 6 是第 6 行的定義

 6
    a = 6

第二個印出的 5 是第 2 行的定義

 2
a = 5

第三個印出的 8 是第 24 行的定義

24
        a = 8

第三個印出的 7 是第 14 行的定義

14
        a = 7

理解程式中的執行順序是很重要的課題,尤其寫出容易閱讀的程式碼也就意味著更好維護, Python 用文件字串 (documentation string) 的方式把程式文件直接插入到程式碼中,接下來我們來看看怎麼寫 Python 的程式文件。

中英文術語對照
程式區塊block
文件字串documentation string
函數function
全域變數global variable
識別字identifier
關鍵字keyword
區域變數local variable
方法method
巢狀函數nested function
變數variable
重點整理
1. 全域變數是指定義在沒有縮排的變數,區域變數則是定義在有縮排的地方。
2. 關鍵字 global 可以在函數或方法修改全域變數。
3. 巢狀函數是指函數中定義其他函數。
4. 關鍵字 nonlocal 可以在巢狀函數中修改上層的區域變數。
問題與討論
1. 全域變數跟區域變數有何不同?
2. 函數中為什麼不能直接修改全域變數?
3. 外層巢狀函數可以使用內層巢狀變數的值嗎?
練習
1. 寫一個程式 exercise1901.py ,裡面建立一個全域變數,例如 count ,然後定義一個函數印出這個全域變數,函數可命名為 print_count() ,最後執行部分就呼叫 print_count() 印出 count 值。 參考程式碼
2. 承上題,將新程式寫在 exercise1902.py 中,新定義一個函數 add_count() 遞增全域變數 count ,執行部分增加呼叫 add_count() ,然後再呼叫 print_count() 一次。 參考程式碼
3. 承上題,將新程式寫在 exercise1903.py 中,先引入 exercise1902 ,然後用 exercise1902count 定義全域變數 count ,然後定義 add_count2()count 加上 10print_count2() 印出 count ,執行部分分別呼叫 exercise1902add_count()add_count2() ,然後用 print_count2()exercise1902print_count() 印出兩個作用域不同的 count 值。 參考程式碼
4. 承上題,將新程式寫在 exercise1904.py 中,先引入 exercise1902 ,然後定義函數 global_variable()exercise1902count 建立全域變數 count參考程式碼
5. 承接上一個單元的練習 2 ,將新程式寫在 exercise1905.py 中,將計算階乘的部分放到內層函數 calculate() 中。 參考程式碼
6. 承上題,將新程式寫在 exercise1906.py 中,將 calculate() 改成 recursion() ,改成用遞迴的方式計算階層。 參考程式碼
7. 承接上一個單元的練習 4 ,將新程式寫在 exercise1907.py 中,將計算費氏數列的部分放到內層函數 calculate() 中。 參考程式碼
8. 承上題,將新程式寫在 exercise1908.py 中,將 calculate() 改成 recursion() ,改成用遞迴的方式計算費氏數列。 參考程式碼
9. 寫一個新程式 exercise1909.py ,定義函數 exercise1909() ,裡面建立區域變數 data 並設定為 100 ,然後定義內層函數 inner()inner() 用參數 numberdata 進行比較,如果 data 較大就回傳 data ,反之回傳 0 ,最後 exercise1909() 回傳 inner()參考程式碼
10. 寫一個新程式 exercise1910.py ,定義函數 my_sum() ,裡面建立區域變數 data 並設定為空串列,然後定義內層函數 calculate()calculate() 將參數 number 附加到 data 中,然後回傳 data 中的總和,最後 my_sum() 回傳內層函數 calculate()參考程式碼

上一頁 單元 18 - 模組與套件
回 Python 入門指南 5.0 首頁
下一頁 單元 20 - 文件字串
回 Python 教材首頁
回程式語言教材首頁