Idioma Python para retornar o primeiro item ou Nenhum


274

Tenho certeza de que há uma maneira mais simples de fazer isso que simplesmente não está ocorrendo para mim.

Estou chamando um monte de métodos que retornam uma lista. A lista pode estar vazia. Se a lista não estiver vazia, desejo retornar o primeiro item; caso contrário, desejo retornar Nenhum. Este código funciona:

my_list = get_list()
if len(my_list) > 0: return my_list[0]
return None

Parece-me que deveria haver um idioma de uma linha simples para fazer isso, mas para a minha vida não consigo pensar nisso. Existe?

Editar:

A razão pela qual estou procurando uma expressão de uma linha aqui não é que eu goste de código incrivelmente conciso, mas porque estou tendo que escrever muito código assim:

x = get_first_list()
if x:
    # do something with x[0]
    # inevitably forget the [0] part, and have a bug to fix
y = get_second_list()
if y:
    # do something with y[0]
    # inevitably forget the [0] part AGAIN, and have another bug to fix

O que eu gostaria de fazer certamente pode ser realizado com uma função (e provavelmente será):

def first_item(list_or_none):
    if list_or_none: return list_or_none[0]

x = first_item(get_first_list())
if x:
    # do something with x
y = first_item(get_second_list())
if y:
    # do something with y

Eu postei a pergunta porque frequentemente fico surpreso com o que expressões simples em Python podem fazer, e achei que escrever uma função era algo tolo de fazer se houvesse uma expressão simples que pudesse resolver o problema. Mas, vendo essas respostas, parece que uma função é a solução simples.


28
btw, no Python 2.6+ você pode usar em next(iter(your_list), None)vez de first_item(your_list)assumir que your_listnão é None( get_first_list()e get_second_list()sempre deve retornar um iterável).
JFS

1
Acho que você quer dizer que next(iter(your_list)), se você fornecer um segundo argumento iter, estará dizendo que o primeiro argumento é passível de chamada.
Robert Rossney

7
Não, Noneaqui está o segundo parâmetro para next(), not iter(). next()retorna seu segundo parâmetro se dado em vez de aumentar StopIterationse your_listestiver vazio.
JFS

Solução sugerida por @JFSebastian existe com pergunta duplicado: stackoverflow.com/a/18533669/144408
shahjapan

Respostas:


205

Python 2.6 ou superior

next(iter(your_list), None)

Se your_listpode ser None:

next(iter(your_list or []), None)

Python 2.4

def get_first(iterable, default=None):
    if iterable:
        for item in iterable:
            return item
    return default

Exemplo:

x = get_first(get_first_list())
if x:
    ...
y = get_first(get_second_list())
if y:
    ...

Outra opção é alinhar a função acima:

for x in get_first_list() or []:
    # process x
    break # process at most one item
for y in get_second_list() or []:
    # process y
    break

Para evitar, breakvocê pode escrever:

for x in yield_first(get_first_list()):
    x # process x
for y in yield_first(get_second_list()):
    y # process y

Onde:

def yield_first(iterable):
    for item in iterable or []:
        yield item
        return

1
Essa última opção é quase exatamente o que estou procurando: é claro, funciona e não exige que eu defina uma nova função. Eu diria "exatamente" se a interrupção não fosse necessária, porque o risco de omitir não é insignificante. Mas essa abordagem tem o toque da verdade.
Robert Rossney

Oh, eu não gosto disso. Se algum item da lista avaliar Falso, esse valor será descartado e substituído. Se você tiver uma sequência vazia ""na lista, ela será descartada e substituída por uma lista vazia []. Se você tiver um 0, também será substituído por []. Se você tiver Falselá, também substituiu. Etc. Você pode se safar disso em um caso específico, mas esse é um mau hábito a ser desenvolvido.
Steveha

4
@ steveha: bool(lst) nos diz se len(lst) > 0não nos diz nada sobre o que os itens lstcontêm, bool([False]) == True;por exemplo, portanto , a expressão [False] or []retorna [False], o mesmo vale para [0] or [] ela [0].
JFS

@RobertRossney: eu adicionei yield_first()para evitar breakdeclarações.
JFS

1
@ Thomasmasle: ambos são verdadeiros. Atualizei a resposta para fornecer a solução para o caso não-Nenhum.
JFS

218

A melhor maneira é esta:

a = get_list()
return a[0] if a else None

Você também pode fazer isso em uma linha, mas é muito mais difícil para o programador ler:

return (get_list()[:1] or [None])[0]

Opa Deveria ter mencionado que este é um aplicativo Python 2.4.
Robert Rossney

15
@ Adam: o futuro pode usarnext(iter(your_list), None)
jfs

@JFSebastian realmente next(iter(your_list))é suficiente
Brad Johnson

13
@BradleagheJohnson: errado. Ele gera StopIteration em vez de retornar Nonecomo o OP pede. Tente: next(iter([])).
JFS

72
(get_list() or [None])[0]

Isso deve funcionar.

BTW eu não usei a variável list, porque isso substitui a list()função interna .

Edit: Eu tinha uma versão um pouco mais simples, mas errada aqui anteriormente.


4
Isso é inteligente e funciona, mas acho que "retorne foo [0] se foo else None", como sugerido pelo efotinis, é um pouco mais fácil de seguir para manutenção.
Jay

3
A pergunta estava pedindo uma solução de uma linha, então eu descartei isso.
recursiva

Nenhuma dessas expressões funciona se get_list () retornar None; você obtém "TypeError: o objeto 'NoneType' não pode ser assinado." ou "TypeError: tipo (s) de operando não suportado para +: 'NoneType' e 'list'."
Robert Rossney

4
Na pergunta, ele diz que os métodos retornam listas, das quais None não é uma. Portanto, considerando os requisitos, ainda acredito que ambos funcionam.
recursivo

Deve funcionar agora se get_list () retornar None, pois não será mais inscrito ou adicionado.
Robert

32

A maneira mais idiomática do python é usar o next () em um iterador, pois a lista é iterável . exatamente como o @JFSebastian colocou no comentário em 13 de dezembro de 2011.

next(iter(the_list), None)Isso retorna Nenhum se the_listestiver vazio. veja o próximo () Python 2.6+

ou se você tiver certeza de que the_listnão está vazio:

iter(the_list).next()veja iterator.next () Python 2.2 ou superior


1
Isso tem a vantagem de você não precisar atribuir sua lista a uma variável como return l[0] if l else Nonese a lista fosse um libcomp. Quando a lista é realmente um genexpr isso é ainda melhor desde que você não tem que construir toda a lista como emnext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna

Se você souber com certeza que the_listnão está vazio, você escreveria the_list[0].
kaya3

12

Se você tentar extrair a primeira coisa (ou Nenhuma) de uma compreensão de lista, poderá mudar para um gerador para fazer o seguinte:

next((x for x in blah if cond), None)

Pro: funciona se blá não é indexável. Con: é uma sintaxe desconhecida. No entanto, é útil enquanto hackers e filtrando coisas no ipython.


11

A solução do OP está quase lá, existem apenas algumas coisas para torná-lo mais Pythonic.

Por um lado, não há necessidade de obter o comprimento da lista. Listas vazias no Python são avaliadas como False em uma verificação se. Simplesmente diga

if list:

Além disso, é uma péssima idéia atribuir a variáveis ​​que se sobrepõem a palavras reservadas. "lista" é uma palavra reservada em Python.

Então, vamos mudar isso para

some_list = get_list()
if some_list:

Um ponto realmente importante que muitas soluções faltam aqui é que todas as funções / métodos Python retornam None por padrão . Tente o seguinte abaixo.

def does_nothing():
    pass

foo = does_nothing()
print foo

A menos que você precise retornar None para encerrar uma função mais cedo, é desnecessário retornar explicitamente None. Muito sucintamente, basta retornar a primeira entrada, se ela existir.

some_list = get_list()
if some_list:
    return list[0]

E, finalmente, talvez isso esteja implícito, mas apenas para ser explícito (porque explícito é melhor que implícito ), você não deve ter sua função obter a lista de outra função; apenas passe como parâmetro. Então, o resultado final seria

def get_first_item(some_list): 
    if some_list:
        return list[0]

my_list = get_list()
first_item = get_first_item(my_list)

Como eu disse, o OP estava quase lá, e apenas alguns toques proporcionam o sabor Python que você está procurando.


1
Por que 'list' é uma palavra reservada em python? Ou, mais precisamente, onde diz que 'lista' é uma palavra de reserver em python? Ou, mais precisamente, como você verificou se 'lista' é uma palavra reservada em python? Considerando que eu posso declarar uma variável chamada 'lista', claramente não é uma palavra reservada.
Lasse V. Karlsen

2
Ponto tomado. As palavras verdadeiramente reservadas no Python podem ser encontradas aqui tinyurl.com/424663 No entanto, é uma boa idéia considerar as funções e tipos internos como reservados. list () na verdade gera uma lista. O mesmo vale para dict, range etc.
gotgenes 12/12/08

Eu combinaria as duas últimas linhas, eliminando a variável temporária (a menos que você precise da minha lista adicional). Isso melhora a legibilidade.
Svante

Esta é praticamente a resposta que eu estava gravitando.
Robert Rossney

1
get_first_itemdeve aceitar um defaultparâmetro (como dict.get) que seja padronizado como Nenhum. Assim, a interface da função comunica explicitamente o que acontece se my_listestiver vazio.
JFS

3
for item in get_list():
    return item

Isso é sucinto e bonito.
gotgenes 12/12/08

Tragicamente, isso não funciona; ele lança uma exceção "TypeError: 'NoneType' não é iterável" se get_list () retornar None.
Robert Rossney

Para corrigir: "para o item em get_list () ou []:"
itsadok

5
A pergunta presume claramente que get_list retorna uma lista. len e getitem são chamados em seu resultado.
A. Coady

2

Francamente falando, não acho que exista uma linguagem melhor: a sua é clara e concisa - não há necessidade de nada "melhor". Talvez, mas isso é realmente uma questão de gosto, você pode mudar if len(list) > 0:com if list:- uma lista vazia será sempre avaliada como False.

Em uma nota relacionada, Python não é Perl (sem trocadilhos!), Você não precisa obter o código mais legal possível.
Na verdade, o pior código que eu já vi em Python, também foi muito legal :-) e completamente insustentável.

A propósito, a maior parte da solução que eu vi aqui não leva em consideração quando a lista [0] é avaliada como False (por exemplo, string vazia ou zero) - nesse caso, todas elas retornam None e não o elemento correto.


No Python, é considerado um bom estilo para escrever, em if lst:vez de if len(lst) > 0:. Além disso, não use palavras-chave Python como nomes de variáveis; termina em lágrimas. É comum usar Lou lstcomo o nome da variável para alguma lista. Tenho certeza de que, neste caso, você não quis sugerir o uso listcomo nome de variável, apenas quis dizer "alguma lista". E, +1 para "quando lst [0] for avaliado como Falso".
Steveha

Sim, eu estava apenas mantendo o mesmo tipo de nome de OP, para melhor clareza. Mas você está definitivamente certo: deve-se sempre tomar cuidado para não substituir as palavras-chave do Python.
Rob

2

Idioma Python para retornar o primeiro item ou Nenhum?

A abordagem mais pitônica é o que a resposta mais votada demonstrou e foi a primeira coisa que me veio à mente quando li a pergunta. Veja como usá-lo, primeiro se a lista possivelmente vazia for passada para uma função:

def get_first(l): 
    return l[0] if l else None

E se a lista for retornada de uma get_listfunção:

l = get_list()
return l[0] if l else None

Outras maneiras demonstradas para fazer isso aqui, com explicações

for

Quando comecei a tentar pensar em maneiras inteligentes de fazer isso, esta é a segunda coisa que pensei:

for item in get_list():
    return item

Isso pressupõe que a função termine aqui, retornando implicitamente Nonese get_listretornar uma lista vazia. O código explícito abaixo é exatamente equivalente:

for item in get_list():
    return item
return None

if some_list

Também foi proposto o seguinte (eu corrigi o nome incorreto da variável) que também usa o implícito None. Isso seria preferível ao acima, pois ele usa a verificação lógica em vez de uma iteração que pode não acontecer. Isso deve ser mais fácil de entender imediatamente o que está acontecendo. Mas se estamos escrevendo para facilitar a leitura e a manutenção, também devemos adicionar o explícito return Noneno final:

some_list = get_list()
if some_list:
    return some_list[0]

cortar or [None]e selecionar o índice zeroth

Este também está na resposta mais votada:

return (get_list()[:1] or [None])[0]

A fatia é desnecessária e cria uma lista de um item extra na memória. O seguinte deve ter mais desempenho. Para explicar, orretorna o segundo elemento se o primeiro estiver Falseem um contexto booleano; portanto, se get_listretornar uma lista vazia, a expressão contida entre parênteses retornará uma lista com 'None', que será acessada pelo 0índice:

return (get_list() or [None])[0]

O próximo usa o fato de que e retorna o segundo item se o primeiro estiver Trueem um contexto booleano, e como ele faz referência a my_list duas vezes, não é melhor que a expressão ternária (e tecnicamente não é uma linha):

my_list = get_list() 
return (my_list and my_list[0]) or None

next

Então, temos o seguinte uso inteligente do built-in nexteiter

return next(iter(get_list()), None)

Para explicar, iterretorna um iterador com um .nextmétodo ( .__next__no Python 3.) Em seguida, o builtin nextchama esse .nextmétodo e, se o iterador estiver esgotado, retorna o padrão que fornecemos None,.

expressão ternária redundante ( a if b else c) e retornando

O abaixo foi proposto, mas o inverso seria preferível, pois a lógica é geralmente melhor compreendida no positivo, em vez do negativo. Como get_listé chamado duas vezes, a menos que o resultado seja memorizado de alguma forma, isso teria um desempenho ruim:

return None if not get_list() else get_list()[0]

O melhor inverso:

return get_list()[0] if get_list() else None

Melhor ainda, use uma variável local para que isso get_listseja chamado apenas uma vez e você tenha a solução Pythonic recomendada discutida primeiro:

l = get_list()
return l[0] if l else None

2

Em relação aos idiomas, há uma receita de ferramentas chamada nth.

De receitas itertools:

def nth(iterable, n, default=None):
    "Returns the nth item or a default value"
    return next(islice(iterable, n, None), default)

Se você quiser one-liners, considere instalar uma biblioteca que implemente esta receita para você, por exemplo more_itertools:

import more_itertools as mit

mit.nth([3, 2, 1], 0)
# 3

mit.nth([], 0)                                             # default is `None`
# None

Está disponível outra ferramenta que retorna apenas o primeiro item, chamado more_itertools.first.

mit.first([3, 2, 1])
# 3

mit.first([], default=None)
# None

Esses instrumentos são dimensionados genericamente para qualquer iterável, não apenas para listas.



1

Por curiosidade, corri horários em duas das soluções. A solução que usa uma declaração de retorno para finalizar prematuramente um loop for é um pouco mais cara na minha máquina com o Python 2.5.1. Suspeito que isso tenha a ver com a configuração do iterável.

import random
import timeit

def index_first_item(some_list):
    if some_list:
        return some_list[0]


def return_first_item(some_list):
    for item in some_list:
        return item


empty_lists = []
for i in range(10000):
    empty_lists.append([])

assert empty_lists[0] is not empty_lists[1]

full_lists = []
for i in range(10000):
    full_lists.append(list([random.random() for i in range(10)]))

mixed_lists = empty_lists[:50000] + full_lists[:50000]
random.shuffle(mixed_lists)

if __name__ == '__main__':
    ENV = 'import firstitem'
    test_data = ('empty_lists', 'full_lists', 'mixed_lists')
    funcs = ('index_first_item', 'return_first_item')
    for data in test_data:
        print "%s:" % data
        for func in funcs:
            t = timeit.Timer('firstitem.%s(firstitem.%s)' % (
                func, data), ENV)
            times = t.repeat()
            avg_time = sum(times) / len(times)
            print "  %s:" % func
            for time in times:
                print "    %f seconds" % time
            print "    %f seconds avg." % avg_time

Estes são os horários que recebi:

empty_lists:
  index_first_item:
    0.748353 segundos
    0,741086 segundos
    0,741191 segundos
    0,743543 segundos méd.
  return_first_item:
    0.785511 segundos
    0.822178 segundos
    0,782846 segundos
    0,796845 segundos méd.
full_lists:
  index_first_item:
    0.762618 segundos
    0.788040 segundos
    0,786849 segundos
    0,779169 segundos méd.
  return_first_item:
    0.802735 segundos
    0.878706 segundos
    0.808781 segundos
    0,830074 segundos méd.
mixed_lists:
  index_first_item:
    0.791129 segundos
    0,743526 segundos
    0,744441 segundos
    0,759699 segundos méd.
  return_first_item:
    0,784801 segundos
    0.785146 segundos
    0.840193 segundos
    0,803380 segundos méd.

0
try:
    return a[0]
except IndexError:
    return None

1
Exceções geralmente não são usadas para controle de fluxo.
Matt Green

Eu não acho que prolongar esse código e adicionar manipulação de exceção a ele é exatamente o idioma que eu estava procurando.
Robert Rossney

3
Mas esse é um idioma comum do Python! Por razões de desempenho, você pode não usá-lo; você obterá melhor desempenho, if len(a) > 0:mas isso é considerado um bom estilo. Observe como isso expressa o problema: "tente extrair o cabeçalho de uma lista e, se isso não der certo, retorne None". Google procurar "EAFP" ou olhar aqui: python.net/~goodger/projects/pycon/2007/idiomatic/...
steveha

@steveha Depende dos seus dados. Se len (a) for geralmente> 0 e len (a) <1 for um estado raro, capturar a exceção será realmente mais eficiente.
limscoder

@limscoder, eu concordo com você. Apenas não entrei em detalhes de quando você não pode usá-lo por motivos de desempenho; Eu não disse que você nunca usaria isso.
Steveha

0
def head(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

print head(xrange(42, 1000)  # 42
print head([])               # None

BTW: Eu refiz o fluxo geral do seu programa para algo assim:

lists = [
    ["first", "list"],
    ["second", "list"],
    ["third", "list"]
]

def do_something(element):
    if not element:
        return
    else:
        # do something
        pass

for li in lists:
    do_something(head(li))

(Evitando repetições sempre que possível)


0

Que tal agora:

(my_list and my_list[0]) or None

Nota: Isso deve funcionar bem para listas de objetos, mas pode retornar resposta incorreta no caso de número ou lista de cadeias conforme os comentários abaixo.


Que tal ([0,1] and 0) or None!
Kenly 8/16

Esse é um bom ponto ... Não é seguro usar com coleções de números na forma atual :(
VitalyB 8/16

A mesma coisa com (['','A'] and '') or None.
Kenly 8/16

Se my_list[0]for avaliado como False, o resultado deve ser None.
Kenly

2
my_list[0] if len(my_list) else None
Nicholas Hamilton

0

Não tenho certeza de quão pythonic é isso, mas até que haja uma primeira função na biblioteca, eu incluo isso na fonte:

first = lambda l, default=None: next(iter(l or []), default)

É apenas uma linha (está de acordo com o preto) e evita dependências.



-1

Provavelmente não é a solução mais rápida, mas ninguém mencionou esta opção:

dict(enumerate(get_list())).get(0)

if get_list()pode retornar Nonevocê pode usar:

dict(enumerate(get_list() or [])).get(0)

Vantagens:

-uma linha

-você liga get_list()uma vez

-fácil de entender


-1

Meu caso de uso foi apenas para definir o valor de uma variável local.

Pessoalmente, achei a tentativa e, exceto o limpador de estilo, para ler

items = [10, 20]
try: first_item = items[0]
except IndexError: first_item = None
print first_item

do que fatiar uma lista.

items = [10, 20]
first_item = (items[:1] or [None, ])[0]
print first_item

-1

Várias pessoas sugeriram fazer algo assim:

list = get_list()
return list and list[0] or None

Isso funciona em muitos casos, mas só funcionará se a lista [0] não for igual a 0, False ou uma sequência vazia. Se a lista [0] for 0, False ou uma sequência vazia, o método retornará incorretamente Nenhum.

Eu criei esse bug no meu próprio código muitas vezes!


3
Este deve ser um comentário sob a (s) resposta (s) defeituosa (s) em vez de uma resposta independente.
IJ Kennedy

-2

Você pode usar o método Extrair . Em outras palavras, extraia esse código em um método que você chamaria.

Eu não tentaria compactá-lo muito mais, os forros parecem mais difíceis de ler do que a versão detalhada. E se você usar o Extract Method, é um liner;)



-3

não é o python idiomático equivalente aos operadores ternários do estilo C

cond and true_expr or false_expr

ie

list = get_list()
return list and list[0] or None

Se um [0] for o número 0 ou a sequência vazia (ou qualquer outra coisa que seja avaliada como falsa), isso retornará Nenhum em vez do que realmente é um [0].
Adam Rosenfield
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.