python 类中定义列表_Python-从类定义中的列表理解访问类变量
小編典典
類范圍和列表,集合或字典的理解以及生成器表達式不混合。
為什么;或者,官方用詞
在Python 3中,為列表理解賦予了它們自己的適當范圍(本地名稱空間),以防止其局部變量滲入周圍的范圍內(即使在理解范圍之后,也請參閱Python列表理解重新綁定名稱。對嗎?)。在模塊或函數中使用這樣的列表理解時,這很好,但是在類中,作用域范圍有點奇怪。
在pep 227中有記錄:
類范圍內的名稱不可訪問。名稱在最里面的函數范圍內解析。如果類定義出現在嵌套作用域鏈中,則解析過程將跳過類定義。
在class復合語句文檔中:
然后,使用新創建的本地名稱空間和原始的全局名稱空間,在新的執行框架中執行該類的套件(請參見Naming and binding部分)。(通常,套件僅包含函數定義。)當類的套件完成執行時,其執行框架將被丟棄,但其本地名稱空間將被保存。[4]然后,使用基類的繼承列表和屬性字典的已保存本地名稱空間創建類對象。
強調我的;執行框架是臨時范圍。
由于范圍被重新用作類對象的屬性,因此允許將其用作非本地范圍也將導致未定義的行為。例如,如果一個類方法稱為x嵌套作用域變量,然后又進行操作Foo.x,會發生什么情況?更重要的是,這對的子類意味著什么Foo?Python必須以不同的方式對待類范圍,因為它與函數范圍有很大不同。
最后但并非最不重要的一點是,執行模型文檔中鏈接的命名和綁定部分明確提到了類作用域:
在類塊中定義的名稱范圍僅限于該類塊。它不會擴展到方法的代碼塊–包括理解和生成器表達式,因為它們是使用函數范圍實現的。這意味著以下操作將失敗:
class A:
a = 42
b = list(a + i for i in range(10))
因此,總結一下:你不能從函數,列出理解或包含在該范圍內的生成器表達式中訪問類范圍;它們的作用就好像該范圍不存在。在Python 2中,列表理解是使用快捷方式實現的,但是在Python 3中,它們具有自己的功能范圍(應該一直如此),因此你的示例中斷了。無論Python版本如何,其他理解類型都有其自己的范圍,因此具有set或dict理解的類似示例將在Python 2中中斷。
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(小的)例外;或者,為什么一部分仍然可以工作
無論Python版本如何,理解或生成器表達式的一部分都在周圍的范圍內執行。那就是最外層迭代的表達。在你的示例中,它是range(1):
y = [x for i in range(1)]
# ^^^^^^^^
因此,x在該表達式中使用不會引發錯誤:
# Runs fine
y = [i for i in range(x)]
這僅適用于最外面的可迭代對象。如果一個理解具有多個for子句,則內部for子句的可迭代項將在該理解的范圍內進行評估:
# NameError
y = [i for i in range(1) for j in range(x)]
做出此設計決定是為了在genexp創建時引發錯誤,而不是在創建生成器表達式的最外層可迭代器引發錯誤時,或者當最外層可迭代器變得不可迭代時,在迭代時拋出錯誤。理解共享此行為以保持一致性。
在引擎蓋下看;或者,比你想要的方式更詳細
你可以使用dis模塊查看所有這些操作。在以下示例中,我將使用Python 3.3,因為它添加了合格的名稱,這些名稱可以整潔地標識我們要檢查的代碼對象。產生的字節碼在其他方面與Python 3.2相同。
為了創建一個類,Python本質上采用了構成類主體的整個套件(因此所有內容都比該class :行縮進了一層),并像執行一個函數一樣執行:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
首先LOAD_CONST在Foo該類中為類主體加載一個代碼對象,然后將其放入函數中并進行調用。然后,該調用的結果用于創建類的名稱空間__dict__。到目前為止,一切都很好。
這里要注意的是,字節碼包含一個嵌套的代碼對象。在Python中,類定義,函數,理解和生成器均表示為代碼對象,這些對象不僅包含字節碼,而且還包含表示局部變量,常量,取自全局變量的變量和取自嵌套作用域的變量的結構。編譯后的字節碼引用這些結構,而python解釋器知道如何訪問給定的字節碼。
這里要記住的重要一點是,Python在編譯時創建了這些結構。該class套件是", line 2>已編譯的代碼對象()。
讓我們檢查創建類主體本身的代碼對象。代碼對象具有以下co_consts結構:
>>> foo.__code__.co_consts
(None, ", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo..Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 ( at 0x10a385420, file "", line 4>)
25 LOAD_CONST 3 ('foo..Foo.')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
上面的字節碼創建了類主體。該函數將被執行,結果locals()命名空間將包含x并y用于創建類(但由于x未定義為全局變量而無法使用)。請注意,存儲5在中后x,它將加載另一個代碼對象;那就是列表理解;它像類主體一樣被包裝在一個函數對象中;創建的函數帶有一個位置參數,該參數range(1)可迭代用于其循環代碼,并轉換為迭代器。如字節碼所示,range(1)在類范圍內進行評估。
從中可以看到,用于函數或生成器的代碼對象與用于理解的代碼對象之間的唯一區別是,后者在執行父代碼對象時立即執行;字節碼只是簡單地動態創建一個函數,然后只需幾個小步驟就可以執行它。
Python 2.x在那里改用內聯字節碼,這是Python 2.7的輸出:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
沒有代碼對象被加載,而是FOR_ITER循環內聯運行。因此,在Python 3.x中,為列表生成器提供了自己的適當代碼對象,這意味著它具有自己的作用域。
然而,理解與當模塊或腳本首先被解釋加載的Python源代碼的其余部分一起編譯,編譯器并沒有考慮一類套件的有效范圍。在列表理解任何引用變量必須在查找范圍周圍的類定義,遞歸。如果編譯器未找到該變量,則將其標記為全局變量。列表理解代碼對象的反匯編顯示x確實確實是作為全局加載的:
>>> foo.__code__.co_consts[1].co_consts
('foo..Foo', 5, at 0x10a385420, file "", line 4>, 'foo..Foo.', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
這字節代碼塊加載傳入的第一個參數(range(1)迭代器),就像Python 2.x版本FOR_ITER用來循環遍歷并創建其輸出一樣。
如果我們x在foo函數中定義,x它將是一個單元格變量(單元格是指嵌套作用域):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
在LOAD_DEREF將間接加載x從代碼對象小區對象:
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
實際引用從當前幀數據結構中查找值,當前幀數據結構是從功能對象的.__closure__屬性初始化的。由于為理解代碼對象創建的函數被再次丟棄,因此我們無法檢查該函數的關閉情況。要查看實際的閉包,我們必須檢查一個嵌套函數:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
因此,總結一下:
列表推導在Python 3中獲得了自己的代碼對象,并且函數,生成器或推導的代碼對象之間沒有區別。理解代碼對象包裝在一個臨時函數對象中,并立即調用。
代碼對象是在編譯時創建的,根據代碼的嵌套范圍,所有非局部變量都將標記為全局變量或自由變量。類主體不被視為查找那些變量的范圍。
執行代碼時,Python只需查看全局變量或當前正在執行的對象的關閉。由于編譯器未將類主體作為范圍包括在內,因此不考慮臨時函數名稱空間。
解決方法;或者,該怎么辦
如果要x像在函數中那樣為變量創建顯式作用域,則可以將類作用域變量用于列表理解:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
y可以直接調用“臨時” 功能。我們用它的返回值替換它。它的范圍是解決時考慮x:
>>> foo.__code__.co_consts[1].co_consts[2]
", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
當然,人們在閱讀你的代碼時會對此有些撓頭。你可能要在其中添加一個大的注釋,以解釋你為什么這樣做。
最好的解決方法是僅使用__init__創建一個實例變量:
def __init__(self):
self.y = [self.x for i in range(1)]
并避免一切費力的工作,并避免提出自己的問題。對于你自己的具體示例,我什至不將其存儲namedtuple在類中;而是將其存儲在類中。直接使用輸出(根本不存儲生成的類),或使用全局變量:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
2020-02-07
總結
以上是生活随笔為你收集整理的python 类中定义列表_Python-从类定义中的列表理解访问类变量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 淘宝打听宣传口号文案28句
- 下一篇: 社戏教学反思简短