2016-03-03 14 views
16

Muhtemelen dict_keys set benzeri bir nesne olarak davranması gerekiyor, ancak difference yönteminden yoksundur ve çıkarma davranışı farklılaşıyor gibi görünüyor.Açıklama tuşları neden liste çıkarma işlemini destekliyor, ancak tuple çıkarma işlemini desteklemiyor?

>>> d = {0: 'zero', 1: 'one', 2: 'two', 3: 'three'} 
>>> d.keys() - [0, 2] 
{1, 3} 
>>> d.keys() - (0, 2) 
TypeError: 'int' object is not iterable 

Neden dict_keys sınıfı burada bir tamsayı yinelemeyi dener? Ördek yazmayı ihlal etmiyor mu?


>>> dict.fromkeys(['0', '1', '01']).keys() - ('01',) 
{'01'} 
>>> dict.fromkeys(['0', '1', '01']).keys() - ['01',] 
{'1', '0'} 
+1

Tuple'ler değişmezdir ve bu nedenle sözlüğün kendileri için anahtarlar olabilir, bu durumda belirsiz olur - eğer en azından bir programcının zihninde sözdizimsel değilse. – L3viathan

+1

@ L3viathan İkna olmadım çünkü 'dict.fromkeys ('') tuşları() -' 02 'hala çalışıyor – wim

+0

Evet, iyi nokta. – L3viathan

cevap

17

Bu bir hata gibi gözüküyor. onlar sadece "O" bir biçim dizesi geçirerek, _PyObject_CallMethodId (PyObject_CallMethod optimize edilmiş bir varyantı) kötüye gibi The implementation is to convert the dict_keys to a set, then call .difference_update(arg) on it.

görünüyor. Thing is, PyObject_CallMethod and friends are documented to require a Py_BuildValue format string that "should produce a tuple". Birden fazla biçim koduyla, tuple'daki değerleri otomatik olarak kaydırır, ancak yalnızca bir biçim koduyla, tuple değil, yalnızca değeri oluşturur (bu durumda, zaten PyObject* olduğu için, hepsi bunu artırır referans sayısı). Bu yapıyor olabilir nerede aşağı izlenmez olsa da

, ben tuple sözde fonksiyon aslında can bir tek eleman yapmak için bir tuple üretemezler CallMethod çağrıları belirleme ve bunları sarıyor içlerinde bir yerlerde şüpheli argümanları beklenen formatta alırsınız. Bir tuple çıkarıldığında, zaten bir tuple, ve bu düzeltme kodu hiçbir zaman etkinleşmez; list'u iletirken, list'u içeren tek bir öğe tuple haline gelir.

difference_update varargs (def difference_update(self, *args) olarak bildirilmiş gibi) alır. Bu nedenle, açılmamış tuple'u aldığında, tuple'daki her girdiden öğeleri çıkarması gerektiğini düşünür, söz konusu girdileri kendileri çıkarmak için değerler olarak ele almaz. Bunu yaparken, göstermek için:

mydict.keys() - (1, 2) 

böcek bunu yapmak neden olan (kabaca):

result = set(mydict) 
# We've got a tuple to pass, so all's well... 
result.difference_update(*(1, 2)) # Unpack behaves like difference_update(1, 2) 
# OH NO! 

ederken:

mydict.keys() - [1, 2] 

yapar:

result = set(mydict) 
# [1, 2] isn't a tuple, so wrap 
result.difference_update(*([1, 2],)) # Behaves like difference_update([1, 2]) 
# All's well 

Bu yüzden yerine 'abc' ve '123' vs., 'c', 'b', 'a' için

result.difference_update(*('abc', '123')) 
# or without unpacking: 
result.difference_update('abc', '123') 

ve str ler kendi karakterlerinin Iterables olduğundan, sadece kaygısızca kaldırır girişleri:işleri (yanlış), - ('abc', '123') bir çağrı eşdeğer performans beklediğin gibi.

Temel olarak, bu bir hatadır ve (bir şans elde ettiğimde), bunu CPython milletine karşı yazacağım.

doğru davranış muhtemelen (bu Id varyant bu API için var varsayarak) aramak olmalıydı: hiç paketleme sorunları olmazdı ve daha hızlı boot aday olacağını

_PyObject_CallMethodObjArgsId(result, &PyId_difference_update, other, NULL); 

; En küçük değişiklik, biçim dizgesini "(O)" biçiminde tek bir öğe için bile tuple oluşturmaya zorlamak olacaktır, ancak biçim dizisi hiçbir şey kazanmadığından, _PyObject_CallMethodObjArgsId daha iyidir.

+3

[Yayın No. 26478 açıldı] (https://bugs.python.org/issue26478). – ShadowRanger

+1

Ve şimdi düzeltildi (ancak, 3.4/3.5'in bir sonraki küçük sürümünün veya düzeltmenin ana yapılara dahil edilmesi için 3.6.0 için beklemek zorunda kalacaksınız). – ShadowRanger