Maneira pitônica de combinar o loop FOR e a instrução IF

266

Eu sei como usar os loops e as instruções if em linhas separadas, como:

>>> a = [2,3,4,5,6,7,8,9,0]
... xyz = [0,12,4,6,242,7,9]
... for x in xyz:
...     if x in a:
...         print(x)
0,4,6,7,9

E sei que posso usar uma compreensão de lista para combiná-las quando as declarações são simples, como:

print([x for x in xyz if x in a])

Mas o que não consigo encontrar é um bom exemplo em qualquer lugar (para copiar e aprender), demonstrando um conjunto complexo de comandos (não apenas "print x") que ocorrem após a combinação de um loop for e algumas declarações if. Algo que eu esperaria se parecer com:

for x in xyz if x not in a:
    print(x...)

Não é assim que o python deve funcionar?

ChewyChunks
fonte
23
É assim que é ... não complique as coisas tentando simplificá-las. Pythonic não significa evitar todo forloop e ifdeclaração explícitos .
Felix Kling
2
Você pode usar a lista gerada em sua compreensão da lista em um loop for. Isso seria um pouco parecido com o seu último exemplo.
Jacob
Então, voltando ao processamento, qual é a maneira mais rápida de combinar um loop for com uma instrução if, se a instrução if estiver excluindo valores que já foram correspondidos e a lista estiver crescendo continuamente durante a iteração do loop for?
ChewyChunks
3
@Chewy, estruturas de dados adequadas tornarão o código mais rápido, e não açúcar sintático. Por exemplo, x in aé lento se ahouver uma lista.
Nick Dandoulakis 8/08
1
Este é Python, uma linguagem interpretada; por que alguém está discutindo a velocidade do código?
ArtOfWarfare

Respostas:

323

Você pode usar expressões geradoras como esta:

gen = (x for x in xyz if x not in a)

for x in gen:
    print x
Kugel
fonte
1
gen = (y for (x,y) in enumerate(xyz) if x not in a)retorna >>> 12quando digito for x in gen: print x- então por que o comportamento inesperado com enumerar?
ChewyChunks
9
Possível, mas não melhor do que o original para e se blocos.
Mike Graham
1
@ChewyChunks. Isso funcionaria, mas a chamada para enumerar é redundante.
Johnsyweb
132
Eu realmente sinto falta em python ser capaz de dizerfor x in xyz if x:
bgusach
10
for x in (x for x in xyz if x not in a):trabalha para mim, mas por que você não deve apenas ser capaz de fazer for x in xyz if x not in a:, eu não tenho certeza ...
Matt Wenham
34

Conforme The Zen of Python (se você está se perguntando se o seu código é "Pythonic", esse é o lugar certo):

  • Bonito é melhor que feio.
  • Explícito é melhor que implícito.
  • Simples é melhor que complexo.
  • Flat é melhor que aninhado.
  • Legibilidade conta.

A maneira pitônica de obter o valor de dois s é:sorted intersectionset

>>> sorted(set(a).intersection(xyz))
[0, 4, 6, 7, 9]

Ou os elementos que estão xyzmas não estão em a:

>>> sorted(set(xyz).difference(a))
[12, 242]

Mas para um loop mais complicado, você pode planificá-lo iterando sobre uma expressão de gerador bem nomeada e / ou chamando para uma função bem nomeada. Tentar encaixar tudo em uma linha raramente é "pitonico".


Atualize após comentários adicionais sobre sua pergunta e a resposta aceita

Não sei ao certo o que você está tentando fazer enumerate, mas se afor um dicionário, você provavelmente desejará usar as teclas, assim:

>>> a = {
...     2: 'Turtle Doves',
...     3: 'French Hens',
...     4: 'Colly Birds',
...     5: 'Gold Rings',
...     6: 'Geese-a-Laying',
...     7: 'Swans-a-Swimming',
...     8: 'Maids-a-Milking',
...     9: 'Ladies Dancing',
...     0: 'Camel Books',
... }
>>>
>>> xyz = [0, 12, 4, 6, 242, 7, 9]
>>>
>>> known_things = sorted(set(a.iterkeys()).intersection(xyz))
>>> unknown_things = sorted(set(xyz).difference(a.iterkeys()))
>>>
>>> for thing in known_things:
...     print 'I know about', a[thing]
...
I know about Camel Books
I know about Colly Birds
I know about Geese-a-Laying
I know about Swans-a-Swimming
I know about Ladies Dancing
>>> print '...but...'
...but...
>>>
>>> for thing in unknown_things:
...     print "I don't know what happened on the {0}th day of Christmas".format(thing)
...
I don't know what happened on the 12th day of Christmas
I don't know what happened on the 242th day of Christmas
Johnsyweb
fonte
Parece com os comentários abaixo, eu deveria estar estudando sobre geradores. Eu nunca os usei. Obrigado. Um gerador é mais rápido que a combinação equivalente das instruções FOR e IF? Também usei conjuntos, mas algumas vezes elementos redundantes em uma lista são informações que não consigo descartar.
ChewyChunks
@ChewyChunks: Geradores não são a única maneira de serem Pythonic!
Johnsyweb
3
@ Johnsyweb, se você vai citar o Zen do Python: "Deve haver um - e de preferência apenas um - caminho óbvio para fazê-lo."
Wooble 8/08/11
@ Wooble: Deveria. Eu citei essa seção na minha resposta a outra pergunta na mesma época!
Johnsyweb 8/08
18

Pessoalmente, acho que esta é a versão mais bonita:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]
for x in filter(lambda w: w in a, xyz):
  print x

Editar

Se você deseja evitar o uso do lambda, pode usar o aplicativo de funções parciais e usar o módulo do operador (que fornece funções para a maioria dos operadores).

https://docs.python.org/2/library/operator.html#module-operator

from operator import contains
from functools import partial
print(list(filter(partial(contains, a), xyz)))
Alex
fonte
4
filter(a.__contains__, xyz). Geralmente, quando as pessoas usam lambda, elas realmente precisam de algo muito mais simples.
Veky
Eu acho que você entendeu algo errado. __contains__é um método como outro qualquer, apenas é um método especial , o que significa que pode ser chamado indiretamente por um operador ( inneste caso). Mas também pode ser chamado diretamente, faz parte da API pública. Os nomes privados são definidos especificamente como tendo no máximo um sublinhado à direita, para fornecer exceção a nomes de métodos especiais - e estão sujeitos a nomes incorretos quando lexicamente nos escopos de classe. Consulte docs.python.org/3/reference/datamodel.html#specialnames e docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky
Certamente, tudo bem, mas duas importações apenas para poder se referir a um método acessível usando apenas um atributo parecem estranhas (os operadores geralmente são usados ​​quando o despacho duplo é essencial, mas iné despachado individualmente pelo operando certo). Além disso, observe que operatortambém exporta containsmétodo sob o nome __contains__, portanto, certamente não é um nome privado. Acho que você precisará aprender a conviver com o fato de que nem todo sublinhado duplo significa "afastar-se". : -]
Veky
Acho que suas lambdanecessidades de fixação para incluir not: lambda w: not w in a, xyz
javadba
O filtro parece mais elegante, especialmente para condições complexas que se tornariam funções definidas em vez de lambdas, talvez nomear a função lambda acrescentaria alguma legibilidade. O gerador parece melhor quando os elementos iterados são alguma modificação nos itens da lista
Khanis Rok
16

A seguir, é uma simplificação / uma linha da resposta aceita:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]

for x in (x for x in xyz if x not in a):
    print(x)

12
242

Observe que o generatorfoi mantido em linha . Isso foi testado python2.7e python3.6 (observe os parênteses no print;))

javadba
fonte
10

Eu provavelmente usaria:

for x in xyz: 
    if x not in a:
        print x...
Wim Feijen
fonte
@KirillTitov Sim, python é uma linguagem fundamentalmente não funcional (essa é uma codificação puramente imperativa - e eu concordo com o autor desta resposta que é a maneira como o python é configurado para ser escrito. Tentar usar os funcionais leva a uma leitura ruim ou não- pythonicEu posso codificar funcionalmente em qualquer outro idioma que eu uso (scala, kotlin, javascript, R, swift, ..) mas difícil / estranho em python
javadba
9
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]  
set(a) & set(xyz)  
set([0, 9, 4, 6, 7])
Kracekumar
fonte
Muito zen, @lazyr, mas não me ajudaria a melhorar um bloco de código complexo que depende da iteração em uma lista e da ignorância dos elementos correspondentes em outra lista. É mais rápido tratar a primeira lista como um conjunto e comparar união / diferença com uma segunda lista crescente de "ignorar"?
ChewyChunks
Tente istoimport time a = [2,3,4,5,6,7,8,9,0] xyz = [0,12,4,6,242,7,9] start = time.time() print (set(a) & set(xyz)) print time.time() - start
Kracekumar
@ChewyChunks se alguma das listas mudar durante a iteração, provavelmente será mais rápido comparar cada elemento com a lista de ignorados - exceto que você deve transformá-lo em um conjunto de ignorados. Verificação de adesão em conjuntos é muito rápido: if x in ignore: ....
Lauritz V. Thaulow 08/08/19
@ Lazyr Acabei de reescrever meu código usando um conjunto de ignorados em uma lista de ignorados. Parece processar o tempo muito mais devagar. (Para ser justo eu estava comparando usando if set(a) - set(ignore) == set([]):então talvez seja por isso que foi muito mais lento do que verificar a adesão Vou testar isso novamente no futuro em um exemplo muito mais simples do que o que eu estou escrevendo..
ChewyChunks
5

Você também pode usar geradores , se as expressões do gerador ficarem muito envolvidas ou complexas:

def gen():
    for x in xyz:
        if x in a:
            yield x

for x in gen():
    print x
Lauritz V. Thaulow
fonte
Isso é um pouco mais útil para mim. Eu nunca olhei para geradores. Eles parecem assustadores (porque eu os vi em módulos que geralmente eram difíceis de usar).
ChewyChunks
2

Use intersectionouintersection_update

  • interseção :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    ans = sorted(set(a).intersection(set(xyz)))
  • intersection_update :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    b = set(a)
    b.intersection_update(xyz)

    então bé sua resposta

Chung-Yen Hung
fonte
2

Gostei da resposta de Alex , porque um filtro é exatamente um se aplicado a uma lista. Portanto, se você deseja explorar um subconjunto de uma lista com uma condição, essa parece ser a maneira mais natural

mylist = [1,2,3,4,5]
another_list = [2,3,4]

wanted = lambda x:x in another_list

for x in filter(wanted, mylist):
    print(x)

esse método é útil para a separação de preocupações, se a função de condição for alterada, o único código a ser utilizado é a própria função

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

for x in filter(wanted, mylist):
    print(x)

O método gerador parece melhor quando você não deseja membros da lista, mas uma modificação desses membros, que parece mais adequada a um gerador

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.5 for x in mylist if wanted(x))

for x in generator:
    print(x)

Além disso, os filtros funcionam com geradores, embora neste caso não seja eficiente

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.9 for x in mylist)

for x in filter(wanted, generator):
    print(x)

Mas é claro, ainda seria bom escrever assim:

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

# for x in filter(wanted, mylist):
for x in mylist if wanted(x):
    print(x)
Khanis Rok
fonte
0

Uma maneira simples de encontrar elementos comuns exclusivos das listas aeb:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
Peawormsworth
fonte