為什麼 Python 的類不構成作用域(scope)?

為什麼要區分scope和namespace?到底是從namespace查找變數還是scope

為什麼以下代碼報錯?

第三行會報a沒有定義,所以可以看出搜索變數名a的過程是inner-&>main為什麼跳過了class的命名空間?

class A(object):

a = 3

b = list(a + i for i in range(10))


就是為了使用方便,如果class也是scope,class里定義的方法就會成為class的閉包,這在語義上是說不通的,也會造成麻煩,比如__name__不是當前模塊的name而是class的。另外,手工給class添加方法時的特性會和在class語句中創建不同。


現有的幾個回答,你們好像都被題主帶溝里去啦,來,題主你試試簡單的:

class A:
a = 123
b = a

至少在我的環境下可以執行通過,這個問題並不是你所認為的作用域的問題(雖然是另一個作用域問題),類中的代碼執行規則可以看以前的一個回答

冒泡:Python 有哪些好玩的語法糖?

所以,是你這個generator導致的

具體問題就是我上面回答裡面說的,雖然類下的代碼塊在init過程中是作為一個函數來執行,但是和普通函數有幾個區別,其中一個就是:類代碼塊中定義的函數,不視為這個代碼塊「假函數」的閉包,你看:

class A:
a = 123
def g():
print a
g()

效果一樣吧,在調用g的時候也出現了找不到global變數"a"的異常,而你把上面的class A換成def f()然後調用f就沒事,這時候g是f的一個閉包函數,可以引用f的局部變數a

那麼題主代碼明明沒有出現定義函數的代碼,為啥也會有這個問題呢,其實就是因為,那個generator的元素求值部分,是作為一個匿名函數來編譯的,也就是說把"a+i"這個表達式編譯成了一個獨立的code對象,然後這個對象會被generator對象引用,當你遍歷generator的時候,其實是一個個地執行這個小函數來返回值,而這個小函數在編譯的時候,由於不能作為閉包來看待,裡面的"a"自然就跟上面的a沒啥關係,而是作為global var來編譯了

所以你把這行改成"b = [a + i for i in range(10)]"就沒事了,列表解析式是立即求所有元素值,所以不需要一個匿名函數做媒介

與這個問題類似的,還有一個eval的坑,也是作用域混亂,不過我忘了具體常式是啥樣的了,反正是碰到過

==========================

忽然想起eval的坑了,幾個例子如下,原因可以自己分析(均為python2.x):

def f(): #運行拋異常
a = 123
def g():
exec "print a"
g()

def f(): #直接編譯失敗
a = 123
def g():
exec "print a"
print a
g()

def f(): #運行拋異常
a = 123
def g():
print eval("a")
g()

def f(): #可以正常列印兩個123,猜猜為什麼
a = 123
def g():
print eval("a")
print a
g()


因為python很傻,都需要你顯示的處理對應對象


著作權歸作者所有。

商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

作者:劉星宇

鏈接:https://www.cnblogs.com/starliu/p/4707936.html

來源:博客園

Namespace and Scope(命名空間和作用域)

Namespace只是從名字到對象的一個映射(a mapping from name to objects)。大部分namespace都是按Python中的字典來實現的。有一些常見的namespace:built-in中的集合( abs() 函數等)、一個模塊中的全局變數等。

從某種意義上來說,一個對象(object)的所有屬性(attribute)也構成了一個namespace。在程序執行期間,可能(其實是肯定)會有多個名空間同時存在。不同namespace的創建/銷毀時間也不同。

此外,兩個不同namespace中的兩個相同名字的變數之間沒有任何聯繫。

scope

有了namespace基礎之後,讓我們再來看看scope。Scope是Python程序的一塊文本區域(textual region)。

在該文本區域中,對namespace是可以直接訪問,而不需要通過屬性來訪問。

Scope是定義程序該如何搜索確切地「名字-對象」的名空間的層級關係。

(The 「scope」 in Python defines the 「hirerchy level」 in which we search namespaces for

certain 「name-to-object」 mappings.)

Tip

直接訪問:對一個變數名的引用會在所有namespace中查找該變數,而不是通過屬性訪問。

屬性訪問:所有名字後加 . 的都認為是屬性訪問。

如 module_name.func_name ,需要指定 func_name 的名空間,屬於屬性訪問。

而 abs(-1),abs 屬於直接訪問。

兩者之間有什麼聯繫呢?

Important

在Python中,scope是由namespace按特定的層級結構組合起來的。

scope一定是namespace,但namespace不一定是scope.

LEGB-rule

在一個Python程序運行中,至少有4個scopes是存在的。

直接訪問一個變數可能在這四個namespace中逐一搜索。

Local(innermost)

包含局部變數。

比如一個函數/方法內部。

Enclosing

包含了非局部(non-local)也非全局(non-global)的變數。

比如兩個嵌套函數,內層函數可能搜索外層函數的namespace,但該namespace對內層函數而言既非局部也非全局。

Global(next-to-last)

當前腳本的最外層。

比如當前模塊的全局變數。

Built-in(outtermost)

Python __builtin__ 模塊。

包含了內建的變數/關鍵字等。

那麼,這麼多的作用域,Python是按什麼順序搜索對應作用域的呢?

著名的」LEGB-rule」,即scope的搜索順序:

Important

Local -&> Enclosing -&> Global -&> Built-in

怎麼個意思呢?

當有一個變數在 local 域中找不到時,Python會找上一層的作用域,即 enclosing 域(該域不一定存在)。enclosing 域還找不到的時候,再往上一層,搜索模塊內的 global 域。最後,會在 built-in 域中搜索。對於最終沒有搜索到時,Python會拋出一個 NameError 異常。

作用域可以嵌套。比如模塊導入時。這也是為什麼不推薦使用 from a_module import * 的原因,導入的變數可能被當前模塊覆蓋。

Assignment rule

看似python作用域到此為止已經很清晰了,讓我們再看一段代碼:

def outer():

a = 0

b = 1

def inner():

print a

print b

inner()

outer()

你覺得結果是什麼呢?So easy是不是?

如果多加一句呢?

def outer():

a = 0

b = 1

def inner():

print a

print b

# b += 1 # A

b = 4 # B

inner()

outer()

結果又會是什麼呢?

Traceback (most recent call last):

File "a.py", line 34, in &

outer()

File "a.py", line 32, in outer

inner()

File "a.py", line 29, in inner

print b

UnboundLocalError: local variable "b" referenced before assignment

是不是很奇怪?原因是這樣的:Python解釋器執行到 inner() 中的 print b 時,發現有個變數 b 在當前作用域(local)中無法找到該變數。它繼續嘗試把整塊代碼解釋完。

Bingo! 找到了。那麼 b 是屬於 inner() 作用域的。既然對變數 b 的賦值(聲明)發生在 print 語句之後, print 語句執行時變數 b 是還未被聲明的,於是拋出錯誤:變數在賦值前就被引用。

在這個例子中,只有A語句沒有B語句也會導致同樣的結果。

因為 b += 1 等同於 b = b + 1。

對於變數的作用域查找有了了解之後,還有兩條很重要的規則:

賦值語句通常隱式地會創建一個局部(local)變數,即便該變數名已存在於賦值語句發生的上一層作用域中;

如果沒有 global 關鍵字聲明變數,對一個變數的賦值總是認為該變數存在於最內層(innermost)的作用域中;

也就是說在作用域內有沒有發生賦值是不一樣的。


這代碼有啥問題。。縮進嗎

類定義就是一個函數扔給_build_class_ 這函數把f_locals給cls(大概吧沒看過源碼。。

所以有個臨時的local


推薦閱讀:

這個python程序不能再簡化了吧?
python2.7,python3.3,對於小白,到底從哪個版本入手比較好?
在python中,怎樣計算list的累積和?不能用loop或者library的function。
想問怎麼用Python編一個 同時投12個骰子 計算每次投出至少出現兩個六的次數及概率的程序?
剛安裝了pycharm, 寫了一句print "nice!" 都報錯是怎麼回事?

TAG:Python | 編程 | Python3x | 計算機科學 | Python入門 |