Python-idioom om het eerste item te retourneren of Geen

Ik weet zeker dat er een eenvoudigere manier is om dit te doen die me niet is opgevallen.

Ik roep een aantal methoden aan die een lijst retourneren. De lijst kan leeg zijn. Als de lijst niet leeg is, wil ik het eerste item retourneren; anders wil ik Geen retourneren. Deze code werkt:

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

Het lijkt mij dat er een eenvoudig eenregelig idioom zou moeten zijn om dit te doen, maar voor het leven van mij kan ik het niet bedenken. Is er?

Bewerken:

De reden dat ik hier een uitdrukking van één regel zoek, is niet dat ik van ongelooflijk beknopte code houd, maar omdat ik veel van dit soort code moet schrijven:

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

Wat ik zou willen doen, kan zeker worden bereikt met een functie (en zal dat waarschijnlijk ook zijn):

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

Ik heb de vraag gepost omdat ik vaak verbaasd ben over wat eenvoudige uitdrukkingen in Python kunnen doen, en ik dacht dat het schrijven van een functie een dwaas ding was om te doen als er een eenvoudige uitdrukking was die de truc zou kunnen doen. Maar als je deze antwoorden ziet, lijkt het alsof een functie isde eenvoudige oplossing.


Antwoord 1, autoriteit 100%

Python 2.6+

next(iter(your_list), None)

Als your_listNonekan zijn:

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

Voorbeeld:

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

Een andere optie is om de bovenstaande functie inline te plaatsen:

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

Om breakte voorkomen, kun je schrijven:

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

Waar:

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

Antwoord 2, autoriteit 87%

De beste manier is dit:

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

Je zou het ook in één regel kunnen doen, maar het is veel moeilijker voor de programmeur om te lezen:

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

Antwoord 3, autoriteit 31%

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

Dat zou moeten werken.

BTW ik heb de variabele listniet gebruikt, omdat die de ingebouwde functie list()overschrijft.

Bewerken: ik had hier eerder een iets eenvoudigere, maar verkeerde versie.


Antwoord 4, autoriteit 13%

De meest idiomatische python-manier is om de next() op een iterator te gebruiken, aangezien de lijst itereerbaaris. net zoals wat @J.F.Sebastian plaatste in de reactie op 13 december 2011.

next(iter(the_list), None)Dit levert None op als the_listleeg is. zie next() Python 2.6+

of als je zeker weet dat the_listniet leeg is:

iter(the_list).next()zie iterator.next () Python 2.2+


Antwoord 5, autoriteit 5%

Als je merkt dat je het eerste (of Geen) uit een lijstbegrip probeert te halen, kun je overschakelen naar een generator om het als volgt te doen:

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

Pro: werkt als blah niet indexeerbaar is. Con: het is een onbekende syntaxis. Het is wel handig tijdens het hacken en filteren van dingen in ipython.


Antwoord 6, autoriteit 4%

De OP-oplossing is er bijna, er zijn maar een paar dingen om het Pythonischer te maken.

Ten eerste is het niet nodig om de lengte van de lijst te bepalen. Lege lijsten in Python evalueren naar False in een if-controle. Zeg gewoon

if list:

Bovendien is het een heel slecht idee om variabelen toe te wijzen die overlappen met gereserveerde woorden. “lijst” is een gereserveerd woord in Python.

Dus laten we dat veranderen in

some_list = get_list()
if some_list:

Een heel belangrijk punt dat veel oplossingen hier missen, is dat alle Python-functies/-methoden standaard Geen retourneren. Probeer het volgende hieronder.

def does_nothing():
    pass
foo = does_nothing()
print foo

Tenzij u None moet retourneren om een ​​functie vroegtijdig te beëindigen, is het niet nodig om None expliciet te retourneren. Heel beknopt, stuur gewoon het eerste item terug, als het bestaat.

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

En tot slot, misschien was dit geïmpliceerd, maar om expliciet te zijn (omdat expliciet is beter dan impliciet), zou u niet moeten hebben dat uw functie de lijst van een andere functie krijgt; geef het gewoon door als een parameter. Het uiteindelijke resultaat zou dus zijn

def get_first_item(some_list): 
    if some_list:
        return list[0]
my_list = get_list()
first_item = get_first_item(my_list)

Zoals ik al zei, de OP was er bijna, en slechts een paar aanrakingen geven het de Python-smaak die je zoekt.


Antwoord 7, autoriteit 2%

Python-idioom om het eerste item te retourneren of Geen?

De meest Pythonische benadering is wat het meest positieve antwoord liet zien, en het was het eerste dat in me opkwam toen ik de vraag las. Hier is hoe het te gebruiken, eerst als de mogelijk lege lijst wordt doorgegeven aan een functie:

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

En als de lijst wordt geretourneerd door een functie get_list:

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

Nieuw in Python 3.8, Assignment Expressions

Toewijzingsexpressies gebruiken de in-place toewijzingsoperator (informeel de walrus-operator genoemd), :=, nieuw in Python 3.8, stelt ons in staat om de controle en toewijzing ter plaatse uit te voeren, waardoor de ene -voering:

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

Als een oude Python-gebruiker, voelt dit alsof we te veel op één regel proberen te doen – ik denk dat het een betere stijl zou zijn om de vermoedelijk even performante te doen:

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

Ter ondersteuning van deze formulering is Tim Peter’s essayin de PEP waarin deze wijziging van de taal wordt voorgesteld. Hij ging niet in op de eerste formulering, maar op basis van de andere formuleringen die hij leuk vond, denk ik niet dat hij het erg zou vinden.

Andere manieren worden hier gedemonstreerd om dit te doen, met uitleg

for

Toen ik slimme manieren begon te bedenken om dit te doen, was dit het tweede waar ik aan dacht:

for item in get_list():
    return item

Dit veronderstelt dat de functie hier eindigt, impliciet Noneteruggeeft als get_listeen lege lijst retourneert. De onderstaande expliciete code is exact gelijk aan:

for item in get_list():
    return item
return None

if some_list

Het volgende werd ook voorgesteld (ik heb de onjuiste variabelenaam gecorrigeerd) die ook de impliciete Nonegebruikt. Dit zou de voorkeur hebben boven het bovenstaande, omdat het de logische controle gebruikt in plaats van een iteratie die mogelijk niet gebeurt. Dit zou gemakkelijker moeten zijn om onmiddellijk te begrijpen wat er gebeurt. Maar als we schrijven voor leesbaarheid en onderhoudbaarheid, moeten we aan het einde ook de expliciete return Nonetoevoegen:

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

slice or [None]en selecteer nulde index

Deze staat ook in het antwoord met de meeste stemmen:

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

Het segment is niet nodig en creëert een extra lijst met één item in het geheugen. Het volgende zou beter moeten presteren. Ter verduidelijking, orretourneert het tweede element als het eerste Falseis in een booleaanse context, dus als get_listeen lege lijst retourneert, bevat de expressie tussen haakjes geeft een lijst terug met ‘Geen’, die dan toegankelijk is via de index 0:

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

De volgende gebruikt het feit dat en retourneert het tweede item als het eerste Trueis in een booleaanse context, en aangezien het tweemaal naar my_list verwijst, is het niet beter dan de ternaire expressie (en technisch gezien geen oneliner):

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

next

Dan hebben we het volgende slimme gebruik van de ingebouwde nexten iter

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

Om uit te leggen, iterretourneert een iterator met een .nextmethode. (.__next__in Python 3.) Dan roept de ingebouwde nextdie .nextmethode aan, en als de iterator is uitgeput, retourneert de standaard we geven, None.

redundante ternaire expressie (a if b else c) en terug cirkelen

Het onderstaande werd voorgesteld, maar het omgekeerde zou de voorkeur hebben, omdat logica meestal beter wordt begrepen in het positieve in plaats van in het negatieve. Aangezien get_listtwee keer wordt aangeroepen, zou dit slecht presteren, tenzij het resultaat op de een of andere manier in het geheugen wordt opgeslagen:

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

De betere inverse:

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

Nog beter, gebruik een lokale variabele zodat get_listmaar één keer wordt aangeroepen, en je hebt de aanbevolen Pythonic-oplossing eerst besproken:

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

Antwoord 8

my_list[0] if len(my_list) else None


Antwoord 9

for item in get_list():
    return item

Antwoord 10

Wat idioom betreft, er is een itertools-receptgenaamd nth.

Van itertools-recepten:

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

Als je oneliners wilt, overweeg dan om een ​​bibliotheek te installeren die dit recept voor je implementeert, b.v. more_itertools:

import more_itertools as mit
mit.nth([3, 2, 1], 0)
# 3
mit.nth([], 0)                                             # default is `None`
# None

Er is een andere tool beschikbaar die alleen het eerste item retourneert, genaamd more_itertools.first.

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

Deze itertools schalen generiek voor elke iterable, niet alleen voor lijsten.


Antwoord 11

Eerlijk gezegd denk ik niet dat er een beter idioom is: uw is duidelijk en beknopt – er is niets “beter” nodig. Misschien, maar dit is echt een kwestie van smaak, je zou if len(list) > 0:met if list:– een lege lijst zal altijd False opleveren.

Over een verwante opmerking, Python is nietPerl (geen woordspeling bedoeld!), je hoeft niet de coolste code te krijgen die mogelijk is.
Eigenlijk was de slechtste code die ik in Python heb gezien, ook erg cool 🙂 en volledig onhoudbaar.

Trouwens, de meeste oplossingen die ik hier heb gezien, houden geen rekening met wanneer list[0] evalueert naar False (bijv. lege string of nul) – in dit geval retourneren ze allemaal Geen en niet het juiste element .


Antwoord 12

Uit nieuwsgierigheid heb ik de timing op twee van de oplossingen gedraaid. De oplossing die een return-statement gebruikt om een ​​for-lus voortijdig te beëindigen, is iets duurder op mijn machine met Python 2.5.1, ik vermoed dat dit te maken heeft met het instellen van de iterable.

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

Dit zijn de tijden die ik heb gekregen:

empty_lists:
 index_first_item: 
0,748353 seconden
  0,741086 seconden
  0.741191 seconden
  0,743543 seconden gem.
 return_first_item:
  0.785511 seconden
  0,822178 seconden
  0,782846 seconden
  0,796845 seconden gem.
volledige_lijsten:
 index_first_item:
  0,762618 seconden
  0,788040 seconden
  0,786849 seconden
  0,779169 seconden gem.
 return_first_item:
  0,802735 seconden
  0,878706 seconden
  0,808781 seconden
  0,830074 seconden gem.
gemengde_lijsten:
 index_first_item:
  0,791129 seconden
  0,743526 seconden
  0,744441 seconden
  0,759699 seconden gem.
 return_first_item:
  0,784801 seconden
  0,785146 seconden
  0,840193 seconden
  0,803380 seconden gem.

Antwoord 13

def head(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None
print head(xrange(42, 1000)  # 42
print head([])               # None

BTW: ik zou je algemene programmastroom herwerken in zoiets als dit:

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

(Vermijd herhaling waar mogelijk)


Antwoord 14

Wat dacht je hiervan:

(my_list and my_list[0]) or None

Opmerking:dit zou prima moeten werken voor lijsten met objecten, maar het kan een onjuist antwoord opleveren in het geval van een nummer of een tekenreekslijst volgens de onderstaande opmerkingen.


Antwoord 15

Ik weet niet zeker hoe pythonisch dit is, maar totdat er een eerste functie in de bibliotheek is, neem ik dit op in de bron:

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

Het is slechts één regel (conform zwart) en vermijdt afhankelijkheden.


Antwoord 16

more_itertools.first_true-code lenen levert iets goed leesbaars op:

def first_true(iterable, default=None, pred=None):
    return next(filter(pred, iterable), default)
def get_first_non_default(items_list, default=None):
    return first_true(items_list, default, pred=lambda x: x!=default)

Antwoord 17

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

Antwoord 18

De en-of-truc gebruiken:

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

Antwoord 19

Waarschijnlijk niet de snelste oplossing, maar niemand noemde deze optie:

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

als get_list()Nonekan retourneren, kun je het volgende gebruiken:

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

Voordelen:

-één regel

-je belt get_list()een keer

-gemakkelijk te begrijpen


Antwoord 20

Mijn gebruik was alleen om de waarde van een lokale variabele in te stellen.

Persoonlijk vond ik de probeer-en-behalve-stijlreiniger om te lezen

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

dan het opdelen van een lijst.

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

Antwoord 21

Je zou de Extract-methodekunnen gebruiken. Met andere woorden, extraheer die code in een methode die je dan zou aanroepen.

Ik zou niet proberen het veel meer te comprimeren, de oneliners lijken moeilijker te lezen dan de uitgebreide versie. En als je de Extract-methode gebruikt, is het een one-liner 😉


Antwoord 22

Verschillende mensen hebben voorgesteld om zoiets als dit te doen:

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

Dat werkt in veel gevallen, maar het werkt alleen als list[0] niet gelijk is aan 0, False of een lege string. Als list[0] 0, False of een lege string is, zal de methode ten onrechte Geen retourneren.

Ik heb deze bug een keer te vaak in mijn eigen code gemaakt!


Antwoord 23

is niet de idiomatische python equivalent aan ternaire operatoren in C-stijl

cond and true_expr or false_expr

dwz.

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

Antwoord 24

if mylist != []:
       print(mylist[0])
   else:
       print(None)

Other episodes