Anlama listesi, anlama kapsamından sonra bile isimleri yeniden bağlar. Bu doğru mu?


118

Kavrayışlar, kapsam belirleme ile bazı beklenmedik etkileşimler yaşıyor. Bu beklenen davranış mı?

Bir yöntemim var:

def leave_room(self, uid):
  u = self.user_by_id(uid)
  r = self.rooms[u.rid]

  other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid]
  other_us = [self.user_by_id(uid) for uid in other_uids]

  r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above

  # Interestingly, it's rebound to the last uid in the list, so the error only shows
  # up when len > 1

Sızlanma riski altında, bu acımasız bir hata kaynağıdır. Yeni kod yazarken, yeniden bağlama nedeniyle ara sıra çok tuhaf hatalar buluyorum - şimdi bile bunun bir sorun olduğunu biliyorum. "Her zaman alt çizgi ile liste anlayışlarında geçici değişkenlere önsöz" gibi bir kural yapmam gerekiyor, ama bu bile aptalca bir kanıt değil.

Bu rastgele saatli bomba bekleme türünün olması, liste anlayışlarının tüm hoş "kullanım kolaylığını" geçersiz kılar.


7
-1: "acımasız hata kaynağı"? Zorlukla. Neden böyle tartışmalı bir terim seçmelisiniz? Genellikle en pahalı hatalar, gereksinimlerin yanlış anlaşılması ve basit mantık hatalarıdır. Bu tür bir hata, birçok programlama dilinde standart bir problem olmuştur. Neden 'acımasız' diyorsun?
S.Lott

44
En az sürpriz ilkesini ihlal ediyor. Python dokümantasyonunda liste kavrayışlarında bahsedilmemiştir, ancak bunların ne kadar kolay ve kullanışlı olduklarından birkaç kez bahsedilir. Esasen bu, dil modelimin dışında var olan bir kara mayınıydı ve bu nedenle öngörmem imkansızdı.
Jabavu Adams

33
"Acımasız hata kaynağı" için +1. 'Acımasız' kelimesi tamamen haklı.
Nathaniel

3
Burada gördüğüm tek "acımasız" şey adlandırma kuralınız. Bu artık 80'ler değil, 3 karakterli değişken isimleriyle sınırlı değilsiniz.
UloPe

5
Not: Belgeleme , liste anlamanın açık- fordöngü yapısına eşdeğer olduğunu ve for-döngü sızıntı değişkenlerini belirtir . Yani açık değildi, üstü kapalı olarak belirtildi.
Bakuriu

Yanıtlar:


172

Liste anlayışları Python 2'de döngü kontrol değişkenini sızdırıyor ancak Python 3'te değil. İşte Guido van Rossum (Python'un yaratıcısı) bunun arkasındaki geçmişi açıklıyor :

Ayrıca Python 3'te, liste anlayışları ve oluşturucu ifadeleri arasındaki denkliği geliştirmek için başka bir değişiklik yaptık. Python 2'de, liste kavrayışı döngü kontrol değişkenini çevreleyen kapsama "sızdırır":

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

Bu, liste anlamalarının orijinal uygulamasının bir ürünüdür; Yıllardır Python'un "kirli küçük sırlarından" biriydi. Liste anlamalarını kör edici derecede hızlı hale getirmek için kasıtlı bir uzlaşma olarak başladı ve yeni başlayanlar için yaygın bir tuzak olmasa da, kesinlikle ara sıra insanları soktu. Jeneratör ifadeleri için bunu yapamadık. Üreteç ifadeleri, yürütülmesi ayrı bir yürütme çerçevesi gerektiren üreteçler kullanılarak gerçekleştirilir. Bu nedenle, oluşturucu ifadeler (özellikle kısa bir dizide yineleniyorlarsa) liste anlamalarından daha az etkiliydi.

Bununla birlikte, Python 3'te, liste anlamalarının "kirli küçük sırrını", jeneratör ifadeleriyle aynı uygulama stratejisini kullanarak düzeltmeye karar verdik. Bu nedenle, Python 3'te, yukarıdaki örnek (print (x) :-) kullanmak için değişiklik yapıldıktan sonra :-) 'önce' yazdıracak ve listedeki 'x'in geçici olarak gölgelediğini ancak çevredeki' x'i geçersiz kılmadığını kanıtlayacaktır. dürbün.


14
Guido'nun onu "kirli küçük bir sır" olarak adlandırmasına rağmen, birçokları bunu bir hata değil, bir özellik olarak değerlendirdi.
Steven Rumbalski

38
Ayrıca 2.7'de, küme ve sözlük anlamalarının (ve oluşturucuların) özel kapsamları olduğunu, ancak liste anlamalarının hala bulunmadığını unutmayın. Bu, birincisinin Python 3'ten geri taşınması açısından biraz mantıklı olsa da, liste anlamalarıyla zıtlığı gerçekten sarsıcı hale getiriyor.
Matt B.

7
Bunun delice eski bir soru olduğunu biliyorum, ama neden bazıları bunu dilin bir özelliği olarak değerlendirdi? Bu tür bir değişken sızıntısının lehine bir şey var mı?
Mathias Müller

2
çünkü: sızıntı yapan döngülerin iyi nedenleri vardır, özellikle. son değere erkenden sonra erişmek için break- ancak karşılaştırmalarla ilgisiz. İnsanların ifadenin ortasında değişkenler atamak istedikleri bazı comp.lang.python tartışmalarını hatırlıyorum. Bulunan daha az çılgınca yol, cümleciklerin tek değeriydi, örneğin. sum100 = [s for s in [0] for i in range(1, 101) for s in [s + i]][-1], ancak sadece bir anlama-yerel değişkeni gerektirir ve Python 3'te de aynı şekilde çalışır. Bence değişkeni bir ifadenin dışında görünür hale getirmenin tek yolu "sızıntı" idi. Herkes bu tekniklerin korkunç olduğu konusunda hemfikir :-)
Beni Cherniavsky-Paskin

1
Buradaki sorun, liste anlamalarının çevreleyen kapsamına erişim değil, çevreleyen kapsamı etkileyen liste anlayışları kapsamındaki bağlayıcıdır.
Felipe Gonçalves Marques

48

Evet, liste anlamaları, tıpkı döngüler için olduğu gibi Python 2.x'te de değişkenlerini "sızdırır".

Geriye dönüp bakıldığında, bu bir hata olarak kabul edildi ve jeneratör ifadeleri ile önlendi. DÜZENLEME: Matt B.'nin belirttiği gibi gibi, set ve sözlük anlama sözdizimleri Python 3'ten geri yüklendiğinde de kaçınıldı.

Liste anlama davranışının Python 2'de olduğu gibi bırakılması gerekiyordu, ancak Python 3'te tamamen düzeltildi.

Bu şu anlama gelir:

list(x for x in a if x>32)
set(x//4 for x in a if x>32)         # just another generator exp.
dict((x, x//16) for x in a if x>32)  # yet another generator exp.
{x//4 for x in a if x>32}            # 2.7+ syntax
{x: x//16 for x in a if x>32}        # 2.7+ syntax

xher zaman bu süre ifadeye yerel:

[x for x in a if x>32]
set([x//4 for x in a if x>32])         # just another list comp.
dict([(x, x//16) for x in a if x>32])  # yet another list comp.

Python 2.x'te tümü xdeğişkeni çevreleyen kapsama sızdırır .


Python 3.8 (?) İçin GÜNCELLEME : PEP 572 , kasıtlı olarak kavrayışlardan ve oluşturucu ifadelerinden sızan:= atama operatörünü tanıtacak ! Temelde 2 kullanım durumu ile motive edilir: ve gibi erken sonlandırıcı işlevlerden bir "tanık" yakalamak :any()all()

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

ve değiştirilebilir durum güncelleniyor:

total = 0
partial_sums = [total := total + v for v in values]

Tam kapsam için Ek B'ye bakın . Değişken, en yakın çevrelere atanır defveya lambdabu işlev nonlocalveya belirtmediği sürece global.


7

Evet, atama tıpkı bir fordöngüde olduğu gibi orada gerçekleşir . Yeni kapsam oluşturulmuyor.

Bu kesinlikle beklenen davranıştır: her döngüde, değer belirttiğiniz ada bağlıdır. Örneğin,

>>> x=0
>>> a=[1,54,4,2,32,234,5234,]
>>> [x for x in a if x>32]
[54, 234, 5234]
>>> x
5234

Bir kez farkına vardıktan sonra, kaçınılması yeterince kolay görünüyor: anlama içindeki değişkenler için mevcut isimleri kullanmayın.


2

İlginç bir şekilde bu, sözlüğü veya küme anlayışlarını etkilemez.

>>> [x for x in range(1, 10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
9
>>> {x for x in range(1, 5)}
set([1, 2, 3, 4])
>>> x
9
>>> {x:x for x in range(1, 100)}
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99}
>>> x
9

Ancak yukarıda belirtildiği gibi 3'te düzeltildi.


Bu sözdizimi Python 2.6'da hiç çalışmıyor. Python 2.7'den mi bahsediyorsunuz?
Paul Hollingsworth

Python 2.6, yalnızca Python 3.0'da olduğu gibi liste anlayışına sahiptir. 3.1 küme ve sözlük anlamaları eklendi ve bunlar 2.7'ye taşındı. Bu net değilse özür dilerim. Başka bir cevaba sınırlama getirilmesi amaçlanmıştı ve hangi versiyonlar için geçerli olduğu tamamen basit değil.
Chris Travers

Yeni kod için python 2.7 kullanmanın mantıklı olduğu durumlar olduğunu iddia edebiliyorum, python 2.6 için aynı şeyi söyleyemem ... İşletim sisteminizle gelen 2.6 olsa bile, takılıp kalmıyorsunuz o. Virtualenv kurmayı ve yeni kod için 3.6 kullanmayı düşünün!
Alex L

Python 2.6 ile ilgili nokta, mevcut eski sistemleri sürdürürken ortaya çıkabilir. Dolayısıyla tarihsel bir not olarak tamamen alakasız değildir. 3.0 ile aynı (ick)
Chris Travers

Kaba görünüyorsam kusura bakma ama bu soruya hiçbir şekilde cevap vermiyor. Yorum olarak daha uygun.
0xc0de

1

Bu davranış arzu edilmediğinde python 2.6 için bazı geçici çözümler

# python
Python 2.6.6 (r266:84292, Aug  9 2016, 06:11:56)
Type "help", "copyright", "credits" or "license" for more information.
>>> x=0
>>> a=list(x for x in xrange(9))
>>> x
0
>>> a=[x for x in xrange(9)]
>>> x
8

-1

Python3'te liste anlayışındayken değişken, kapsamı bittikten sonra değişmiyor, ancak basit döngü kullandığımızda değişken kapsam dışında yeniden atanıyor.

i = 1 baskı (i) baskı ([i (5) aralığında]) baskı (i) i'nin değeri sadece 1 olarak kalacaktır.

Şimdi sadece döngü için kullanın, i'nin değeri yeniden atanacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.