2016-03-03 14 views
16

おそらくdict_keysはset-likeオブジェクトとして動作するはずですが、それらはdifferenceメソッドが欠けていて、減算の動作がばらついているようです。dictキーはリスト減算をサポートしますが、タプル減算はサポートしないのはなぜですか?

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

なぜdict_keysクラスがここで整数を反復しようとしますか?ダックタイピングに違反しないのでしょうか?


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

タプルは不変であるため、辞書自体のキーになる可能性があります。その場合、文法的でなくても少なくともプログラマの心には曖昧です。 – L3viathan

+1

@ L3viathan dict.fromkeys( '0123')。keys() - '02''はまだ動作しているので、私は確信していません – wim

+0

うん、良い点。 – L3viathan

答えて

17

これはバグのように見えます。 The implementation is to convert the dict_keys to a set, then call .difference_update(arg) on it.

彼らはちょうど"O"のフォーマット文字列を渡すことで、_PyObject_CallMethodIdPyObject_CallMethodの最適化されたバリアント)を誤用ように見えます。 Thing is, PyObject_CallMethod and friends are documented to require a Py_BuildValue format string that "should produce a tuple"。複数の書式コードを使用すると、値は自動的にtupleにラップされますが、書式コードが1つのみの場合はtupleではなく、値を作成します(この場合はすでにPyObject*です)。参照カウント)。

それがこれを行う可能性がありますどこ私がダウンして追跡していないが、私はどこかにそれが実際にそう呼ばれる関数をすることができtupleを生成しないCallMethod通話を識別し、一つの要素tupleを作るためにそれらを包んだ内部の容疑者引数を期待される形式で受け取る。 tupleを差し引くと、それはすでにtupleです。この修正コードは決してアクティブになりません。 listを渡すと、listを含む1つの要素tupleになります。

difference_updateは、(宣言されているかのように)def difference_update(self, *args)となります。それは開封されたtupleを受けたときに、自分自身を離れて減算する値として言わエントリを扱う、tupleの各エントリから要素を離れないで引くことになっています考えています。

mydict.keys() - (1, 2) 

バグが(大体)、それが何を引き起こしている:あなたがないとき、説明するために

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! 

中:

mydict.keys() - [1, 2] 

することはありません:

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 

だからこそtuplestr作品(間違って)、- ('abc', '123')はへの呼び出しに相当を実行している:

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

str sが自分のキャラクターの反復可能オブジェクトなので、それだけで軽率代わり'abc''123'のなど'a''b''c'、のエントリを削除します期待通り。

これはバグです(私がチャンスを得たとき)、私はCPythonの人々に対してそれを提出します。

正しい行動は、おそらく(このIdバリアントは、このAPIのために存在すると仮定)を呼び出すされている必要があります:すべての梱包上の問題を持っていないだろう、と高速ブートに実行します

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

。最小の変更はフォーマット文字列を"(O)"に変更して、単一の項目に対してもtupleを強制的に作成することですが、フォーマット文字列は何も得られないため、_PyObject_CallMethodObjArgsIdが優れています。

+3

[Issue#26478オープン](https://bugs.python.org/issue26478)。 – ShadowRanger

+1

これで修正されました(メインビルドにフィックスされるためには、3.4/3.5の次のマイナーリリース、または3.6.0を待たなければなりません)。 – ShadowRanger

関連する問題