como escolher apenas um item de um gerador (em python)?

213

Eu tenho uma função de gerador como a seguinte:

def myfunct():
  ...
  yield result

A maneira usual de chamar essa função seria:

for r in myfunct():
  dostuff(r)

Minha pergunta, existe uma maneira de obter apenas um elemento do gerador sempre que eu quiser? Por exemplo, eu gostaria de fazer algo como:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...
Alexandros
fonte

Respostas:

304

Crie um gerador usando

g = myfunct()

Sempre que quiser um item, use

next(g)

(ou g.next()no Python 2.5 ou abaixo).

Se o gerador sair, ele aumentará StopIteration. Você pode capturar essa exceção, se necessário, ou usar o defaultargumento para next():

next(g, default_value)
Sven Marnach
fonte
4
Observe que isso só aumentará StopIteration quando você tentar usar g.next () depois que o último item em g for fornecido.
Wilduck
26
next(gen, default)também pode ser usado para evitar a StopIterationexceção. Por exemplo, next(g, None)para um gerador de strings, produzirá uma string ou None após a conclusão da iteração.
Átila
8
em Python 3000, próxima () é __next __ ()
Jonathan Baldwin
27
@ JonathanBaldwin: Seu comentário é um pouco enganador. No Python 3, você usaria a segunda sintaxe dada na minha resposta next(g),. Isso chamará internamente g.__next__(), mas você realmente não precisa se preocupar com isso, assim como geralmente não se importa com o que len(a)chama internamente a.__len__().
precisa saber é o seguinte
14
Eu deveria ter sido mais claro. g.next()está g.__next__()em py3k. O built next(iterator)-in existe desde o Python 2.6, e é o que deve ser usado em todos os novos códigos do Python, e é trivial implementar de novo se você precisar dar suporte ao py <= 2.5.
11137 Jonathan Baldwin
29

Para escolher apenas um elemento de um gerador, use breakem uma fordeclaração oulist(itertools.islice(gen, 1))

De acordo com o seu exemplo (literalmente), você pode fazer algo como:

while True:
  ...
  if something:
      for my_element in myfunct():
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Se você deseja " obter apenas um elemento do gerador [uma vez gerado] sempre que eu quiser " (suponho que 50% seja a intenção original e a intenção mais comum), então:

gen = myfunct()
while True:
  ...
  if something:
      for my_element in gen:
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Dessa maneira, o uso explícito de generator.next()pode ser evitado e o tratamento de final de entrada não requer StopIterationtratamento de exceção (enigmático) ou comparações extras de valores padrão.

A seção else:de fordeclaração é necessária apenas se você quiser fazer algo especial no caso de fim de gerador.

Nota sobre next()/ .next():

No Python3, o .next()método foi renomeado .__next__()por um bom motivo: é considerado de baixo nível (PEP 3114). Antes do Python 2.6, a função interna next()não existia. E foi discutido até a mudança next()para o operatormódulo (o que seria sensato), devido à sua rara necessidade e inflação questionável de nomes internos.

Usar next()sem padrão ainda é uma prática de nível muito baixo - lançar o enigmático StopIterationcomo um raio do nada no código normal do aplicativo abertamente. E o uso next()com o sentinel padrão - que melhor deve ser a única opção para uma entrada next()direta builtins- é limitado e geralmente dá motivo a lógicas / legibilidade não-pitonais estranhas.

Conclusão: Usar next () deve ser muito raro - como usar funções do operatormódulo. Usando for x in iterator, islice, list(iterator)e outras funções aceitar um iterador perfeitamente é a maneira natural de usar iteradores no nível de aplicação - e bastante sempre possível. next()é de baixo nível, um conceito extra, não óbvio - como mostra a pergunta deste tópico. Enquanto, por exemplo, o uso breakem foré convencional.

kxr
fonte
8
Isso é trabalho demais para obter o primeiro elemento de um resultado da lista. Frequentemente, não preciso que seja preguiçoso, mas não tenho escolha no py3. Não há algo parecido mySeq.head?
Javadba 3/03
2

Não acredito que exista uma maneira conveniente de recuperar um valor arbitrário de um gerador. O gerador fornecerá um método next () para se movimentar, mas a sequência completa não é produzida imediatamente para economizar memória. Essa é a diferença funcional entre um gerador e uma lista.

gddc
fonte
1

Para aqueles que estão pesquisando essas respostas para obter um exemplo completo de trabalho para Python3 ... bem, aqui está:

def numgen():
    x = 1000
    while True:
        x += 1
        yield x

nums = numgen() # because it must be the _same_ generator

for n in range(3):
    numnext = next(nums)
    print(numnext)

Isso gera:

1001
1002
1003
Dave Rove
fonte
1

Gerador é uma função que produz um iterador. Portanto, depois de ter a instância do iterador, use next () para buscar o próximo item do iterador. Como exemplo, use a função next () para buscar o primeiro item e, posteriormente, use for inpara processar os itens restantes:

# create new instance of iterator by calling a generator function
items = generator_function()

# fetch and print first item
first = next(items)
print('first item:', first)

# process remaining items:
for item in items:
    print('next item:', item)
andrii
fonte
0
generator = myfunct()
while True:
   my_element = generator.next()

certifique-se de capturar a exceção lançada após a captura do último elemento

John
fonte
Não é válido para o Python 3, veja a excelente resposta do kxr .
Clacke
2
Apenas substitua "generator.next ()" por "next (generator)" para o Python 3.
iyop45 4/19/19
-3

Acredito que a única maneira é obter uma lista do iterador e obter o elemento que você deseja dessa lista.

l = list(myfunct())
l[4]
keegan3d
fonte
A resposta de Sven provavelmente é melhor, mas vou deixar isso aqui, caso esteja mais alinhado com suas necessidades.
precisa saber é o seguinte
26
Certifique-se de ter um gerador finito antes de fazer isso.
Seth
6
Desculpe, isso tem complexidade no tamanho do iterador, enquanto o problema é obviamente O (1).
yo '
1
Desperdiçando muita memória e processo para sugar do gerador! Além disso, como o @Seth mencionado anteriormente, os geradores não têm garantia para quando parar de gerar.
pylover 16/09/16
Obviamente, essa não é a única maneira (ou a melhor maneira, se myfunct()gera um grande número de valores), pois você pode usar a função interna nextpara obter o próximo valor gerado.
precisa saber é o seguinte