web-dev-qa-db-ja.com

ジェネレータ式Python

私は次のような辞書のリストを持っています:

lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]

私は次のようなジェネレータ式を書きました:

next((itm for itm in lst if itm['a']==5))

奇妙な部分は、これは'a'のキーと値のペアでは機能しますが、次回は他のすべての式でエラーをスローすることです。式:

next((itm for itm in lst if itm['b']==6))

エラー:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <genexpr>
KeyError: 'b'
16
Apurva Kunkulol

それは変ではありません。 itm内のすべてのlstに対して。最初にフィルター句を評価します。フィルタ句が_itm['b'] == 6_の場合、そのディクショナリから_'b'_キーをフェッチしようとします。しかし、first辞書にはそのようなキーがないため、エラーが発生します。

最初のフィルターの例では、最初の辞書has _'a'_キーであるため、これは問題ではありません。 next(..)は、ジェネレーターによって発行されたfirst要素にのみ関心があります。したがって、それ以上の要素をフィルタリングするように要求することはありません。

ここで.get(..)を使用して、ルックアップをよりフェイルセーフにすることができます。

next((itm for itm in lst if itm.get('b',None)==6))

辞書にそのようなキーがない場合、.get(..)部分はNoneを返します。また、Noneは6に等しくないため、フィルターは最初の辞書を省略し、さらに別の一致を探します。 デフォルト値を指定しない場合、Noneがデフォルト値であるため、同等のステートメントは次のようになります。

next((itm for itm in lst if itm.get('b')==6))

ジェネレーターの括弧を省略することもできます。複数の引数がある場合にのみ、次の追加の括弧が必要です。

next(itm for itm in lst if itm.get('b')==6)
33

ジェネレータ式を個別に見てください。

_(itm for itm in lst if itm['a']==5)
_

これにより、リスト内の_itm['a'] == 5_のすべてのアイテムが収集されます。ここまでは順調ですね。

その上でnext()を呼び出すと、Pythonに、そのジェネレータ式からfirstアイテムを生成するように指示します。ただし、最初の項目のみです。

したがって、条件が_itm['a'] == 5_の場合、ジェネレーターはリストの最初の要素である_{'a': 5}_を取得し、それに対してチェックを実行します。条件はtrueであるため、itemはジェネレータ式によって生成され、next()によって返されます。

ここで、条件を_itm['b'] == 6_に変更すると、ジェネレーターはリストの最初の要素_{'a': 5}_を再度取得し、キーbで要素を取得しようとします。これは失敗します:

_>>> itm = {'a': 5}
>>> itm['b']
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    itm['b']
KeyError: 'b'
_

最初の要素を見ようとしているときにすでに失敗しているため、2番目の要素を見る機会すらありません。

これを解決するには、ここでKeyErrorを発生させる可能性のある式の使用を避ける必要があります。 dict.get()を使用して、例外を発生させずに値の取得を試みることができます。

_>>> lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
>>> next((itm for itm in lst if itm.get('b') == 6))
{'b': 6}
_
15
poke

明らかに、辞書にitm['b']キーがない場合、'b'KeyErrorを生成します。 1つの方法は

next((itm for itm in lst if 'b' in itm and itm['b']==6))

どの辞書にもNoneが含まれていないと思われる場合は、次のように簡略化できます。

next((itm for itm in lst if itm.get('b')==6))

(これは6と比較するので同じように機能しますが、Noneと比較すると間違った結果になります)

またはプレースホルダーを使用して安全に

PLACEHOLDER = object()
next((itm for itm in lst if itm.get('b', PLACEHOLDER)==6))
6
freakish

確かに、あなたの構造は辞書のリストです。

>>> lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]

最初の状態で何が起こっているかをよりよく理解するには、次のことを試してください。

>>> gen = (itm for itm in lst if itm['a'] == 5)
>>> next(gen)
{'a': 5}
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
KeyError: 'a'

nextを呼び出すたびに、次の要素を処理してアイテムを返します。また...

next((itm for itm in lst if itm['a'] == 5))

どの変数にも割り当てられていないジェネレーターを作成し、lstの最初の要素を処理し、キー'a'が実際に存在することを確認して、アイテムを返します。その後、ジェネレーターはガベージコレクションされます。エラーがスローされない理由は、lstの最初の項目に実際にこのキーが含まれているためです。

したがって、最初の項目に含まれていないものにキーを変更すると、次のようなエラーが発生します。

>>> gen = (itm for itm in lst if itm['b'] == 6)
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
KeyError: 'b'

ソリューション

さて、すでに説明した1つの解決策は、dict.get関数を使用することです。 defaultdictを使用する別の方法は次のとおりです。

from collections import defaultdict
from functools import partial

f = partial(defaultdict, lambda: None)

lst = [{'a': 5}, {'b': 6}, {'c': 7}, {'d': 8}]
lst = [f(itm) for itm in lst] # create a list of default dicts

for i in (itm for itm in lst if itm['b'] == 6):
    print(i)

これは印刷されます:

defaultdict(<function <lambda> at 0x10231ebf8>, {'b': 6})

キーが存在しない場合、defaultdictNoneを返します。

1
cs95

多分あなたはこれを試すことができます:

next(next((itm for val in itm.values() if val == 6) for itm in lst))

これは少し注意が必要かもしれません。2層のgeneratorを生成するため、結果を得るには2つのnextが必要です。

0
Hou Lu