Sök…


Introduktion

En listaförståelse är ett syntaktiskt verktyg för att skapa listor på ett naturligt och kortfattat sätt, som illustreras i följande kod för att skapa en lista med rutor med siffrorna 1 till 10: [i ** 2 for i in range(1,11)] Dockan i från en befintlig lista range används för att göra ett nytt element mönster. Det används där en for loop skulle vara nödvändig på mindre uttrycksfulla språk.

Syntax

  • [i för i inom intervallet (10)] # grundläggande listaförståelse
  • [i för i i xrange (10)] # grundläggande listaförståelse med generatorobjekt i python 2.x
  • [i för i inom intervallet (20) om jag% 2 == 0] # med filter
  • [x + y för x i [1, 2, 3] för y i [3, 4, 5]] # kapslade öglor
  • [i om i> 6 annars 0 för i inom intervallet (10)] # ternärt uttryck
  • [i om i> 4 annars 0 för i inom intervallet (20) om i% 2 == 0] # med filter och ternärt uttryck
  • [[x + y för x i [1, 2, 3]] för y i [3, 4, 5]] # kapslad listförståelse

Anmärkningar

Listförståelser beskrivs i PEP 202 och introducerades i Python 2.0.

Villkorliga lista Förståelser

Med en listaförståelse kan du lägga till en eller flera if villkor för att filtrera värden.

[<expression> for <element> in <iterable> if <condition>]

För varje <element> i <iterable> ; om <condition> utvärderas till True , lägg till <expression> (vanligtvis en funktion av <element> ) till den returnerade listan.


Till exempel kan detta användas för att extrahera endast jämna nummer från en helhetssekvens:

[x for x in range(10) if x % 2 == 0]
# Out: [0, 2, 4, 6, 8]

Live-demo

Ovanstående kod motsvarar:

even_numbers = [] 
for x in range(10):
    if x % 2 == 0:
        even_numbers.append(x)

print(even_numbers)
# Out: [0, 2, 4, 6, 8]

Dessutom är en villkorad listförståelse av formen [e for x in y if c] (där e och c är uttryck i termer av x ) ekvivalent med list(filter(lambda x: c, map(lambda x: e, y))) .

Trots att du ger samma resultat, var uppmärksam på att det förra exemplet är nästan 2x snabbare än det senare. För dig som är nyfiken är detta en trevlig förklaring av orsaken till det.


Observera att detta skiljer sig helt från ... if ... else ... villkorat uttryck (ibland känt som ett ternärt uttryck ) som du kan använda för <expression> -delen i listförståelsen. Tänk på följande exempel:

[x if x % 2 == 0 else None for x in range(10)]
# Out: [0, None, 2, None, 4, None, 6, None, 8, None]

Live-demo

Här är det villkorliga uttrycket inte ett filter, utan en operatör som bestämmer värdet som ska användas för listobjekten:

<value-if-condition-is-true> if <condition> else <value-if-condition-is-false>

Detta blir mer uppenbart om du kombinerar det med andra operatörer:

[2 * (x if x % 2 == 0 else -1) + 1 for x in range(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]

Live-demo

Om du använder Python 2.7, kan xrange vara bättre än range av flera skäl som beskrivs i dokumentationen för xrange .

[2 * (x if x % 2 == 0 else -1) + 1 for x in xrange(10)]
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]

Ovanstående kod motsvarar:

numbers = []
for x in range(10):
    if x % 2 == 0:
        temp = x
    else:
        temp = -1
    numbers.append(2 * temp + 1)
print(numbers)
# Out: [1, -1, 5, -1, 9, -1, 13, -1, 17, -1]

Man kan kombinera ternära uttryck och if villkor. Den ternära operatören fungerar på det filtrerade resultatet:

[x if x > 2 else '*' for x in range(10) if x % 2 == 0]
# Out: ['*', '*', 4, 6, 8]

Detsamma kunde inte ha uppnåtts bara av en ternär operatör:

[x if (x > 2 and x % 2 == 0) else '*' for x in range(10)]
# Out:['*', '*', '*', '*', 4, '*', 6, '*', 8, '*']

Se även: Filter , som ofta ger ett tillräckligt alternativ till villkorade listförståelser.

Lista förståelser med kapslade slingor

Listförståelser kan använda kapslade for slingor. Du kan koda valfritt antal kapslade för slingor inom en listaförståelse, och varje for loop kan ha ett valfritt tillhörande if test. När du gör det är ordningen på for konstruktioner samma ordning som när du skriver en serie kapslade for uttalanden. Den allmänna strukturen för listförståelser ser så här ut:

[ expression for target1 in iterable1 [if condition1]
             for target2 in iterable2 [if condition2]...
             for targetN in iterableN [if conditionN] ]

Till exempel, följande kod plattar ut en lista med listor med flera for uttalanden:

data = [[1, 2], [3, 4], [5, 6]]
output = []
for each_list in data:
    for element in each_list:
        output.append(element)
print(output)
# Out: [1, 2, 3, 4, 5, 6]

kan skrivas på motsvarande sätt som en listaförståelse med multipel for konstruktioner:

data = [[1, 2], [3, 4], [5, 6]]
output = [element for each_list in data for element in each_list]
print(output)
# Out: [1, 2, 3, 4, 5, 6]

Live-demo

I både den utvidgade formen och listförståelsen kommer den yttre slingan (först för uttalande) först.


Förutom att vara mer kompakt är den kapslade förståelsen också betydligt snabbare.

In [1]: data = [[1,2],[3,4],[5,6]]
In [2]: def f():
   ...:     output=[]
   ...:     for each_list in data:
   ...:         for element in each_list:
   ...:             output.append(element)
   ...:     return output
In [3]: timeit f()
1000000 loops, best of 3: 1.37 µs per loop
In [4]: timeit [inner for outer in data for inner in outer]
1000000 loops, best of 3: 632 ns per loop

Kostnaden för funktionssamtalet ovan är cirka 140n .


Inline if s är kapslade på liknande sätt och kan förekomma i valfri position efter den första for :

data = [[1], [2, 3], [4, 5]]
output = [element for each_list in data
                if len(each_list) == 2
                for element in each_list
                if element != 5]
print(output)
# Out: [2, 3, 4]

Live-demo

För läsbarhetens skull bör du dock överväga att använda traditionella för-slingor . Detta är särskilt sant när häckningen är mer än 2 nivåer djup och / eller logiken för förståelsen är för komplex. flera kapslade slingor i listan kan vara felaktiga eller det ger oväntat resultat.

Refactoring filter och karta för att lista förståelser

De filter eller map bör ofta bytas ut mot list uppfattningar . Guido Van Rossum beskriver detta väl i ett öppet brev 2005 :

filter(P, S) skrivs nästan alltid tydligare som [x for x in S if P(x)] , och detta har den enorma fördelen att de vanligaste användningarna involverar predikat som är jämförelser, t.ex. x==42 , och definiera en lambda för det kräver bara mycket mer ansträngning för läsaren (plus att lambda är långsammare än listförståelsen). Ännu mer för map(F, S) som blir [F(x) for x in S] . Naturligtvis skulle du i många fall kunna använda generatoruttryck istället.

Följande kodrader betraktas som " inte pythoniska " och kommer att ge fel i många pythonlinters.

filter(lambda x: x % 2 == 0, range(10)) # even numbers < 10
map(lambda x: 2*x, range(10)) # multiply each number by two
reduce(lambda x,y: x+y, range(10)) # sum of all elements in list

Med det vi har lärt oss från föregående citat kan vi dela upp dessa filter och map uttryck i deras motsvarande listförståelser ; ta också bort lambda- funktionerna från var och en - vilket gör koden mer läsbar i processen.

# Filter:
# P(x) = x % 2 == 0
# S = range(10)
[x for x in range(10) if x % 2 == 0]

# Map
# F(x) = 2*x
# S = range(10)
[2*x for x in range(10)]

Läsbarheten blir ännu tydligare när man hanterar kedjefunktioner. Där resultaten av en karta eller filterfunktion på grund av läsbarheten ska skickas som ett resultat till nästa; med enkla fall kan dessa ersättas med en enda listförståelse. Vidare kan vi från listförståelsen enkelt berätta vad resultatet av vår process är, där det finns mer kognitiv belastning när vi resonerar om den kedjade Map & Filter-processen.

# Map & Filter
filtered = filter(lambda x: x % 2 == 0, range(10))
results = map(lambda x: 2*x, filtered)

# List comprehension
results = [2*x for x in range(10) if x % 2 == 0]

Refactoring - snabbreferens

  • Karta

    map(F, S) == [F(x) for x in S]
    
  • Filtrera

    filter(P, S) == [x for x in S if P(x)]
    

där F och P är funktioner som transformerar respektive ingångsvärden och returnerar en bool

Kapslade listförståelser

Kapslade listförståelser, till skillnad från listförståelser med kapslade slingor, är listförståelser inom en listförståelse. Det första uttrycket kan vara vilket godtyckligt uttryck som helst, inklusive en annan listaförståelse.

#List Comprehension with nested loop
[x + y for x in [1, 2, 3] for y in [3, 4, 5]]
#Out: [4, 5, 6, 5, 6, 7, 6, 7, 8]

#Nested List Comprehension
[[x + y for x in [1, 2, 3]] for y in [3, 4, 5]]
#Out: [[4, 5, 6], [5, 6, 7], [6, 7, 8]]

Exempel-exemplet motsvarar

l = []
for y in [3, 4, 5]:
    temp = []
    for x in [1, 2, 3]:
        temp.append(x + y)
    l.append(temp)

Ett exempel där en kapslad förståelse kan användas för att transponera en matris.

matrix = [[1,2,3],
          [4,5,6],
          [7,8,9]] 

[[row[i] for row in matrix] for i in range(len(matrix))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Precis som kapslade for slingor finns det ingen gräns för hur djupa förståelser kan häckas.

[[[i + j + k for k in 'cd'] for j in 'ab'] for i in '12']
# Out: [[['1ac', '1ad'], ['1bc', '1bd']], [['2ac', '2ad'], ['2bc', '2bd']]]

Iterera två eller flera listor samtidigt inom listförståelse

För att upprepa mer än två listor samtidigt inom listförståelse kan man använda zip() som:

>>> list_1 = [1, 2, 3 , 4]
>>> list_2 = ['a', 'b', 'c', 'd']
>>> list_3 = ['6', '7', '8', '9']

# Two lists
>>> [(i, j) for i, j in zip(list_1, list_2)]
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

# Three lists
>>> [(i, j, k) for i, j, k in zip(list_1, list_2, list_3)]
[(1, 'a', '6'), (2, 'b', '7'), (3, 'c', '8'), (4, 'd', '9')]

# so on ...


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow