iT邦幫忙

3

【Python 超入門】(12) (補充篇)當你的同學與全國明星同名同姓?談python的區域變數與全域變數

今天總算有時間把這一篇補上來了,
本篇文章算是【Python 超入門】(11) (完結篇) 實作你的第一個遊戲-金頭腦益智問答的補充說明

蠻多邦友對於我在【Python 超入門】(11)中用到了global變數感到不解,
為何這段程式要在函數裡面要寫global score,把score設成全域變數呢?

score = 0
def check_ans(guess, answer):
    global score 
    if guess==answer:
        print("恭喜你答對了")
        score += 1
    else:
        print("答錯了,再接再勵")

所以今天小馬要用初學者都能懂的語言,
讓讀者能很清楚了解區域變數全域變數是什麼東東,
並說明為什麼要用global

什麼是區域變數(local variable)與全域變數(global variable)?

在程式中,似乎常聽見「區域變數」與「全域變數」這兩個專有名詞,
到底這兩個詞是什麼意思呢?

原則零、在python中寫等號會被認為是宣告一個變數

首先,在python中宣告變數很簡單,

只要用等號賦值就算宣告變數了
只要用等號賦值就算宣告變數了
只要用等號賦值就算宣告變數了
(這點很重要,在後面例子講解時會用到這個觀念)

一般來說,如果我們寫這兩行程式

x = 2
x = 3

我們會認為第一行的x = 2是宣告了一個變數x,它的數值是2
第二行的x = 3是把x的值修改為3
你要這麼想也可以

但是小馬認為更好的想法是把第二行的x = 3也想成是宣告變數x
只是後面的宣告覆蓋了前面的宣告,
這樣等一下看到區域變數時應該會比較容易理解

簡單來說,區域變數就是被包在某個區塊(比如說: 函數)裡面宣告的變數,
例如說我寫了一個函數叫myFunc:

def myFunc():
    var = 5

我在這個函數裡面宣告了var = 5
那麼var就是一個區域變數

反之,如果我不在任何區塊內宣告變數,
例如:

var = 5

我的程式就只寫一行var = 5
那麼var就是一個全域變數

理解了這個簡單定義後,
接下來就要用超簡單的語言講變數名稱在不同區域的規則囉~

我們做個聯想,把函數想成是你學校的班級
函數外的空間就想成是整個國家

比喻: 同一個班級內不可以有兩個人同名,但班級可以有人與全國明星同名

在日常生活中難免會有人同名同姓,
為了區別,如果同一個班級內有兩個人都叫做「王小明」,
那麼老師在點名的時候,兩個「王小明」便無法區分老師在叫誰

程式也是一樣的道理,
所以在同一個函數內,同一個名字一定是代表同個物件

(為了方便舉例,人名隨意虛構)
但是假設有個全國明星叫做「周潔輪」,
你們班剛好也有一位同學就叫做「周潔輪」,
那麼有沒有辦法區分呢?

那是可以的,因為老師點名叫「周潔輪」的名字時,
不必特別講,一定是指班上的「周潔輪」,
不會是指全國明星的「周潔輪」,
因為全國明星「周潔輪」離自己太遙遠了
除非班上沒有「周潔輪」這個人,那肯定聊的是全國明星的「周潔輪」

原則一、區域優先查找

為了方便解說,本文稱之「區域優先查找原則」,
當用到一個變數時,
會優先看看區域裡面有沒有宣告這個變數,
再去看全域有沒有這個變數

所以譬如以下程式:

x = 2
def myFunc():
    x = 3
    print(x)    
myFunc()
print(x)

我在全域內宣告了一個變數x=2
在區域內把x改成3
那麼請問程式會印出什麼?

在讀小馬這篇文章以前,
可能會有蠻多初學者以為答案會印出
3
3
尤其學一開始有學C語言的同學可能會這麼以為,
等等小馬在結尾當附錄跟C語言做個比較/images/emoticon/emoticon31.gif

但事實上,myFunc()裡面寫的x = 3
並不是把宣告在全域的x的值改成3
因為在python中只要用等號賦值就算宣告變數了 (原則零)

基於班上同學與全國明星名字相同沒有關係,
所以,在myFunc()裡面寫的x = 3
其實是宣告一個區域變數叫做x,再把x的值設成3

所以這段程式應該印出
3
2

第一次執行myFunc()時,
myFunc()裡面用print(x)呼叫x
這時區域變數x跟全域變數x的名字相同,
根據「區域優先查找原則」,指的當然是區域的x (即數字3)

在全域呼叫print(x)時,
如同全國人民在談論明星「周潔輪」般,
他們並不認識你們班沒有名氣的「周潔輪」,
故全域會指向數字2

腦力激盪一

再看一例,
請問下面程式會印出什麼?

x = 10
def myFunc():
    print(x)    
myFunc()

在這支程式中,
我們嘗試在myFunc函數裡去呼叫x
根據「區域優先查找原則」,
會先嘗試在函數內找有沒有x這個變數,
因為找不到就會往外去找,
所以會印出全域的10

原則二、區域內的變數是當做區域變數還是全域變數只能二擇一

講完基礎的例子後,接著要引入第二個原則,
雖說一開始講如果你們班同學跟全國明星都叫「周潔輪」的話還是很容易區分的

但事實上,你們老師的頭腦不太靈光,
如果一下子呼叫班上的「周潔輪」,
一下子又呼叫全國的「周潔輪」,
他會感到很混亂,
因此,在python中區域內裡面去用到一個變數,
到底是呼叫區域變數還是全域變數只能選邊站,
不能一下是區域變數又是全域變數

腦力激盪二

如果懂了的話,
我們再看更進階的例子哦~
請看這段程式會印出什麼?

x = 10
def myFunc():
    print(x)
    x = 5
myFunc()

嘿嘿,別被騙了,不論你回答105都錯,
這段程式執行期間會產生錯誤/images/emoticon/emoticon06.gif
為什麼?因為這段程式有互相矛盾的地方,
你在myFunc裡使用print(x)
這時x還沒有在區域宣告,
所以print(x)裡面的x看似只能是全域變數x

但是在下一行寫x=5時,
根據「原則零、在python中寫等號會被認為是宣告一個變數」,
你宣告了一個區域變數x
違反原則二說x不能一下是區域變數又是全域變數
故執行myFunc()時出錯

腦力激盪三

此時我們再看更更更進階的例子,
這隻程式會印出什麼?

score = 0
def add_score():
    score = score+1 #或寫score += 1
    print(score)    
add_score()

根據先前講解的規則給你思考30秒,





30…29……2…1
時間到~
小馬猜應該有一部分同學想猜程式要印出1
因為做score的值改成score+1
score還沒宣告過所以只能是全域變數

但是別忘了,
原則零、在python中寫等號會被認為是宣告一個變數
add_score函數內寫到score = score+1是宣告區域變數的意思

如果score是區域變數,
那你就沒辦法在宣告score之前去計算score+1
因此本題的答案是程式執行期間會產生錯誤

global的效果- 在區域使用變數猶如在全域使用

講完關於「區域變數」與「全域變數」的基礎知識後,
總算可以講global這個字了,
為什麼我們在【Python 超入門】(11) (完結篇) 實作你的第一個遊戲-金頭腦益智問答中,
只是想在函數內改個全域變數score的值,要在函數內寫global score?

因為普通來說,在區域內寫等號會被認為是宣告變數
在區域內宣告的變數當然就是區域變數
原因是如果單純這樣寫:

score = 0
def check_ans(guess, answer):
    if guess==answer:
        print("恭喜你答對了")
        score += 1
    else:
        print("答錯了,再接再勵")

你函數內的score += 1會被認為是宣告區域變數,
根據前述原因,
guess==answer的情況下程式就會出錯,
並不會去改到全域變數score的值

global這個字可以讓程式明白在一個區域內如果使用一個變數,
指的是全域的變數,
這樣在使用「等號」時,就不會被認為是宣告區域變數了,
而是宣告一個全域變數去改掉原來在外面全域變數的值

方便解說,我將函數簡化給個例子:

score = 0
def add_score():
    global score
    score += 1
    print(score)    
add_score()
print(score)

結果會印出
1
1

在這支程式中,由於add_score()裡面用了global
程式便知道說之後在函數裡面看到score時,
指的是全域的score
此時的score += 1便會認為是宣告全域變數score = score +1 而非區域變數,
因此全域變數score的值會真的被覆寫

腦力激盪四

最後一個腦力激盪,
看看你理解了沒

這支程式在執行期間會產生錯誤,請問原因?

score = 0
def func():
    score = 1
    global score  
func()

再給你思考30秒,





30…29……2…1
時間到~
執行func()時,
第一行的score = 1會認為是宣告了一個區域變數score
但是第二行global score又將score視為全域變數,
抵觸「原則二、區域變數還是全域變數只能二擇一」

章節小結

估計本章是【Python 超入門】系列中,
觀念最複雜的一章了,
這邊稍微統整一下

  • 原則零、在python中寫等號會被認為是宣告一個變數
  • 原則一、使用變數名字時,優先查找區域再找全域
  • 原則二、區域內的變數是當做區域變數還是全域變數只能二擇一,若違反此原則程式會報錯
  • 只有global才能讓你修改全域變數的值

估計到這邊還是有人想問說要修改全域變數,為什麼在函數裡面要用global?
根據「區域優先查找原則」,
不是只要區域裡面沒有這個變數,就自動往全域找了嗎?

很簡單,因為只要在區域內寫等號就會被視為是宣告區域變數(而非你想要的修改),
所以不寫global的狀況下,你根本改不了全域變數的值

總之呢…本章節觀念有點多,
建議可反覆品讀直到你能輕鬆了解所有「腦力激盪」的結果

附錄: C語言與python修改全域變數的行為比較

如果你有學過一點點C語言,
你大概會對於為什麼python修改全域變數如此複雜感到非常的不可思議

小馬覺得這大概可歸咎於python語言的「簡潔」,
因此太簡潔而造成的語意不清

先統整重點:

  • C語言的「宣告變數」和「賦值」是分開的
  • python的「宣告變數」和「賦值」是同時進行的

首先先了解C語言的基礎概念,
C語言的「宣告變數」和「賦值」是兩回事,
要宣告一個變數,一定要指明它是什麼型態,
例如說明確告訴它是一個整數(int)

c語言宣告變數的方法

你可以先宣告一個變數再賦值

int x; //宣告一個整數型態的變數,名字叫x
x = 2; //將x的值設為2

也可以宣告時直接賦值

int x = 2; //宣告一個整數型態的變數,名字叫x,並將x的值設為2

但在python語言不是這樣,
宣告與賦值是同一回事,
你寫x = 2就宣告了一個變數並且賦值

在平時,python這種語法比C語言簡單,
一旦牽涉函數內修改全域變數恐怕就是惡夢

小馬認為C語言也有類似這種「區域優先查找原則」,
但由於在C語言「宣告變數」和「賦值」是分開的,
到底是在用全域變數還是宣告區域變數,
語義是很清楚的

情境一、C語言函數內修改全域變數

// 這是C語言程式
int x = 2; //宣告全域變數,名字叫x,並將x的值設為2
void func()
{
    x = 5;
}

我們在函數中寫x=5
這時我們可以很明確知道這邊的x一定是全域變數

因為如果要宣告區域變數的話,一定要加上型態,
而會寫成int x=5

情境二、python語意不明的困難

但在python裡面可不一樣了,
你在python中寫:

### 這是python程試
x = 10
def myFunc():
    x = 5

由於在python寫等號等同宣告變數,
所以myFunc()中的x=5,到底是要用全域變數的x呢?
還是我本身就要創建一個新的區域變數,名字剛好也叫x
這變得難以區分,
以致python會需要global這個關鍵字來區分這兩種情況,
但也困擾許多初學者吧


1
Pondudu
iT邦新手 5 級 ‧ 2020-04-08 23:27:12

小馬哥您好,這幾天看完您的新手教學真的受益良多!很多淺顯易懂的例子和練習題作為教材超棒的!也順利練習到最後一篇,結果就死在global了XDD 感謝小馬哥願意再花心力寫一篇觀念介紹,看完這篇終於更加了解它的涵義,但還是有個地方不是很確定,想請問:

score = 0 #宣告全域變數score
def add_score():
    score+=1 #宣告區域變數score
    print(score)    
add_score() #無法執行

我自己的理解是,因為 score+=1 是宣告區域變數的意思,但是宣告之前的score仍然為全域變數0,因為找不到區域變數score的值所以無法計算score+1的"score"(就像全班只有一位同學分數提高1分但是不知道他本來幾分?),這樣想是對的嗎?? 再麻煩指教了>.< 謝謝您!!
PS:有買了您推薦的《完全圖解Python》,真的很讚!很適合像我這種外行人XD 非常感謝Q.Q!!

心原一馬 iT邦研究生 5 級 ‧ 2020-04-09 15:13:36 檢舉

嗨嗨,邦友您好:
之前看你發問問題還蠻真誠的,
小馬有嘗試回答,也對你有印象,
看到你真的來看小馬自薦的【Python 超入門】系列教材,
感動之餘,也蠻開心在這邊寫文章寫寫寫可以幫助到一些人

回到你的問題,你理解的蠻好的,
小馬覺得能夠用自己的話理解很棒哦,
「就像全班只有一位同學分數提高1分但是不知道他本來幾分」

很高興你的回饋,能夠看到有人留言文章有幫助,
小馬由衷感到開心,
也很高興你為目標踏出第一步嘗試學習

對了,最近小馬剛好在整理【Python 入門教室】系列文,
希望做為【Python 超入門】的延續
如果有興趣的話也歡迎繼續看下去,
也很歡迎後續繼續交流(給文章批評指教也是可的~ 自己也在透過網友回饋慢慢成長,期許文章愈寫愈好)

這邊小馬先祝你學習順利,一切平安啦(っ●ω●)っ
一起加油~

Pondudu iT邦新手 5 級 ‧ 2020-04-09 15:29:02 檢舉

謝謝小馬哥~沒理解錯誤太好了(Q.Q) 昨天看完這篇後就有發現小馬哥發了熱騰騰的新教學,今天就立馬去看了XD~還在消化中~

好感動您還記得我(QωQ) 以後也請多多指教了!!
謝謝您~祝一切順心(^.^)~

心原一馬 iT邦研究生 5 級 ‧ 2020-04-09 15:45:05 檢舉

好的,祝你學習愉快悠 (◕ܫ◕)

1
medivh0102
iT邦新手 5 級 ‧ 2020-05-28 21:59:28

謝謝小馬哥的文章精闢的解說
後面的部分因為C語言上沒有研究過所以看了一下發現是完全陌生的概念所以直接省略XDDD
發現經過這個章節的解說後 global 這個語法的概念並沒有想像中難懂
感覺就像是同樣叫「周截輪」可是因為有無global所以讓系統知道你到底是在叫哪一個
稍微整理了一下:
1.如果在班級(函數)內有global:那就是叫全世界都知道的
2.如果在班級(函數)內沒有global:那就是班上那個
3.如果在班級(函數)內指名周截輪卻沒有global班上也沒有人叫周截輪:出錯

獲益良多 謝謝小馬哥:)

心原一馬 iT邦研究生 5 級 ‧ 2020-05-28 22:33:24 檢舉

謝謝你的留言分享心得,
能夠幫助到新手,是小馬撰文的動力
/images/emoticon/emoticon41.gif

1
s23699
iT邦新手 5 級 ‧ 2020-07-06 08:40:18

但事實上,myFunc()裡面寫的x = 3,
並不是把宣告在全域的x的值改成2,<---(這段是不是打錯了,應該是"並不是把宣告在全域的x的值改成3",因為全域變數給得值就是2)

心原一馬 iT邦研究生 5 級 ‧ 2020-07-06 16:07:43 檢舉

你說的沒錯,是筆誤,已於文中修正,謝謝您~

我要留言

立即登入留言