Sınıf kapsamı ve listesi, küme veya sözlük anlamlarının yanı sıra jeneratör ifadeleri karışmaz.
Neden; veya bu konudaki resmi kelime
Python 3'te, liste değişkenlerine, yerel değişkenlerinin çevreleyen kapsama geçmesini önlemek için kendilerine ait uygun bir kapsam (yerel ad alanı) verilmiştir (bkz. Python listesi anlama, anlama kapsamından sonra bile isimleri yeniden hatırlatır . Bir modülde veya işlevde böyle bir liste kavrayışı kullanırken bu harika, ancak sınıflarda kapsam belirleme biraz, uhm, garip .
Bu, pep 227'de belgelenmiştir :
Sınıf kapsamındaki adlara erişilemez. İsimler, en içteki kapatma işlevi kapsamında çözümlenir. İç içe kapsamlar zincirinde bir sınıf tanımı oluşursa, çözümleme işlemi sınıf tanımlarını atlar.
ve class
bileşik beyan belgelerinde :
Daha sonra sınıfın paketi, yeni oluşturulan bir yerel ad alanı ve orijinal genel ad alanı kullanılarak yeni bir yürütme çerçevesinde yürütülür (bkz. Bölüm Adlandırma ve ciltleme ). (Genellikle, paket yalnızca işlev tanımlarını içerir.) Sınıfın paketi yürütmeyi bitirdiğinde , yürütme çerçevesi atılır ancak yerel ad alanı kaydedilir . [4] Daha sonra temel sınıflar için devralma listesi ve öznitelik sözlüğü için kaydedilmiş yerel ad alanı kullanılarak bir sınıf nesnesi oluşturulur.
Vurgu madeni; yürütme çerçevesi geçici kapsamdır.
Kapsam, bir sınıf nesnesindeki öznitelikler olarak yeniden konumlandırıldığından, yerel olmayan bir kapsam olarak kullanılmasına izin vermek, tanımlanmamış davranışa yol açar; x
iç içe kapsam değişkeni olarak adlandırılan bir sınıf yöntemi Foo.x
, örneğin de manipüle ederse ne olur ? Daha da önemlisi, bu alt sınıflar için ne anlama gelir Foo
? Python , bir sınıf kapsamını işlev kapsamından çok farklı olduğu için farklı şekilde ele almalıdır.
Son olarak, ama en önemlisi, Yürütme modeli belgelerindeki bağlantılı Adlandırma ve bağlama bölümünde sınıf kapsamlarından açıkça bahsedilmektedir:
Sınıf bloğunda tanımlanan adların kapsamı sınıf bloğuyla sınırlıdır; yöntemlerin kod bloklarını kapsamaz - bu, bir işlev kapsamı kullanılarak uygulandıkları için kavrayışları ve üretici ifadelerini içerir. Bu, aşağıdakilerin başarısız olacağı anlamına gelir:
class A:
a = 42
b = list(a + i for i in range(10))
Özetlemek gerekirse, sınıf kapsamına o kapsamdaki fonksiyonlar, liste kavrayışları veya üretici ifadelerinden erişemezsiniz; bu kapsam yokmuş gibi davranırlar. Python 2'de liste kavrayışları bir kısayol kullanılarak uygulandı, ancak Python 3'te kendi işlev kapsamına sahiplerdi (baştan beri olması gerektiği gibi) ve böylece örnek kopuyorlar. Diğer anlama türlerinin Python sürümüne bakılmaksızın kendi kapsamları vardır, bu nedenle Python 2'de küme veya dikte anlama ile benzer bir örnek kırılacaktır.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
(Küçük) istisna; ya, neden bir kısım may eser hala
Python sürümüne bakılmaksızın, çevreleyen kapsamda yürütülen bir anlama veya oluşturma ifadesinin bir kısmı vardır. Bu, en dıştaki yinelenebilir olanın ifadesi olurdu. Örneğinizde range(1)
:
y = [x for i in range(1)]
# ^^^^^^^^
Bu nedenle, x
bu ifadede kullanmak bir hata atmaz:
# Runs fine
y = [i for i in range(x)]
Bu sadece en dıştaki yinelenebilirler için geçerlidir; bir kavrayışın birden fazla for
cümlesi varsa, iç for
maddeler için yinelenebilir değerler kavrama kapsamında değerlendirilir:
# NameError
y = [i for i in range(1) for j in range(x)]
Bu tasarım kararı, bir jeneratör ifadesinin en dıştaki yinelemesini oluştururken bir hata atıldığında veya en dıştaki yinelemenin yinelenemez olmadığı ortaya çıktığında yineleme zamanı yerine genexp oluşturma zamanında bir hata atmak için verildi. Anlamalar bu davranışı tutarlılık için paylaşır.
Kaputun altına bakmak; ya da hiç istediğinden çok daha fazla ayrıntı
dis
Modülü kullanarak bunların tümünü çalışırken görebilirsiniz . Aşağıdaki örneklerde Python 3.3 kullanıyorum , düzgün bir şekilde incelemek istediğimiz kod nesnelerini tanımlayan nitelikli adlar ekliyor . Üretilen bayt kodu aksi halde Python 3.2 ile işlevsel olarak aynıdır.
To oluşturmak bir sınıf, Python esasen (herşey bir seviye aşağıya daha girintili yüzden sınıf gövdesine oluşturan bütün paketi alır class <name>:
çizgi) ve sanki bir işlev olduğunu yürüttüğü:
>>> 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 (<code object Foo at 0x10a436030, file "<stdin>", 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
İlk önce sınıf gövdesi LOAD_CONST
için bir kod nesnesi yükler Foo
, daha sonra bunu bir işleve dönüştürür ve çağırır. Bu çağrının sonucu daha sonra sınıfın ad alanını oluşturmak için kullanılır __dict__
. Çok uzak çok iyi.
Burada dikkat edilmesi gereken nokta bayt kodunun iç içe bir kod nesnesi içermesidir; Python'da sınıf tanımları, fonksiyonlar, kavrayışlar ve üreteçlerin hepsi sadece bayt kodunu değil aynı zamanda yerel değişkenleri, sabitleri, globallerden alınan değişkenleri ve iç içe kapsamdan alınan değişkenleri temsil eden yapılar olarak kod nesneleri olarak temsil edilir. Derlenmiş bayt kodu bu yapıları ifade eder ve python yorumlayıcısı sunulan bayt kodlarına nasıl erişileceğini bilir.
Burada hatırlanması gereken önemli şey, Python'un bu yapıları derleme zamanında yaratmasıdır; class
süitte kod nesne (olduğu <code object Foo at 0x10a436030, file "<stdin>", line 2>
zaten derlenmektedir).
Sınıf gövdesini oluşturan kod nesnesini inceleyelim; kod nesnelerinin bir co_consts
yapısı vardır:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", 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.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
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
Yukarıdaki bayt kodu sınıf gövdesini oluşturur. İşlev yürütülür ve sonuç locals()
ad alanı, sınıfı oluşturmak için kullanılır x
ve y
kullanılır (ancak x
genel olarak tanımlanmadığı için çalışmaz ). Saklandıktan sonra bu Not 5
olarak x
, başka bir kod nesne yükler; liste kavraması bu; sınıf gövdesi gibi bir işlev nesnesine sarılır; oluşturulan işlev range(1)
, döngüsel kodu için bir yineleyici için kullanılan yinelenebilir bir konum bağımsız değişkeni alır . Bayt kodunda gösterildiği gibi range(1)
, sınıf kapsamında değerlendirilir.
Buradan, bir işlev veya üretici için bir kod nesnesi ile bir kavrama için bir kod nesnesi arasındaki tek farkın , üst kod nesnesi yürütüldüğünde, ikincisinin hemen yürütülmesidir; bayt kodu basitçe anında bir işlev oluşturur ve bunu birkaç küçük adımda yürütür.
Python 2.x orada satır içi bayt kodu kullanır, burada Python 2.7 çıktısı:
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
Kod nesnesi yüklenmedi, bunun yerine bir FOR_ITER
döngü satır içi çalıştırıldı. Yani Python 3.x'te, liste üreticisine kendi kod kodu nesnesi verildi, yani kendi kapsamı var.
Ancak, modül veya komut dosyası yorumlayıcı tarafından ilk kez yüklendiğinde anlama, python kaynak kodunun geri kalanıyla birlikte derlendi ve derleyici bir sınıf paketini geçerli bir kapsam olarak görmüyor . Liste kavrayışında başvurulan tüm değişkenler , sınıf tanımını çevreleyen kapsamı tekrarlamalı olarak incelemelidir. Değişken derleyici tarafından bulunamazsa, bunu genel olarak işaretler. Liste anlama kodu nesnesinin sökülmesi x
, gerçekten genel olarak yüklendiğini gösterir :
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 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
Bu bayt kodu, iletilen ilk bağımsız değişkeni ( range(1)
yineleyici) yükler ve tıpkı Python 2.x sürümünün FOR_ITER
onu döngüye sokmak ve çıktısını oluşturmak için kullandığı gibi .
Belirlediğimiz olsaydı x
içinde foo
işlevin yerine, x
bir hücre değişken (hücreler iç içe kapsamlar bakın) olacaktır:
>>> 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
Dolaylı olarak yükleyecek x
kod konusu, hücre nesnelerden:
>>> 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]
Gerçek referans, bir fonksiyon nesnesinin .__closure__
özelliğinden başlatılan geçerli çerçeve veri yapılarından gelen değeri arar . Anlama kodu nesnesi için oluşturulan işlev tekrar atıldığından, bu işlevin kapanışını denetleyemiyoruz. Bir kapatma eylemini görmek için bunun yerine iç içe bir işlevi incelemeliyiz:
>>> 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
Özetlemek gerekirse:
- Liste kavrayışları Python 3'te kendi kod nesnelerini alır ve fonksiyonlar, üreteçler veya kavrayışlar için kod nesneleri arasında bir fark yoktur; anlama kodu nesneleri geçici bir işlev nesnesine sarılır ve hemen çağrılır.
- Kod nesneleri derleme zamanında oluşturulur ve yerel olmayan değişkenler, kodun iç içe kapsamlarına bağlı olarak genel veya serbest değişkenler olarak işaretlenir. Sınıf beden olduğu değil bu değişkenleri arayanlar için bir kapsam düşündü.
- Kodu yürütürken, Python yalnızca genel olanlara veya o anda yürütülmekte olan nesnenin kapanmasına bakmak zorundadır. Derleyici sınıf gövdesini kapsam olarak içermediğinden, geçici işlev ad alanı dikkate alınmaz.
Geçici çözüm; ya da bu konuda ne yapmalı
Eğer için açık bir kapsam oluşturmak için olsaydı x
bir işlevde, şöyle değişken olabilir bir liste anlama için sınıf kapsamlı değişkenleri kullanın:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
'Geçici' y
işlev doğrudan çağrılabilir; dönüş değeri ile yaptığımız zaman değiştiririz. Kapsamı edilir çözme dikkate x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
Elbette, kodunuzu okuyan insanlar bunun üzerine biraz kafa çizecek; Buraya neden bunu yaptığınızı açıklayan büyük bir yağ yorumu koymak isteyebilirsiniz.
En iyi çözüm, yalnızca __init__
bir örnek değişkeni oluşturmak için kullanmaktır:
def __init__(self):
self.y = [self.x for i in range(1)]
ve kafa çizmekten ve kendinizi açıklamak için sorulardan kaçının. Kendi somut örneğiniz namedtuple
için, sınıfı sınıfta bile saklamam ; çıktıyı doğrudan kullanın (oluşturulan sınıfı hiç saklamayın) veya genel bir kullanın:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
Python 3.2 ve ben ne beklenir 3.3.