SORU
16 Aralık 2012, Pazar


Listesi sınıf tanımında kavrama erişim sınıf değişkenleri

Nasıl sınıf tanımı içinde liste bir anlama gelen diğer sınıf değişkenleri erişim musun? Aşağıdaki Python 2'de çalışıyor ama Python 3 başarısız:

class Foo:
    x = 5
    y = [x for i in range(1)]

Python 3.2 hata verir:

NameError: global name 'x' is not defined

Foo.x çalışıyor ya da çalışmıyor. Python 3'te bunun nasıl yapılacağı hakkında bir fikriniz var mı?

Biraz daha karmaşık motive edici bir örnek:

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]

Bu örnekte, apply() iyi bir çözüm olurdu, ama ne yazık ki Python 3 kaldırılır.

CEVAP
17 Aralık 2012, PAZARTESİ


Sınıf kapsamı ve liste veya sözlük kapsam, yanı sıra jeneratör ifadeler karıştırmayın.

Neden; ya da, bu konuda resmi bir kelime

Python 3, liste üreteçleri uygun bir kapsam (yerel ad) kendi, kendi yerel değişkenleri üzerinde çevreleyen kapsamı içine (Python list comprehension rebind names even after scope of comprehension. Is this right?) kanama önlemek için verildi. Bu fonksiyonu bir modüle ya da böyle bir liste anlama kullanırken harika ama sınıflarda, kapsam, uhm birazgarip.

Bu pep 227 belgelenmiştir:

Sınıf kapsamında adları mevcut değildir. İsim çözümlenir en içteki kapsayan bir fonksiyon kapsamı. Bir sınıf tanımı ise iç içe kapsam zinciri oluşur, çözüm süreci atlar sınıf tanımlar.

ve class compound statement documentation:

Sınıfın suite sonra yeni yürütme çerçevesi (Bakınız bölüm Naming and binding), yeni oluşturulan yerel bir ad kullanarak ve özgün genel ad yürütülür. (Genellikle, en suite, yalnızca işlev tanımlarını içerir.) Sınıfın suite yürütme tamamlandığında,yürütme çerçeve atılır ama yerel ad kaydedilir. [4] Bir sınıf nesnesi temel sınıf için miras listesi kullanarak ve öznitelik sözlük için kayıtlı yerel ad oluşturulur.

Benim vurgu; yürütme çerçeve geçici kapsamına almaktadır.

Çünkü bu alan başka amaçla kullanıldı gibi özellikleri hakkında bir sınıf nesnesi, izin olmak bir kapsam da yol açar tanımsız davranış; ne olurdu diye bir sınıf yöntemi denir x gibi bir iç içe kapsamlı bir değişken, o zaman yönetir Foo.x de, örneğin? Daha da önemlisi, o Foo alt sınıflar için ne anlama gelir? Pythonvardırfonksiyon kapsamı çok farklı olarak bir sınıf kapsamında tedavi farklı.

Son olarak, fakat kesinlikle en az değil, İnfaz model belgeleri Naming and binding bağlantılı bölümünde açıkça sınıf kapsamları bahseder:

Kapsam adları tanımlanmış bir sınıf bloğu sınırlı sınıfı block; değil genişletmek için kod blokları yöntemleri – bu içerir kapsam ve jeneratör ifadeleri beri uygulandığını kullanarak bir fonksiyon kapsamı. Bu aşağıdaki başarısız anlamına gelir:

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

Yani, özetlemek gerekirse: erişim fonksiyonları, liste üreteçleri veya jeneratör ifadeler kapsam içine alınmış sınıf kapsamında; onlar ise bu kapsam yoksa genel olarak. Python 2, liste üreteçleri bir kısayol kullanarak uygulanan, ama Python 3'te kendi işlevi, kendi alanlarında başından beri vardı olmalıydı gibi) ve böylece örnek tatili var.

Kaputun altındakiler; ya da, istediğin daha detaylı bir şekilde

Tüm eylem bu dis module kullanarak görebilirsiniz. qualified names düzgün bir şekilde incelemek istiyoruz kod nesneleri tanımlamak ekler çünkü aşağıdaki örneklerde Python 3.3 kullanıyorum. Bayt kodu üretilen aksi takdirde işlevsel Python 3.2 ile aynıdır.

İçinoluşturunbir sınıf, Python esas alır bütün paketi yapar sınıf beden (yani her şeyi girintili bir seviye daha derin daha class <name>: çizgi) ve yürüten ilk olarak bir fonksiyonu

>>> 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         

LOAD_CONST ilk yükler. Foo sınıf beden için kod bir nesne, bir fonksiyon haline getirir, ve onu çağırır.sonuçbu ara sınıf ad alanı oluşturmak için kullanılır, __dict__. Şimdiye kadar çok iyi.

Şey için not işte bu bayt kodu içeren bir iç içe geçmiş nesne kodu, Python, sınıf tanımları, işlevleri, kapsam ve jeneratörler tüm temsil olarak kod içeren nesneleri, sadece bayt, ama aynı zamanda bu yapıların temsil yerel değişkenler, sabitler, değişkenler alınan genel değerler ve Değişkenler alınan iç içe kapsam. Derlenmiş bayt kodu bu yapıları ifade eder ve python yorumlayıcı bytecodes sunulan verilen bu erişmek için nasıl bilir.

Burada hatırlanması gereken en önemli şey Python derleme zamanında bu yapıları oluşturur; class suite zaten derlenmiş kod bir nesne (<code object Foo at 0x10a436030, file "<stdin>", line 2>).

Hadi sınıf beden kendini oluşturur, bu kod nesneyi inceleyin; kod nesneleri co_consts bir yapıya sahip:

>>> 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 Java sınıf beden oluşturur. İşlevi çalıştırılır ve sonuç locals() ad, içeren x y oluşturmak için kullanılır sınıfı (dışında işe yaramıyor çünkü x değil tanımlanan bir küresel). Not bundan sonra saklamak 5 x yükler, başka bir kod nesne; bu liste üreteci; sarılı bir işlev nesne gibi sınıf beden; oluşturulan fonksiyonun aldığı bir konumsal bağımsız değişken, range(1) iterable kullanmak için döngü kodu, oyuncular için bir yineleyici.

Bu bir fonksiyon veya bir jeneratör için kod bir nesne ve bir anlama için bir kod nesne arasındaki tek fark, ikincisinin yürütülen olduğunu görebilirsinizhemenüst kod nesne çalıştırıldığında; bayt kodu sadece anında bir işlev oluşturur ve birkaç küçük adım yürütür.

Python 2.x orada içi bayt kodu yerine kullanır, burada Python 2.7 çıktı

  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 nesne FOR_ITER bir döngü satır içi çalıştırmak yerine yüklendi. Python 3'te.x, listesi jeneratör kendi kapsama sahip olması anlamına gelen kendi uygun kod bir nesne, verildi.

Ancak, anlama birlikte python kaynak geri kalanı Modül veya script ilk tercüman tarafından yüklendiğinde kod derlenmiş ve derleyici yapardeğilsınıf paketi geçerli bir kapsam düşünün. Liste bir anlama herhangi bir başvurulan değişkenleri kapsamında bakmak gerekirçevreleyensınıf tanımı, yinelemeli olarak. Eğer değişken derleyici tarafından bulunmuş olmasaydı, küresel olarak işaretler. Anlama nesne kodu listesi demontaj x aslında genel olarak dolu olduğunu 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         

Bayt kodu bu parça ilk bağımsız değişken (range(1) yineleyici) geçti, ve sadece Python 2 gibi yükler.x sürümü FOR_ITER üzerinde döngü ve çıkış oluşturmak için kullanır.

x, 42 ** hücre bir değişken olurdu foo işlevi (hücre iç içe kapsam bakın) önceden vardı:

>>> 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ı kodu x yükleyecektir hücre nesneleri nesne:

>>> 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 .__closure__ nesne bir işlev olarak başlatılmış olan mevcut çerçeve veri yapıları, değer özniteliği görünüyor. İşlevini anlama kod nesne için oluşturulan tekrar atılır beri, bu işlevin kapatılması incelemek için yapmıyoruz. Eylem kapanışı görmek, iç içe geçmiş bir işlevi yerine incelemek zorundayız:

>>> 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

Yani, özetlemek gerekirse:

  • Liste üreteçleri fonksiyonları, jeneratörler ya da kapsam için kod nesneler arasında fark yoktur 3, ve Python kod nesneleri almak, anlamanın kod nesneleri geçici fonksiyon bir nesne sarılmış ve hemen aradı.
  • Kod nesneleri derleme zamanında oluşturulur ve yerel olmayan herhangi bir değişken kod iç içe kapsamları göre genel veya serbest değişken olarak işaretlenmiş. Sınıf organıdırdeğilbu değişkenleri araştırmak için bir kapsam olarak kabul edilir.
  • Bu kodu çalıştırırken, Python yalnızca genel değerler, ya da şu anda yürütülen nesne kapatılması incelemek gerekmektedir. Derleyici kapsam olarak sınıf beden dahil olmadığı için, geçici fonksiyon ad olarak kabul edilmez.

Bu konuda ne bir geçici çözüm; ya

Eğer x değişken için açıkça bir kapsam oluşturmak olsaydı, fonksiyon, senin gibiolabilirsınıf-kapsam listesinde bir anlama için: değişkenler kullanın

>>> class Foo:
...     x = 5
...     def y(x):
...         return [x for i in range(1)]
...     y = y(x)
... 
>>> Foo.y
[5]

'Geçici' 51* *doğrudan fonksiyon çağrılabilir; biz dönüş yaptığımız zaman bunun yerine değer. Kapsamx çözerken dikkat edilmesi gerekenler:

>>> 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',)

Tabii ki, insanlar sizin bu kodu okuma kafalarının üzerinde küçük bir çizik olacak; bunu neden yapıyorsunuz orada açıklayan kocaman bir yorum koymak isteyebilirsiniz.

En iyi çözüm sadece __init__ örnek değişkeni yerine oluşturmak için kullanılır

def __init__(self):
    self.y = [self.x for i in range(1)]

ve kafa karıştırıcı, ve kendini açıklamak sorulara kaçının. Kendi somut örneği de, aynı görüşte değilim deposu namedtuple sınıf; ya da çıkış doğrudan (yok deposu oluşturulan sınıf), bir global:

from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])

class StateDatabase:
    db = [State(*args) for args in [
       ('Alabama', 'Montgomery'),
       ('Alaska', 'Juneau'),
       # ...
    ]]

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • 30GB

    30GB

    14 AĞUSTOS 2006
  • chrmoe

    chrmoe

    7 Kasım 2006
  • Justin Schenck

    Justin Schen

    24 Kasım 2006