selecione o item único de uma coleção: Python
Pergunta
Eu criei uma função de utilidade para retornar o item único esperado de um gerador de expressão
print one(name for name in ('bob','fred') if name=='bob')
Esta é uma boa maneira de ir sobre ele?
def one(g):
try:
val = g.next()
try:
g.next()
except StopIteration:
return val
else:
raise Exception('Too many values')
except StopIteration:
raise Exception('No values')
Solução
A solução mais simples é a descompactação uso tupla. Isso já vai fazer tudo o que quiser, incluindo a verificação de que ele contém exatamente um item.
único item:
>>> name, = (name for name in ('bob','fred') if name=='bob')
>>> name
'bob'
Muitos itens:
>>> name, = (name for name in ('bob','bob') if name=='bob')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack
Não há itens:
>>> name, = (name for name in ('fred','joe') if name=='bob')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 0 values to unpack
Outras dicas
abordagem simples:
print (name for name in ('bob', 'fred') if name == 'bob').next()
Se você realmente quer um erro quando há mais de um valor, então você precisa de uma função. O mais simples que posso pensar é ( EDITADO para trabalhar com listas também):
def one(iterable):
it = iter(iterable)
val = it.next()
try:
it.next()
except StopIteration:
return val
else:
raise Exception('More than one value')
Para aqueles que usam ou interessados ??em uma biblioteca de terceiros, more_itertools
implementos como uma ferramenta com a manipulação de erro nativo:
> pip install more_itertools
Código
import more_itertools as mit
mit.one(name for name in ("bob", "fred") if name == "bob")
# 'bob'
mit.one(name for name in ("bob", "fred", "bob") if name == "bob")
# ValueError: ...
mit.one(name for name in () if name == "bob")
# ValueError: ...
more_itertools
docs para mais detalhes. O código-fonte subjacente é semelhante ao resposta aceita.
Tenha um olhar para o href="http://docs.python.org/library/itertools.html#itertools.islice" rel="nofollow noreferrer"> itertools.islice () método
Este módulo implementa uma série de iterador blocos de construção inspirado por construções de linguagens de programação Haskell e SML. Cada um tem sido reformulada em uma forma adequada para Python. O módulo padroniza um conjunto de rápido, ferramentas de memória eficientes que são úteis por si só ou em combinação. Padronização ajuda a evitar os problemas de legibilidade e de fiabilidade que surgem quando muitos indivíduos diferentes criar suas próprias implementações ligeiramente diferentes, cada um com suas próprias peculiaridades e convenções de nomenclatura. As ferramentas são projetadas para combinar facilmente uns com os outros. Isto torna mais fácil para construir ferramentas mais especializadas de forma sucinta e eficiente em puro Python. >>> i2=itertools.islice((name for name in ('bob','fred') if name=='bob'),0,1,1)
>>> i2.next()
'bob'
>>> i2.next()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
StopIteration
>>>
Você quer dizer?
def one( someGenerator ):
if len(list(someGenerator)) != 1: raise Exception( "Not a Singleton" )
O que você está tentando realizar com todo o código extra?
Aqui está a minha tentativa em função one()
. Gostaria de evitar a chamada .next()
explícita e usar um loop for em vez disso.
def one(seq):
counter = 0
for elem in seq:
result = elem
counter += 1
if counter > 1:
break
if counter == 0:
raise Exception('No values')
elif counter > 1:
raise Exception('Too many values')
return result
Em primeiro lugar, (para responder à pergunta real!) Sua solução irá funcionar bem como vão as outras variantes proposto.
Gostaria de acrescentar que, neste caso, IMO, geradores são excessivamente complicado. Se você espera ter um valor, você provavelmente nunca tem o suficiente para o uso da memória a ser uma preocupação, então eu teria utilizado apenas o óbvio e muito mais clara:
children = [name for name in ('bob','fred') if name=='bob']
if len(children) == 0:
raise Exception('No values')
elif len(children) > 1:
raise Exception('Too many values')
else:
child = children[0]
Que tal usar Python para .. na sintaxe com um contador? Semelhante a resposta de unbeknown.
def one(items):
count = 0
value = None
for item in items:
if count:
raise Exception('Too many values')
count += 1
value = item
if not count:
raise Exception('No values')
return value