web-dev-qa-db-fra.com

Syntaxe avancée de compréhension des listes imbriquées

Je jouais avec des listes de compréhension pour mieux les comprendre et je suis tombé sur une sortie inattendue que je ne suis pas en mesure d'expliquer. Je n'ai pas trouvé cette question posée auparavant, mais si c'est/est/une question répétée, je m'excuse.

J'essayais essentiellement d'écrire un générateur qui générait des générateurs. Un générateur simple qui utilise la compréhension de liste ressemblerait à ceci:

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

Ce que j'essayais de faire était d'écrire un générateur qui a généré deux générateurs - le premier a généré les nombres pairs dans la plage (10) et le second a généré les nombres impairs dans la plage (10). Pour cela, j'ai fait:

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

Je ne comprends pas pourquoi "i" est référencé avant l'affectation

J'ai pensé que cela pouvait avoir quelque chose à voir avec i in range(2), alors j'ai fait:

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

Cela n'avait aucun sens pour moi, alors j'ai pensé qu'il valait mieux essayer quelque chose de plus simple en premier. Je suis donc retourné aux listes et j'ai essayé:

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

que je pensais être le même que:

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

Mais quand je l'ai essayé sur une intuition, cela a fonctionné:

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

J'ai donc pensé que cela pourrait être un problème avec le niveau de portée de l'instruction if. J'ai donc essayé ceci:

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

Et maintenant, je suis complètement confus. Quelqu'un peut-il expliquer ce comportement? Je ne comprends pas pourquoi mes compréhensions de liste semblent être mal formées, ni le fonctionnement de la portée des instructions if.

PS: En corrigeant la question, j'ai réalisé que cela ressemblait un peu à une question de devoirs - ce n'est pas le cas.

42
inspectorG4dget

vous devez utiliser des parenthèses:

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

Cela n'avait pas de sens pour moi, donc j'ai pensé qu'il valait mieux essayer quelque chose de plus simple en premier. Je suis donc retourné aux listes et j'ai essayé:

[>>> [x pour x dans la plage (10) si x% 2 == i pour i dans la plage (2)] [1, 1, 3, 3, 5, 5, 7, 7, 9, 9]

Cela a fonctionné car une compréhension de liste précédente fuit la variable i dans la portée englobante et devient le i pour la portée actuelle. Essayez de démarrer un nouvel interpréteur python, et cela échouerait en raison de NameError. Le comportement de fuite du compteur a été supprimé dans Python 3.

MODIFIER:

L'équivalent de boucle pour:

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

serait:

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

ce qui donne également une erreur de nom.

EDIT2:

la version entre parenthèses:

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

est équivalent à:

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

L'équivalent for-loop de Lie Ryan m'amène à ce qui suit, qui semble très bien fonctionner:

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

les sorties

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

Développant un peu la réponse de Lie Ryan:

quelque chose = (x pour x dans la plage (10) si x% 2 == i pour i dans la plage (2))

est équivalent à:

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

alors que la version entre parenthèses équivaut à:

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

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

Cela donne en fait les deux générateurs:

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

Malheureusement, les générateurs qu'il génère sont quelque peu instables car la sortie dépendra de la façon dont vous les consommerez:

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

Mon conseil est d'écrire les fonctions du générateur dans leur intégralité: je pense qu'essayer d'obtenir la bonne portée sur i sans le faire peut être presque impossible.

7
Duncan

Lie a la réponse à la question syntaxique. Une suggestion: ne pas trop fourrer dans le corps d'un générateur. Une fonction est beaucoup plus lisible.

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