Python 變數與變數存取範圍:
Variables and Scopes
Python 世界的邏輯是 We are all consenting adults! 。
所以,小標題寫的是 Naming Rules 而不是 Conventions。我沒要求你,但你應該這樣做。
若做不到,我會在心裡笑你沒長大。class name 以大寫字母開頭(same as Java)
module name (package name) 以小寫字母開頭(Java)
function name 以小寫字母開頭,以 Underscore 連接單詞
命名規則: Variable Naming Rules
說明 | |
---|---|
varName | public |
_varName | module /package, 無法 from x import x |
__varName | local variable 類別內 |
varName | 系統變數 system variables |
與 Java 不同:
underscore _ (物件封裝) : 當不希望 caller 直接使用時的規則
_x 單一底線變數
單一底線變數: weak internal use indicator,隱含的 import 語法:
告知這個變數或方法應該限制在 class 之內使用,
不應該被任意 import。
但需注意的是 single underscore 並非一種限制,只是告知。
所以還是可以經由 classInstance._x 取得內容。且在 Object Oriented 結構下,單一底線變數 _x 他還隱含著: 這個變數將被子類別複寫的意思。
所以要變免直接使用單一底線變數。test.py 中的單一底線變數 _x
無法經由
from test import * 被匯入但是可以明確的
from test import _x 被匯入
__x 雙底線變數
雙底線變數:類似私有變數
會被 Python 的 name managling 機制改名為, _classNamex。
因此無法經由 classInstance.x 取得,而避免資訊被誤用。
進而產生出 類似私有變數 (private variable)的結果。
但設計(改名機制)本意是,避免資料被誤用,或是方法被子類覆寫等情境。
__x__ 前後雙底線
前後雙底線:Python 系統所使用與定義的變數
用來提醒該變數是 Python 系統內建使用的變數。
特殊長相,一方面可用作提醒,另一方面也可避免與使用者自訂變數相衝突。
簡單的來說就是
求值策略: Argument Evaluation Binding
與大多數程式語言相同,易理解。
- Primitive type(Immutable Arguments) : Call by Value
- 數值, 字串
- Object type(Mutable Arguments) : Call by reference
- list, map, object...
注意:
匯入模組中的 值變數 value variablefrom x import y : call by value
import y : call by reference
變數存取範圍: Variable Scope
Shadow Effect : LEGB Rule
- Python 中變數查找的順序(由內而外)
- 注意 _x 無法經由 import * 取得
- 注意 __x 會被改名
Local => Enclosing => Global => Built-in
嵌套作用域 Enclosing Scope
- enclosing scope(嵌套作用域) 又稱 statically nested scope(靜態嵌套作用域)
這邊指的是槽串內部的 function 可以取得外部 host 的資訊。
下方範例 labmda 可取得外面 host_var。
範例: 嵌套作用域 Enclosing Scope
def host_fun(a, b):
host_var = 100
sum = lambda i, j: i + j + host_var
return sum(a, b)
print(host_fun(1, 3))
#104
範例: 嵌套作用域 Enclosing Scope 遇到迴圈
- 下面範例釋疑:
- 第一範例: 確實以迴圈建立多個 lambdas,每個 lambda 也確實可以拿到 host 的引數,但只拿到 reference,所以當迴圈不斷改變引數值時,真正使用的是最後的元素值。
- 第二範例: 利用 python 未 function arguments 可指定預設值的特性來暫存迴圈的 index。
def magazine(bullet: int):
shoots = []
for i in range(bullet):
shoots.append(lambda : 'Got %d Points' % (i + 1))
# 此處因為 labmda 尚未執行,而 looping 過程中 i 值不斷改變。
# 最終造成每個 lambda 的 i 值都相同
return shoots
shooting = magazine(3)
for s in shooting:
print(s())
# Got 3 Points
# Got 3 Points
# Got 3 Points
def magazine(bullet: int):
shoots = []
for i in range(bullet):
shoots.append(lambda s=i: 'Got %d Points' % (s + 1))
# 此處因為 labmda 有將 i 值另存保留(指定單一 lambda 的預設),所以得到不同結果
return shoots
shooting = magazine(3)
for s in shooting:
print(s())
# Got 1 Points
# Got 2 Points
# Got 3 Points
工廠函數 Factory Function
Factory Function 又稱 closure
指的是 nested function 在 runtime 時可以記住 host 所屬變數值的特性。
而 host 函式因為具備建立內層函式所需要的環境,故被稱為 factory function。
範例: shoot function 可以記住 bullet 的總數
def magazine(bullet):
def shoot(fire):
return '%d bullets left' % (bullet - fire)
return shoot
shooting = magazine(10) # 返回內層的 shoot()
print(shooting(2)) # shoot() 保留了 bullets 總數,即使外層 scope 已消失
# 8 bullets left
# magazine(10)(2)
global 關鍵字
global 關鍵字只會出現在 非頂層 的結構中。 用來指像頂層的全域變數用(與 nonlocal 比較)。
使用情境略分為下列三項:
解決區域變數與全域變數命名衝突
用來宣告將 同名的區域變數指向全域變數 。
Enclosing Scope(嵌套作用域)的運作下,
函數或是一般的程式區塊預設是 可直接存取,但不能改變 全域變數的內容。
因此若想在區塊內更改全域變數內容的話,
會需要在區域區塊內將變數補上 global 修飾詞。在程式區域區塊中聲明一個全域變數。
按照 Python 經常出現的省略語法,可以直接想成:
加 global 修飾詞的區域變數,等同於 也 在全域宣告了一個同名的全域變數。若想在區塊內修改全域變數,則必須以 gloabl 關鍵告知 VM,此變數是全域變數。
不然區塊內的變數預設視為區域變數。
關於 global 的語法面就如同上面敘述。$ hidden
$ insect-totem.net
$ TODO:
回歸到程式語言的設計面,
到底 Python 什麼時候會用到 global/nonlocal?
- 為什麼會有工程師想要或是必要設計出命名衝突的變數或方法?
LEGB Rule 變數查找過程預設會由內往外找。
所以即使命名衝突那通常想要的也是 local 的那一個啊?
若發生,我認為是誤用。
- 全域變數晉升。為什麼要在函數中定義一個不是自己能管轄的全域變數?
蘋果應該長在蘋果樹上。全域的就應該放在全域吧?
這對我來說,完全不合理。
這情形就像是公司的獎逞制度定義在清潔人員身上一樣難以理解。
- 剩下 global 的用途便是想更改全域變數內容。
我接受。
我願意接受在一個 module Scope 或 class Scope 中的全域變數,
被一個明確指名異動該變數的方法中使用,不然會天下大亂。
解決區域變數與全域變數命名衝突 and 區塊內修改全域變數
address = 'Taipei, Taiwan'
# LEGB
def resetIp():
address = '0.0.0.0'
return address
# modify global var
def uproot(location:str):
global address
address = location
return address
print(address)
# print host address variable
# 'Taipei, Taiwan'
# uproot should be invoked out of this module
# and host address sholud be renamed to __address
uproot("Taichun, Taiwan")
print(address)
# update and print host address variable
# 'Taichun, Taiwan'
print(resetIp())
# LEGB
# 0.0.0.0
在程式區域區塊中聲明一個全域變數:全域變數晉升
def fun():
global public_X
public_X = 55
print(public_X)
# print(y)
def fun2():
global public_X
print(public_X)
fun() # 需告並初始化全域變數處
fun2() # 取用隱藏的全域變數
# 55
# 55
模組內全域變數被其他模組匯入
- 如果在程式區塊(如:函式中)將 global 指向 import 進來的 variable。會發生?
- 先記得一件事: primitive type 會 copy 進來。 object type 是傳進 reference。
- imported vars 本身被視為是模組內的全域變數。
- 所以,整個情境是跟 module 內全域變數的一樣。
- 但 要注意 object type call by reference,也就是說你會改到 來源模組 的內容。也會 影響到其他 import 同模組 的程式片段。
這邊的重點在 封裝
匯入的資料預設是整個 module 的全域變數。
回歸前面說的 Pythonic Convention: underscore 變數名名規則,你應該先做 適當封裝 。
We are all consenting adults。變數放出來,就別怪其他使用者改變你的變數值。
也就是使用 __x 雙底線變數。
# 小心 資料被誤改啊.
nonlocal 關鍵字
- 在 槽串結構中,例如 function 中又再定義 function 時,最內層(inner fun)可以藉由 nonlocal 取得外層(outer fun)中變數的 reference。
- 這設計的邏輯到底是...... 真方便使用......
- 避免命名衝突才是上策。
nonlocal 關鍵字只會出現在 槽串結構 的內層結構中。
用來指像外層的同名變數用。所以這邊隱含著 全域/中間層s/內層 至少出現 三次同名變數 的情境。
真的是來亂的。與 global 不同處在於。
內層的區塊中的 global 指向全域同名變數(最外層)。而 nonlacal 則是
內層的區塊中的 nonlacal 指向由內向外依序發現的第一個同名變數(內向外第一個發現)。
nonlocal 範例
passenger = "PASSENGER"
# without nonlocal
def tom():
passenger = "TOM"
def tom_jr():
passenger = "TOM JUNIOR"
print('tom_jr: ', passenger)
tom_jr() # tom_jr() ececuting
print('********** tom: ', passenger)
tom() # executing
print("passenger: ", passenger)
# tom_jr: TOM JUNIOR
# ********** tom: TOM **********
# passenger: PASSENGER
# with nonlocal
def gary():
passenger = "GARY" # modified by nonlocal var
def gary_jr():
nonlocal passenger # ref to var in gary()
passenger = "GARY JUNIOR"
print('gary_jr: ', passenger)
gary_jr() # tom_jr() ececuting
print('********** gary: ', passenger)
gary() # executing
print("passenger: ", passenger)
# gary_jr: GARY JUNIOR
# ********** gary: GARY JUNIOR **********
# passenger: PASSENGER