[Python] 變數定義與傳遞

程式語言:Python

簡介:(Global vs. Local) and (Call-by-value? or Call-by-reference?)

Global vs. Local

簡單範例
def f():
    s = "Local"
    print(s) # >> Local

s = "Global" 
f()
print(s) # >> Global

NG 範例
print(s) 未定義 s 會失敗,如下
def f():
    print(s)
# >> UnboundLocalError: local variable 's' referenced before assignment
    s = "Local"
    print(s)

s = "Global" 
f()
print(s)

OK 範例
global 變數使用前,需先告知,故需多一行 global s
此時 f() 的 s 變成 global,故 print(s) 顯示 "Local",因為 s 在 f() 內被更改
def f():
    global s
    print(s) # >> Global
    s = "Local"
    print(s) # >> Local

s = "Global" 
f()
print(s) # >> Local, modified in f()

特殊範例 I
若 global 變數使用前,已有同名的變數會產生 warning,但仍然視為 global 變數
故請將告知的程式碼放置在最前面
def f():
    s = "Local"
    print(s) # >> Local
# >> SyntaxWarning: name 's' is assigned to before global declaration
    global s
    print(s) # >> Local

s = "Global" 
f()
print(s) # >> Local

特殊範例 II
在Python 3中新增了nonlocal,可以指明變數並非區域變數
且依 函式(Local functon)=> 外包涵式(Endosing function)=> 全域(Global)=> 內建(Builtin)的順序來尋找
也就是 python 的 LEGB(Local,Enclosing,Global,Built-in)規則,即是 Lexical scoping
但只適用於 function,若在 class 則會被限於 class 中,如下範例
class C(object):
    x = 'class'
    def __init__(self):
        print('in C:', self.x) # class
    
    def outer_f(self):
        x = 'outer'
        def inner_f():
            x = 'inner'
            print('in inner_f:', x) # inner
        
        inner_f()
        print('in outer_f:', x) # outer
            
x = 'global'
c = C()
c.outer_f()
print('in global:', x) # global
======================================================================
class C(object):
    x = 'class'
    def __init__(self):
        print('in C:', self.x) # class
    
    def outer_f(self):
        x = 'outer'
        def inner_f():
            # 需放在前面,不然會發生 warning
            # SyntaxWarning: name 'x' is assigned to before nonlocal declaration
            nonlocal x
            x = 'inner' # 此時修改 outer_f 的 x
            print('in inner_f:', x) # inner
        
        inner_f()
        print('in outer_f:', x) # inner
            
x = 'global'
c = C()
c.outer_f()
print('in global:', x) # global
======================================================================
class C(object):
    x = 'class'
    def __init__(self):
        print('in C:', self.x)
    
    def outer_f(self):
        # SyntaxError: no binding for nonlocal 'x' found
        nonlocal x # 錯誤,找不到 x,但 global 可正常
        x = 'outer'
        def inner_f():
            x = 'inner'
            print('in inner_f:', x)
        
        inner_f()
        print('in outer_f:', x)
            
x = 'global'
c = C()
c.outer_f()
print('in global:', x)

特殊範例 III
b 的賦值操作不能執行,原因在於 list comprehension 會創建自己的局部命名空間
所以此時的 a 不是跟原本的 a 同空間
class C():
    a = '123'
    b = [a + i for i in a]  #NameError: name 'a' is not defined
    print(b)
======================================================================
class C():
    a = '123'
    b = [i for i in a] 
    print(b) # ['1', '2', '3']

Call-by-value? or Call-by-reference?

答案:都不是


Python 的物件分成兩種
  • Immutable Object:不可修改
    • numbers
    • booleans
    • strings
    • tuples
    • frozensets
    • ...
  • Mutable Object:可修改 
    • list
    • dict 
    • ...

Python 賦於變數時如下,想像有個標籤叫 a,然後貼到數字 1 的物件上
a = 1 而每個物件都有 id,可用 id(obj) 取得

簡單來說,取決於標籤貼的物件
  • 若是可修改,就像個籃子,只貼到籃子上,而內容物任意可變
  • 若是不可修改,則貼到物件本身,變了當然就換個物件

數字物件範例
可以發現 id 號碼並不一樣
def f(a):
    a += 1
    print(id(a)) >> 2004603344 取得標籤 a 貼上物件的 id 號碼
a = 0
print(id(a)) >> 2004603328
f(a)
print(a) >> 0

List 物件範例
可以發現 id 號碼是一樣的,所以 list 被更改
def f(a):
    a.append(1)    
    print(id(a)) >> 27941584
    
a = []
print(id(a)) >> 27941584
f(a)
print(a) >> [1]

重新給值 List 物件範例
可以發現重新給值 id 號碼不一樣,所以外部 list 不更改為 [1, 2]
def f(a):
    a.append(1)
    print(id(a)) >> 28334800
    a = [1, 2]
    print(id(a)) >> 28334840

a = []
print(id(a)) >> 28334800
f(a)
print(a) >> [1]

參考:

留言