2 回答

TA貢獻1966條經驗 獲得超4個贊
這通常稱為動態作用域和靜態作用域。粗略地說,動態作用域通過調用嵌套確定作用域,靜態作用域通過聲明嵌套確定作用域。
一般來說,對于任何具有調用堆棧的語言來說,動態作用域很容易實現——名稱查找只是線性地搜索當前堆棧。相比之下,靜態范圍更復雜,需要幾個不同的范圍和自己的生命周期。
然而,靜態作用域通常更容易理解,因為變量的作用域永遠不會改變——名稱查找必須解析一次并且將始終指向相同的作用域。相比之下,動態作用域更脆弱,在調用函數時名稱在不同的作用域或沒有作用域被解析。
Python 的作用域規則主要由引入嵌套作用域(“閉包”)的PEP 227和引入可寫嵌套作用域(“閉包”)的PEP 3104nonlocal定義。這種靜態作用域的主要用例是允許高階函數(“函數生成函數”)自動參數化內部函數;這通常用于回調、裝飾器或工廠函數。
def adder(base=0): # factory function returns a new, parameterised function
def add(x):
return base + x # inner function is implicitly parameterised by base
return add
兩個 PEP 都將 Python 如何處理靜態作用域的復雜性編纂成文。具體來說,范圍在編譯時解析一次——此后每個名稱都嚴格地是全局的、非本地的或本地的。作為回報,靜態作用域允許優化變量訪問——從快速的局部數組、閉包單元的間接數組或慢速全局字典中讀取變量。
這種靜態作用域的名稱解析的人工制品是UnboundLocalError :名稱可能在本地作用域但尚未在本地分配。即使在某處為名稱分配了一些值,靜態作用域也禁止訪問它。
>>> some_name = 42
>>> def ask():
... print("the answer is", some_name)
... some_name = 13
...
>>> ask()
UnboundLocalError: local variable 'some_name' referenced before assignment
存在各種方法來規避這一點,但它們都歸結為程序員必須明確定義如何解析名稱。
雖然 Python 本身并不實現動態作用域,但它可以很容易地被模擬。由于動態作用域與每個調用堆棧的作用域堆棧相同,因此可以顯式實現。
Python 本機提供了threading.local將變量上下文化到每個調用堆棧的功能。類似地,contextvars允許明確地將變量置于上下文中——這對于例如async回避常規調用堆棧的代碼很有用。線程的原始動態范圍可以構建為線程本地的文字范圍堆棧:
import contextlib
import threading
class DynamicScope(threading.local): # instance data is local to each thread
"""Dynamic scope that supports assignment via a context manager"""
def __init__(self):
super().__setattr__('_scopes', []) # keep stack of scopes
@contextlib.contextmanager # a context enforces pairs of set/unset operations
def assign(self, **names):
self._scopes.append(names) # push new assignments to stack
yield self # suspend to allow calling other functions
self._scopes.pop() # clear new assignments from stack
def __getattr__(self, item):
for sub_scope in reversed(self._scopes): # linearly search through scopes
try:
return sub_scope[item]
except KeyError:
pass
raise NameError(f"name {item!r} not dynamically defined")
def __setattr__(self, key, value):
raise TypeError(f'{self.__class__.__name__!r} does not support assignment')
assign這允許全局定義一個動態范圍,可以在有限的時間內將名稱編輯到該范圍。分配的名稱在被調用的函數中自動可見。
scope = DynamicScope()
def print_answer():
print(scope.answer) # read from scope and hope something is assigned
def guess_answer():
# assign to scope before calling function that uses the scope
with scope.assign(answer=42):
print_answer()
with scope.assign(answer=13):
print_answer() # 13
guess_answer() # 42
print_answer() # 13
print_answer() # NameError: name 'answer' not dynamically defined

TA貢獻1851條經驗 獲得超5個贊
靜態(早期)和動態(晚期)綁定:
綁定是指將程序文本中的名稱與它們所指的存儲位置相關聯。在靜態綁定中,這種關聯是在構建時預先確定的。使用動態綁定,此關聯直到運行時才確定。
動態綁定是發生在 Python 中的綁定。這意味著 Python 解釋器僅在代碼運行時才進行綁定。例如 -
>>> if False: ... x # This line never runs, so no error is raised ... else: ... 1 + 2 ...3>>>
動態綁定的優點
動態類型綁定的主要優點是靈活性。編寫通用代碼更容易。
Ex - 使用動態類型綁定的語言處理數據列表的程序可以編寫為通用程序。
動態綁定的缺點
編譯器的錯誤檢測能力減弱。編譯器可能捕獲的一些錯誤。
運行時的相當大的開銷。
添加回答
舉報