Python的類定義有沒有建立新的作用域?

學習python的作用域,好多資料中說def/class/lambda語句會建立作用域,def和lambda我理解,但是class真的建立作用域了嗎?

代碼:

class A:
a = 3
def foo():
print(a)

在python3下運行這段類定義,然後調用

A.foo()

會報錯:

NameError: name a is not defined

但是如果類的定義真的建立了作用域,那麼foo()函數中對a的引用應該是合理的啊?在foo()作用域中找不到a,接下來在包含foo()的作用域(即類定義建立的作用域,如果真的建立了的話)中是可以找到a的啊?

如果把代碼改成:

class A:
a = 3
def foo():
print(A.a)

這樣就沒有問題了。但是A.a中的A事實上是在全局作用域中的,A.a是對全局作用域中class對象A的屬性a的引用。

感覺上類的定義好像只建立了新的命名空間,但是並沒有建立新的作用域。

另外:

順便問一個python2的問題,上面的類定義在python2中運行後,無法通過A.foo()的方式調用,會產生錯誤:

TypeError: unbound method foo() must be called with A instance as first argument (got nothing instead)

但是在python3中可以。沒有學過python2,這個差別有的深層機理是什麼?

(P.S:在python2中通過給foo添加一個參數例如self,並通過A.foo(A())調用仍可以重複python3中name a is not defined的錯誤)

-----------------------------------------------------

添加一個實例:

python3中以下的類定義無法通過,也會產生a沒有定義的異常:

class A:
a = 3
b = [a + i for i in range(10)]

python3中列表推倒式會建立新的作用域,所以這個跟函數定義一樣,產生新作用域後就找不到a了。

這段代碼在python2中可以通過,因為python2中列表推倒式並不產生新的作用域,所以b的賦值沒有問題。

也就是說,無論在python2還是python3中,凡是有新的作用域產生(通過函數定義或者列表推倒式),那麼在尋找自由變數的時候並沒有去訪問所謂的類定義產生的作用域,而是直接去全局作用域,所以我懷疑類定義並沒有產生新的作用域。


好問題!

The class』s suite is then executed in a new execution frame (see Naming and binding),
using a newly created local namespace and the original global namespace.
(Usually, the suite contains mostly function definitions.) When the class』s
suite finishes execution, its execution frame is discarded but its local
namespace is saved. [4] A class object is then created using the inheritance
list for the base classes and the saved local namespace for the attribute
dictionary.

(語言參考 8.7 節)

所以,為什麼類中函數定義不能訪問這個作用域呢?

Class definition blocks and arguments to exec() and eval() are
special in the context of name resolution
.
A class definition is an executable statement that may use and define names.
These references follow the normal rules for name resolution with an exception
that unbound local variables are looked up in the global namespace

(語言參考 4.2.2 節)

同時它也說了:

The scope of names defined in a class block is limited to the
class block; it does not extend to the code blocks of methods – this includes
comprehensions and generator expressions since they are implemented using a
function scope
. This means that the following will fail:

class A:
a = 42
b = list(a + i for i in range(10))

就是這樣子。有興趣去把整個語言參考啃一遍吧: The Python Language Reference


class的定義並沒有作用域,而是一個隔離的namespace,跟作用域有個微妙的差異,就是它不能在裡面再嵌套其他作用域。文檔當中就是這麼寫的。而且它是在定義的時候立即執行的,而不是像函數一樣,在函數執行的時候才進行綁定。

這導致的結果就是在類當中定義的成員函數並不可以綁定到所謂「類的作用域」,因為根本就沒有這個東西。

後一個問題關於Python2和Python3的差異,Python2當中定義的函數會變成unboundmethod對象,而Python3當中類的成員函數就是普通的function對象,這是個實現差異。可以調用這個事情,只能說是因為實現差異導致的效果,並不是設計的一部分。


貼圖的來啦(不過這個工具還不夠完善)


你舉的例子跟作用域其實無關。你所說的其實只是一個成員函數要不要傳入參數要不要綁定的問題,說白了就是類里定義的函數在默認情況下一定會以一個實例引用作為第一參數,如果你的函數帶個self參數,那麼就不會有問題了。

至於作用域,當然有,雖然是偽裝的。你在類里定義的量並不能不通過類成員的形式來使用;調用類成員其實是調的一個__getattribute__……

對於你的這個現象,建議看看staticmethod這個裝飾器的源碼。

當然,從你的提問看來,你對作用域概念的理解是錯誤的。


是的,可以理解為`class A`的定義並沒有引入一個可供Python搜索的作用域,執行對象方法時,是不會先搜索類定義內部(class A: 語句以下)與方法定義外部之間這個範圍的,而是直接搜索Class A的直接上層作用域。所以要想訪問class關鍵字與方法定義之間這塊兒空間的名字,必須使用類的名字空間(A.__dict__)中的名字來(A.a或self.a)引用。

其實從本質上理解下,class A的定義做的事情實際就是把定義的名字、方法填充到自己的名字空間(A.__dict__)里,不引入新的作用域可以保證LEGB作用域搜索的清晰和簡單性。畢竟在方法內訪問類內的名字name可以直接以(A.name或self.name或cls.name)等等多種不同方式,已經足夠了,沒必要在class定義時再引入一個作用域,只會徒增語言本身的複雜性和實現難度。


以下為個人理解,有問題請高手指正,謝謝,一個一個說:

1. 首先,類定義的代碼塊是可執行代碼,解釋器在遇到類定義的時候,就開始執行,如下圖,運行結果為「i am excuting」:

2. 其次,很多人都說類定義沒有作用域,但我認為其實是有的,如下圖所示:

上述代碼的輸出為:

可以看到,局部變數里只有cVar,而沒有其它,所以在執行Test類這個代碼塊時,是存在一個局部作用域的,那麼在這種情況下,肯定是遵守LEGB原則的,對於當下正在執行的Test類定義這個代碼塊而言,E就是 fVar所在的作用域,G就是 gVar所在的作用域,B仍舊是內置的,所以如下圖的訪問都是OK的:

結果也自然如下圖:

好,繼續

3. 那麼對於成員函數呢?在Python中,類的局部作用域內部,是無法再繼續嵌套其他作用域的(原因可能是類的作用域在類定義的時候就開始執行並創建,類定義完成後就消失了(但形成了一個namespace),而函數是在真正被調用那一刻才開始綁定),所以如下圖:

可以看到,在成員函數method中,Test類的局部作用域是無法成為成員函數method的外部作用域的,method的外部作用域是func,所以 nonlocal cVar會報語法錯誤,而對fVar和gVar的引用是正確的

再來看一個極端情況,類中定義類

可以看到,結果和上面一樣,這就充分證明了,在類的局部作用域中,是無法嵌套其他作用域的,也可以說,在LEGB原則面前,類本身、類方法和嵌套類是平行的。

所以通過實驗總結來說,對於新手,只需要記住以下高度統一的LEGB規則:

L:有3種:函數內部,類定義內部、方法內部

E:L的外層函數作用域

G:全局作用域

B:內置

PS. 本人Python新手,關於Python如此設計的具體原因,還望高手解答,謝謝


我覺得題主說的是對的。類沒有創建新的作用域。所謂作用域也就LEGB,類不屬於其中任何一個。類名如果在模塊級別定義,那類名是屬於Globle的,所以在類內部是可以解析的,類內部定義的變數,被轉化成其屬性,成為其成員,所以通過類名進一步得到其屬性。通過實例和類的關係也可以去引用其屬性。

作用域是作用域的問題,成員與實例和類之間的關係是另外一種關係。(個人感覺這個關係更複雜,期待大神能系統說說這個)


python3可以這麼寫?沒用過

其實python2裡面很明確了

  • 靜態方法:由調用;無默認參數; 所以你只可以用A.a來訪問
  • 普通方法:由對象調用;至少一個self參數;執行普通方法時,自動將調用該方法的對象賦值給self


如圖


我說下「TypeError: unbound method foo() must be called with B instance as first argument (got nothing instead」這個異常的原因吧(python2):

class A:
a = 3
def foo():
print(a)
A.foo()

之所以出錯是因為foo()方法沒有綁定對象。python中的方法(要和函數區分出來,方法特指是類中定義的)都是需要綁定對象之後才能調用的任意。如果你想不綁定對象直接使用類名調用,則可以將方法聲明為類方法(使用classmethod裝飾器)或者是靜態方法(使用staticmethod裝飾器)

至於你說的類變數A.a在類A中為什麼不能寫作a來調用的問題。。。不清楚

不過我認為名字空間和作用域應該是一回事


推薦閱讀:

對寫的python代碼進行加密有什麼好的實現方法?
如何看待「Python星人」這個群體?
Python基本語法學完了,接下來不知道要幹什麼?
python在不用框架的情況下如何寫網站後台?
如何使用Python實現多進程編程?

TAG:Python | Python開發 |