B bir liste olduğunda b + = (4,) neden çalışır ve b = b + (4,) çalışmaz?


77

Eğer alırsak b = [1,2,3]ve yapmaya çalışırsak:b+=(4,)

Dönüyor b = [1,2,3,4], ama yapmaya çalışırsak b = b + (4,)işe yaramaz.

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

b+=(4,)Bir liste ve bir demet ekleyemediğiniz için başarısız olmayı bekledim , ancak işe yaradı. Bu yüzden b = b + (4,)aynı sonucu almayı bekledim , ama işe yaramadı.


4
Burada bir cevap bulunabileceğine inanıyorum .
jochen


İlk başta bunu yanlış okudum ve çok geniş olarak kapatmaya çalıştım, sonra geri çektim. Sonra bunun bir kopya olması gerektiğini düşündüm, ama sadece tekrar oy vermekle kalmayıp, bunun gibi başka cevaplar bulmaya çalışırken saçımı çıkardım. : /
Karl Knechtel

Yanıtlar:


70

"Neden" sorularının sorunu genellikle birden fazla farklı anlama gelebilmeleridir. Aklınızda olabileceğini düşündüğüm her birine cevap vermeye çalışacağım.

"Neden farklı çalışması mümkün?" örneğin bu şekilde cevaplanır . Temel olarak, +=nesnenin farklı yöntemlerini kullanmaya çalışır: __iadd__(yalnızca sol tarafta kontrol edilir), vs __add__ve __radd__("ters ekleme", sol tarafta yoksa kontrol edilir __add__) için +.

"Her sürüm tam olarak ne yapıyor?" Kısacası, list.__iadd__yöntem aynı şeyi yapar list.extend(ancak dil tasarımı nedeniyle hala bir atama vardır).

Bu aynı zamanda örneğin

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+, elbette, yeni bir nesne oluşturur, ancak öğeleri farklı bir diziden çıkarmaya çalışmak yerine açıkça başka bir liste gerektirir.

"+ = İçin bunu yapmak neden yararlı? Daha verimli; extendyöntem yeni bir nesne oluşturmak zorunda değil. Tabii ki, bu bazen (yukarıda olduğu gibi) bazı şaşırtıcı etkilere sahip ve genellikle Python aslında verimlilikle ilgili değil ancak bu kararlar uzun zaman önce verilmiştir.

"+ İle liste ve grup eklemeye izin vermemenin nedeni nedir?" Buraya bakınız (teşekkürler, @ splash58); bir fikir (tuple + list) 'in (list + tuple) ile aynı türü üretmesi ve sonucun ne tür olması gerektiği açık değildir. +=bu sorunu yaşamıyor çünkü a += baçıkçası türünü değiştirmemeliyiz a.


2
Oof, oldukça doğru. Listeler kullanmaz |, bu yüzden örneğimi mahveder. Daha net bir örnek düşünürsem daha sonra takas edeceğim.
Karl Knechtel

1
|Setler için Btw bir işe gidip gelme operatörüdür, ancak +listeler için değildir. Bu nedenle tip belirsizliği hakkındaki argümanın özellikle güçlü olduğunu düşünmüyorum. Operatör işe gidip gelmediğinden, tipler için neden aynı şey gereklidir? Sonuç sadece lhs tipi olduğu kabul edilebilir. Öte yandan, kısıtlayıcı olarak list + iterator, geliştiricinin niyetleri konusunda daha açık olması teşvik edilir. Eğer bir şeyler içeren yeni bir liste oluşturmak istiyorsanız adan şeyler uzatıldı bzaten bunu yapmanın bir yolu var: new = a.copy(); new += b. Bir satır daha ama kristal berraklığında.
a_guest

Bunun neden a += bfarklı davrandığı a = a + bverimlilik değildir. Aslında Guido seçilen davranışı daha az kafa karıştırıcı saydı . Listeyi aargüman olarak alan ve sonra yapan bir fonksiyon düşünün a += [1, 2, 3]. Bu sözdizimi kesinlikle yeni bir liste oluşturmak yerine listeyi değiştiriyor gibi görünüyor , bu yüzden çoğu insanın beklenen sonuç hakkındaki sezgisine göre davranması gerektiğine karar verildi. Bununla birlikte, mekanizma da intmevcut tasarıma yol açan s gibi değişmez tipler için çalışmak zorundaydı .
Sven Marnach

Şahsen tasarımın Ruby'nin yaptığı gibi sadece bir steno yapmaktan daha kafa karıştırıcı olduğunu düşünüyorum , ancak oraya nasıl geldiğimizi anlayabiliyorum. a += ba = a + b
Sven Marnach

21

Bunlar eşdeğer değildir:

b += (4,)

kısaltması:

b.extend((4,))

ederken +bitiştirir listeleri, bunu:

b = b + (4,)

bir diziyi bir listeye birleştirmeye çalışıyorsunuz


14

Bunu yaptığınızda:

b += (4,)

şuna dönüştürülür:

b.__iadd__((4,)) 

Kaputun altında çağırıyor b.extend((4,)), extendbir yineleyici kabul ediyor ve bu yüzden bu da işe yarıyor:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

ama bunu yaptığınızda:

b = b + (4,)

şuna dönüştürülür:

b = b.__add__((4,)) 

yalnızca liste nesnesini kabul et.


4

Resmi belgelerden, değiştirilebilir sıra türleri için :

s += t
s.extend(t)

şu şekilde tanımlanır:

siçeriği ile uzanırt

Hangisi olarak tanımlanmaktan farklı:

s = s + t    # not equivalent in Python!

Bu aynı zamanda , örneğin örnekte olduğu gibi bir demet de dahil olmak üzere herhangi bir dizi türünün işe yarayacağıt anlamına gelir .

Ama aynı zamanda aralıklar ve jeneratörler için de çalışıyor! Örneğin, şunları da yapabilirsiniz:

s += range(3)

3

"Artırılmış" atama operatörleri +=Ekim 2000'de piyasaya sürülen Python 2.0'da tanıtıldı. Tasarım ve gerekçe PEP 203'te açıklanmaktadır . Bu operatörlerin beyan edilen hedeflerinden biri yerinde operasyonların desteklenmesiydi. yazı

a = [1, 2, 3]
a += [4, 5, 6]

listesinin a yerinde güncellenmesi gerekiyor . Bu, listeye başka referanslar olup olmadığı a, örneğin abir işlev bağımsız değişkeni olarak alındığında önemlidir .

Ancak, tamsayılar ve dizgiler de dahil olmak üzere birçok Python türü değişmez olduğundan , işlem her zaman gerçekleşemez, bu nedenle örneğin i += 1bir tamsayı için iyerinde çalışamaz.

Özetle, artırılmış atama operatörlerinin mümkünse yerinde çalışması ve aksi takdirde yeni bir nesne yaratması gerekiyordu. Bu tasarım hedeflerini kolaylaştırmak için, ifadenin x += yaşağıdaki gibi davranacağı belirtildi:

  • Eğer x.__iadd__tanımlanır, x.__iadd__(y)değerlendirilir.
  • Aksi takdirde, x.__add__uygulanırsa x.__add__(y)değerlendirilir.
  • Aksi takdirde, y.__radd__uygulanırsa y.__radd__(x)değerlendirilir.
  • Aksi takdirde bir hata oluşturun.

Bu işlemle elde edilen ilk sonuç geri atanacaktır x(bu sonuç NotImplementedsingleton değilse, bu durumda arama bir sonraki adımla devam eder).

Bu işlem, yerinde modifikasyonu destekleyen türlerin uygulanmasına izin verir __iadd__(). Türleri yok yerinde değişiklik desteklemek Python otomatik esasen geri düşecek çünkü herhangi bir yeni sihirli yöntemleri eklemek gerekmez x = x + y.

Nihayet asıl sorunuza gelelim - neden artırılmış bir atama operatörü ile bir listeye bir demet ekleyebilirsiniz? Bellekten, bunun geçmişi kabaca şöyleydi: Yöntem Python 2.0'da list.__iadd__()zaten var olan list.extend()yöntemi çağırmak için uygulandı . Yineleyiciler Python 2.1'de tanıtıldığında, list.extend()yöntem rasgele yineleyicileri kabul edecek şekilde güncellendi. Bu değişikliklerin sonucu my_list += my_tuplePython 2.1'den başlayarak çalışıldı. list.__add__()Ancak bu usul, sağ argüman olarak keyfi yineleyicinızı desteklemek gerekiyordu asla - bu kesinlikle yazılı dil için uygun bulunmamıştır.

Kişisel olarak, artırılmış operatörlerin uygulanmasının Python'da biraz fazla karmaşık olduğunu düşünüyorum. Birçok şaşırtıcı yan etkisi vardır, örneğin bu kod:

t = ([42], [43])
t[0] += [44]

İkinci satır yükselir TypeError: 'tuple' object does not support item assignment, ancak işlem yine de başarıyla gerçekleştirilir - hatayı yükselten satır yürütüldükten sonra tolacaktır ([42, 44], [43]).


Bravo! PEP'e referans vermek özellikle yararlıdır. Diğer uca bir bağlantı listesi listesi davranışı hakkında önceki SO sorusuna bir bağlantı ekledim. Geri Python önce 2.3 ya da öylesine gibi ne baktığınızda, bu ... Bugüne kıyasla pratik olarak kullanılamaz görünüyor (ve hala çalışıyor ve çok eski bir Mac üzerinde yararlı bir şey yapmak 1.5 almak için başarısız belli belirsiz bir hafızaya sahip)
Karl Knechtel

2

Çoğu kişi X + = Y'nin X = X + Y'ye eşdeğer olmasını beklerdi. Gerçekten de, Mark Lutz tarafından yazılan Python Cep Referansı (4. baskı) "Şu iki format kabaca eşdeğerdir: X = X + Y, X + = Y ". Bununla birlikte, Python'u belirten insanlar onları eşdeğer yapmamıştır. Muhtemelen bu, Python kullanımda olduğu sürece sinirli programcılar tarafından saatlerce hata ayıklama süresi ile sonuçlanacak bir hataydı, ancak şimdi sadece Python böyle. X değişebilir bir sekans tipiyse, X + = Y, X = X + Y ile değil, X.extend (Y) ile eşdeğerdir.


> Muhtemelen bu, Python kullanımda olduğu sürece sinirli programcılar tarafından saatlerce hata ayıklama süresi ile sonuçlanacak bir hataydı <- bu yüzden gerçekten acı çektiniz mi? Deneyimden bahsediyor gibisin. Hikayenizi duymak isterim.
Veky

1

O en açıklandığı gibi burada eğer arrayuygulamıyor __iadd__yöntemi, b+=(4,)sadece bir elemanımız olurdu b = b + (4,)ama belli o kadar, değil arrayuygulamak yapar __iadd__yöntemi. Görünüşe göre __iadd__yöntemin uygulanması şu şekildedir:

def __iadd__(self, x):
    self.extend(x)

Bununla birlikte, yukarıdaki kodun __iadd__yöntemin gerçek uygulaması olmadığını biliyoruz, ancak girişleri extendkabul eden yöntem gibi bir şey olduğunu varsayabiliriz tupple.

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.