首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

Python 函數屬性實現「全域變數」的優雅模式

     編輯:更多程式設計知識

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、多實例、繼承、描述符協議較重、樣板代碼多複雜狀態、需多實例、框架擴充點

進階模式 1:裝飾器工廠產生帶狀態函數


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 (獨立狀態)

進階模式 2:函數屬性作為快取 (Memoization)


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}

進階模式 3:配置預設值動態調整


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()  # 使用新預設值

進階模式 4:描述符實現「屬性化函數」


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 序列化不保存屬性:pickle.dumps(counter) 僅存函數代碼,屬性遺失。需自行實作 __getstate__/__setstate__ 或改用類別。
  • 遞迴函數屬性共享:遞迴呼叫共用同一 function 物件,屬性會被所有遞迴層級共享,需謹慎設計。

最佳實踐總結

  1. **輕量狀態** (計數、快取、旗標) → 函數屬性。
  2. **需多實例/繼承/序列化** → 類別 __call__。
  3. **模組級單例資源** (連線池、配置) → 模組層級變數 + _init_once() 模式。
  4. **文檔化**:在函數文檔字串說明屬性用途,如 """Returns call count. Attribute: .calls"""。

函數屬性是 Python「函數即物件」哲學的直接體現,善用它能寫出更內聚、命名空間更乾淨的代碼,是介於全域變數與類別之間的甜蜜點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved