-2

setdefault がa辞書内包表記内で発生するたびに 1 ずつインクリメントされないのはなぜですか? 何が起きてる?

代替ソリューションは素晴らしいです。なぜこれが機能しないのかを理解することに主に興味があります。

setdefault が機能するループ

a = [1,1,2,2,2,3,3]

b = {}

for x in a:
    b[x] = b.setdefault(x, 0) + 1

b

Out[4]: {1: 2, 2: 3, 3: 2}

setdefault による辞書内包表記が機能しない

b = {k: b.setdefault(k, 0) + 1 for k in a}

b

Out[7]: {1: 1, 2: 1, 3: 1}

アップデート

答えてくれてありがとう、私は解決策のタイミングを試してみたかった.

def using_get(a):
    b = {}
    for x in a:
        b[x] = b.get(x, 0) + 1
    return b


def using_setdefault(a):
    b = {}
    for x in a:
        b[x] = b.setdefault(x, 0) + 1
    return b


timeit.timeit(lambda: Counter(a), number=1000000)
Out[3]: 15.19974103783569

timeit.timeit(lambda: using_get(a), number=1000000)
Out[4]: 3.1597984457950474

timeit.timeit(lambda: using_setdefault(a), number=1000000)
Out[5]: 3.231248461129759
4

3 に答える 3

4

dict 内包内にはまだ辞書がありません。以前にバインドされていたものを置き換えて、完全に新しい辞書を作成しています。b

言い換えれば、辞書内包b.setdefault()表記はまったく別の辞書であり、内包表記によって構築されるオブジェクトとは何の関係もありません。

実際、辞書内包表記は、式を実行する前にメソッドbを使用してオブジェクトにバインドされている場合にのみ機能します。がまだ定義されていない.setdefault()場合、またはそのようなメソッドでオブジェクトにバインドされていない場合は、単に失敗して例外が発生します。b

>>> a = [1,1,2,2,2,3,3]
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
NameError: global name 'b' is not defined
>>> b = 42
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
AttributeError: 'int' object has no attribute 'setdefault'

itertools.groupby()並べ替えと;が必要な数値をグループ化しない限り、辞書内包表記で必要なことを行うことはできません。これは効率的なアプローチではありません(O(N) ではなく O(NlogN) ステップが必要です):

>>> from itertools import groupby
>>> {k: sum(1 for _ in group) for k, group in groupby(sorted(a))}
{1: 2, 2: 3, 3: 2}

標準ライブラリには、カウントを行うためのツールが既に付属していることに注意してください。collections.Counter()オブジェクトを参照してください:

>>> from collections import Counter
>>> Counter(a)
Counter({2: 3, 1: 2, 3: 2})
于 2015-09-28T10:12:36.090 に答える
2

実際、2 番目のスニペットはNameError、クリーンな名前空間 (事前に定義されていない名前空間) で試した場合に発生しますb

bruno@bigb:~/Work/playground$ python
Python 2.7.3 (default, Jun 22 2015, 19:33:41) 
>>> a = [1,1,2,2,2,3,3]
>>> b = {k: b.setdefault(k, 0) + 1 for k in a}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
NameError: global name 'b' is not defined

これにより、何がうまくいかなかったのかについてのヒントが得られるはずです。

ステートメント:

b = {k: b.setdefault(k, 0) + 1 for k in a}

最初に右辺の expression を評価 (まあ、実際に試みます...) し{k: b.setdefault(k, 0) + 1 for k in a}、その結果を nameバインドしbます。

b式が評価されたときに が定義されていない場合、上記の例外が発生します (もちろん) 。それが定義され、dict (または setdefault(x, y)メソッド FWIW を持つもの) にバインドされている場合、この時点でバインドされているものを呼び出しsetdefault()た結果が得られます。b

于 2015-09-28T10:16:56.277 に答える
2

b辞書の理解が完了する前に定義されていないため、これは機能しません。通常、これには を取得する必要がありますNameError。そうでない場合は、b以前に定義したためですが、これは別の辞書になります。

そうは言っても、これだけで使えるようcollections.Counterです。

>>> a = [1,1,2,2,2,3,3]
>>> collections.Counter(a)
Counter({2: 3, 1: 2, 3: 2})
于 2015-09-28T10:19:21.643 に答える