Python 函數是**一級物件**,可動態附加屬性。利用這特性,可在函數內部儲存狀態(如計數器、快取、配置),避免污染全域命名空間,且比類別更輕量。本文詳細解析函數屬性機制、與全域變數/類別的比較、實戰模式與進階應用。
def counter(x=1):
try:
counter.value += x
except AttributeError:
counter.value = x
return counter.value
print(counter(3)) # 3
print(counter(4)) # 7
print(counter(10)) # 17
print(counter.value) # 17 (可直接讀取)
function 物件,儲存在模組全域命名空間 (如 counter)。__dict__,可任意增刪屬性:counter.value = 0。| 方案 | 語法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 全域變數 | count = 0 | 簡單直觀 | 污染命名空間、多執行緒需鎖、模組重載失效 | 極簡腳本、單執行緒 |
| 函數屬性 | func.attr = val | 封裝在函數內、無額外物件、可攜帶文檔字串 | 僅限單函數、無繼承/多型 | 計數器、快取、單例資源、狀態機 |
| 類別 + __call__ | class C: def __call__(self):... | 完整 OOP、多實例、繼承、描述符協議 | 較重、樣板代碼多 | 複雜狀態、需多實例、框架擴充點 |
def make_counter(start=0):
def counter(x=1):
counter.value += x
return counter.value
counter.value = start
return counter
c1 = make_counter(100)
c2 = make_counter(0)
print(c1(5)) # 105
print(c2(5)) # 5
print(c1.value, c2.value) # 105, 5 (獨立狀態)
def fib(n):
if n < 2:
return n
try:
return fib.cache[n]
except AttributeError:
fib.cache = {}
except KeyError:
pass
fib.cache[n] = fib(n-1) + fib(n-2)
return fib.cache[n]
print([fib(i) for i in range(10)]) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
print(fib.cache) # {2: 1, 3: 2, 4: 3, 5: 5, 6: 8, 7: 13, 8: 21, 9: 34}
def connect(host='localhost', port=5432):
# 允許運行時修改預設值
host = connect.default_host or host
port = connect.default_port or port
...
connect.default_host = 'db.prod.internal'
connect.default_port = 5433
connect() # 使用新預設值
class MethodProperty:
"""讓函數屬性像 property 一樣可透過實例存取"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
return self.func
return lambda *a, **kw: self.func(obj, *a, **kw)
class Service:
def __init__(self):
self._counter = 0
@MethodProperty
def next_id(self):
self._counter += 1
return self._counter
s = Service()
print(s.next_id()) # 1
print(s.next_id()) # 2
函數屬性**非執行緒安全**。若需多執行緒共享計數器:
import threading
def thread_safe_counter():
with thread_safe_counter._lock:
thread_safe_counter.value += 1
return thread_safe_counter.value
thread_safe_counter.value = 0
thread_safe_counter._lock = threading.Lock()
或改用 threading.local() 實現執行緒區域儲存。
def counter():... 再次執行會建立新 function 物件,舊屬性消失。生產代碼避免在迴圈/條件判斷中重複定義函數。pickle.dumps(counter) 僅存函數代碼,屬性遺失。需自行實作 __getstate__/__setstate__ 或改用類別。__call__。_init_once() 模式。"""Returns call count. Attribute: .calls"""。函數屬性是 Python「函數即物件」哲學的直接體現,善用它能寫出更內聚、命名空間更乾淨的代碼,是介於全域變數與類別之間的甜蜜點。