web-dev-qa-db-ja.com

高度なネストされたリストの理解の構文

リストを理解するためにリスト内包表記をいじっていたところ、説明できない予期せぬ出力に遭遇しました。この質問は以前に聞かれたことはありませんが、/ is /が繰り返し質問される場合は、お、び申し上げます。

私は本質的に、ジェネレータを生成するジェネレータを書き込もうとしていました。リスト内包表記を使用する単純なジェネレーターは次のようになります。

_(x for x in range(10) if x%2==0) # generates all even integers in range(10)
_

私がやろうとしていたことは、2つのジェネレーターを生成するジェネレーターを書くことでした-最初のジェネレーターはrange(10)の偶数を生成し、2番目のジェネレーターはrange(10)の奇数を生成しました。このために、私はやった:

_>>> (x for x in range(10) if x%2==i for i in range(2))
<generator object <genexpr> at 0x7f6b90948f00>

>>> for i in g.next(): print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> g = (x for x in range(10) if x%2==i for i in range(2))
>>> g
<generator object <genexpr> at 0x7f6b90969730>
>>> g.next()
Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 1, in <genexpr>
    UnboundLocalError: local variable 'i' referenced before assignment
_

割り当て前に「i」が参照されている理由がわかりません

私はそれがi in range(2)と何か関係があるかもしれないと思ったので、私はしました:

_>>> g = (x for x in range(10) if x%2==i for i in [0.1])
>>> g
<generator object <genexpr> at 0x7f6b90948f00>
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
UnboundLocalError: local variable 'i' referenced before assignment
_

これは私には意味がありませんでしたので、最初にもっと簡単なものを試すのがベストだと思いました。だから私はリストに戻って試しました:

_>>> [x for x in range(10) if x%2==i for i in range(2)]
[1, 1, 3, 3, 5, 5, 7, 7, 9, 9]
_

私はこれと同じだと思っていました:

_>>> l = []
>>> for i in range(2):
...     for x in range(10):
...             if x%2==i:
...                     l.append(x)
... 
>>> l
[0, 2, 4, 6, 8, 1, 3, 5, 7, 9] # so where is my list comprehension malformed?
_

しかし、私が思い切って試してみると、これはうまくいきました:

_>>> [[x for x in range(10) if x%2==i] for i in range(2)]
[[0, 2, 4, 6, 8], [1, 3, 5, 7, 9]] # so nested lists in nested list comprehension somehow affect the scope of if statements? :S
_

だから、ifステートメントがどのレベルのスコープで動作するのかという問題だと思ったので、これを試しました。

_>>> [x for x in range(10) for i in range(2) if x%2==i]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_

そして今、私は完全に混乱しています。誰かがこの動作を説明してください。リストの内包表記が正しくないように見える理由がわかりません。また、ifステートメントのスコープがどのように機能するのかもわかりません。

PS:質問を校正しながら、これは宿題の質問のように見えることに気付きました-そうではありません。

42
inspectorG4dget

括弧を使用する必要があります。

((x for x in range(10) if x%2==i) for i in range(2))

これは私には意味がありませんでしたので、最初にもっと簡単なものを試すのがベストだと思いました。だから私はリストに戻って試しました:

[>>> [範囲内のxのx(10)範囲iのx%2 == iの場合] [1、1、3、3、5、5、7、7、9、9]

これは、前のリストの内包表記がi変数を囲んでいるスコープにリークし、現在のスコープのiになるために機能しました。新鮮なpythonインタープリターを起動してみてください。これはNameErrorにより失敗します。カウンターのリーク動作はPython 3.で削除されました。

編集:

Forの同等のループ:

(x for x in range(10) if x%2==i for i in range(2))

だろう:

l = []
for x in range(10):
    if x%2 == i:
        for i in range(2):
            l.append(x)

また、名前エラーが発生します。

EDIT2:

括弧付きバージョン:

((x for x in range(10) if x%2==i) for i in range(2))

以下と同等です:

li = []
for i in range(2):
    lx = []
    for x in range(10):
        if x%2==i:
            lx.append(x)
    li.append(lx)
39
Lie Ryan

Lie Ryanのfor-loopと同等のものは、私を次のように導きます。

[x for i in range(2) for x in range(10) if i == x%2]

出力

[0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
8

リー・ライアンの答えを少し広げて:

何か=(範囲(10)のxのx x(範囲(2)のiのx%2 == iの場合))

以下と同等です:

def _gen1():
    for x in range(10):
        if x%2 == i:
            for i in range(2):
                yield x
something = _gen1()

一方、括弧で囲まれたバージョンは次と同等です。

def _gen1():
    def _gen2():
        for x in range(10):
            if x%2 == i:
                yield x

    for i in range(2):
        yield _gen2()
something = _gen1()

これにより、実際には2つのジェネレーターが生成されます。

[<generator object <genexpr> at 0x02A0A968>, <generator object <genexpr> at 0x02A0A990>]

残念ながら、出力は消費方法に依存するため、生成されるジェネレーターはやや不安定です。

>>> gens = ((x for x in range(10) if x%2==i) for i in range(2))
>>> for g in gens:
        print(list(g))

[0, 2, 4, 6, 8]
[1, 3, 5, 7, 9]
>>> gens = ((x for x in range(10) if x%2==i) for i in range(2))
>>> for g in list(gens):
        print(list(g))

[1, 3, 5, 7, 9]
[1, 3, 5, 7, 9]

私のアドバイスは、ジェネレーター関数を完全に記述することです:iで正しいスコープを取得しようとすることは、ほとんど不可能だと思います。

7
Duncan

嘘は構文的な質問に対する答えを持っています。提案:ジェネレーターの本体にあまり詰め込まないでください。関数ははるかに読みやすくなっています。

def make_generator(modulus):
    return (x for x in range(10) if x % 2 == modulus)
g = (make_generator(i) for i in range(2))
3
Glenn Maynard