Python Language
Listförståelser
Sök…
Introduktion
Listförståelser i Python är kortfattade, syntaktiska konstruktioner. De kan användas för att generera listor från andra listor genom att tillämpa funktioner på varje element i listan. Följande avsnitt förklarar och demonstrerar användningen av dessa uttryck.
Syntax
- [x + 1 för x in (1, 2, 3)] # listaförståelse, ger [2, 3, 4]
- (x + 1 för x in (1, 2, 3)) # generatoruttryck, ger 2, sedan 3 och sedan 4
- [x för x in (1, 2, 3) om x% 2 == 0] # listaförståelse med filter, ger [2]
- [x + 1 om x% 2 == 0 annars x för x in (1, 2, 3)] # listaförståelse med ternär
- [x + 1 om x% 2 == 0 annars x för x inom intervallet (-3,4) om x> 0] # listaförståelse med ternär och filtrering
- {x för x in (1, 2, 2, 3)} # set förståelse, ger {1, 2, 3}
- {k: v för k, v i [('a', 1), ('b', 2)]} # dict-förståelse, ger {'a': 1, 'b': 2} (python 2.7+ och Endast 3.0+)
- [x + y för x i [1, 2] för y i [10, 20]] # Kapslade slingor, ger [11, 21, 12, 22]
- [x + y för x i [1, 2, 3] om x> 2 för y i [3, 4, 5]] # Tillstånd kontrollerat vid 1: a för slinga
- [x + y för x i [1, 2, 3] för y i [3, 4, 5] om x> 2] # Tillstånd kontrollerat vid 2: a för loop
- [x för x i xrange (10) om x% 2 == 0] # Villkor kontrolleras om slingade siffror är udda siffror
Anmärkningar
Förståelser är syntaktiska konstruktioner som definierar datastrukturer eller uttryck som är unika för ett visst språk. Korrekt användning av förståelser tolkar dessa om till lättförståelige uttryck. Som uttryck kan de användas:
- i högra sidan av uppdrag
- som argument för att fungera samtal
- i kroppen av en lambda-funktion
- som fristående uttalanden. (Till exempel:
[print(x) for x in range(10)]
)
Lista förståelser
En listaförståelse skapar en ny list
genom att tillämpa ett uttryck på varje element i en iterable . Den mest grundläggande formen är:
[ <expression> for <element> in <iterable> ]
Det finns också ett valfritt "if" -villkor:
[ <expression> for <element> in <iterable> if <condition> ]
Varje <element>
i <iterable>
är ansluten till <expression>
om (valfritt) <condition>
utvärderas till sant . Alla resultat returneras samtidigt i den nya listan. Generatoruttryck utvärderas lata, men listförståelser utvärderar hela iteratorn omedelbart - konsumerar minne proportionellt mot iteratorns längd.
Så här skapar du en list
med kvadratiska heltal:
squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]
for
uttrycket sätter x
till varje värde i tur och ordning från (1, 2, 3, 4)
. Resultatet av uttrycket x * x
läggs till i en intern list
. Den interna list
tilldelas variabla squares
när den är klar.
Förutom en hastighetsökning (som förklaras här ), är en listaförståelse ungefär motsvarande följande för-loop:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
Uttrycket som tillämpas på varje element kan vara så komplicerat som behövs:
# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
annan
else
kan användas i listförståelsekonstruktioner, men var försiktig när det gäller syntaxen. Klausulerna om / annars bör användas tidigare for
loop, inte efter:
# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax
# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']
Observera att detta använder en annan språkkonstruktion, ett villkorligt uttryck , som i sig inte är en del av förståelsyntaxen . Medan if
efter for…in
är en del av listförståelser och används för att filtrera element från källan som är iterable.
Dubbel Iteration
Ordningen med dubbel iteration [... for x in ... for y in ...]
är antingen naturlig eller motintuitiv. Tumregeln är att följa en motsvarighet for
loop:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
Detta blir:
[str(x)
for i in range(3)
for x in foo(i)
]
Detta kan komprimeras till en rad som [str(x) for i in range(3) for x in foo(i)]
Mutation på plats och andra biverkningar
Innan du använder listförståelse, förstå skillnaden mellan funktioner som kallas för deras biverkningar ( mutera eller på plats funktioner) som vanligtvis returnerar None
, och funktioner som ger ett intressant värde.
Många funktioner (särskilt rena funktioner) tar helt enkelt ett objekt och returnerar ett objekt. En in-plats funktion ändrar befintligt objekt, som kallas en bieffekt. Andra exempel inkluderar ingångs- och utmatningsoperationer, t.ex. utskrift.
list.sort()
sorterar en lista på plats (vilket innebär att den ändrar originallistan) och returnerar värdet None
. Därför fungerar det inte som förväntat i en listaförståelse:
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
I stället returnerar sorted()
en sorterad list
istället för att sortera på plats:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]
Att använda förståelser för biverkningar är möjligt, till exempel I / O eller platsfunktioner. Ändå är en för-loop vanligtvis mer läsbar. Medan detta fungerar i Python 3:
[print(x) for x in (1, 2, 3)]
Använd istället:
for x in (1, 2, 3):
print(x)
I vissa situationer, bieffekt funktioner lämpar sig för listan förståelse. random.randrange()
har en bieffekt av att ändra tillståndet för slumptalsgeneratorn, men det ger också ett intressant värde. Dessutom kan next()
kallas på en iterator.
Följande slumpmässiga värdegenerator är inte ren men är ändå meningsfull eftersom slumpmässig generator återställs varje gång uttrycket utvärderas:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
Whitepace i listförståelser
Mer komplicerade listförståelser kan nå en oönskad längd eller bli mindre läsbara. Även om det är mindre vanligt i exempel är det möjligt att dela upp en listaförståelse i flera rader på så sätt:
[
x for x
in 'foo'
if x not in 'bar'
]
Ordbok förståelser
En ordbokförståelse liknar en listaförståelse förutom att den producerar ett ordbokobjekt istället för en lista.
Ett grundläggande exempel:
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
vilket bara är ett annat sätt att skriva:
dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Liksom med en listaförståelse kan vi använda ett villkorligt uttalande inuti diktförståelsen för att producera endast de dikterade elementen som uppfyller något kriterium.
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
# Out: {'Exchange': 8, 'Overflow': 8}
Eller skrivs om med ett generatoruttryck.
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}
Börjar med en ordbok och använder ordbokförståelse som ett nyckelvärdesparfilter
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}
Växla nyckel och värde för ordlistan (invertera ordlistan)
Om du har en dikt som innehåller enkla hashable- värden (dubbla värden kan ha oväntade resultat):
my_dict = {1: 'a', 2: 'b', 3: 'c'}
och du ville byta nycklar och värden kan du ta flera metoder beroende på din kodningsstil:
-
swapped = {v: k for k, v in my_dict.items()}
-
swapped = dict((v, k) for k, v in my_dict.iteritems())
-
swapped = dict(zip(my_dict.values(), my_dict))
-
swapped = dict(zip(my_dict.values(), my_dict.keys()))
-
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Om din ordbok är stor, kan du överväga att importera itertools och använda izip
eller imap
.
Sammanfoga ordböcker
Kombinera ordböcker och åsidosätt eventuellt gamla värden med en kapslad ordbokförståelse.
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Emellertid kan uppackning av ordbok ( PEP 448 ) vara en föredragen.
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Obs : ordbokförståelser lades till i Python 3.0 och backportades till 2,7+, till skillnad från listförståelser, som lades till i 2.0. Versioner <2,7 kan använda generatoruttryck och dict()
inbyggd för att simulera beteendet hos ordbokförståelser.
Generatoruttryck
Generatoruttryck liknar listförståelser. Huvudskillnaden är att det inte skapar en fullständig uppsättning resultat på en gång; det skapar ett generatorobjekt som sedan kan itereras över.
Se till exempel skillnaden i följande kod:
# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>
Dessa är två mycket olika objekt:
listan förståelse returnerar en
list
objekt medan generatorn förståelse returnerar engenerator
.generator
kan inte indexeras och användernext
funktion för att få saker i ordning.
Obs : Vi använder xrange
eftersom det också skapar ett generatorobjekt. Om vi skulle använda intervall skulle en lista skapas. Även xrange
finns endast i senare version av Python 2. I Python 3, range
bara tillbaka en generator. Mer information finns i exempel på skillnader mellan intervall och xrange-funktioner .
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'
g.next() # 0
g.next() # 1
g.next() # 4
...
g.next() # 81
g.next() # Throws StopIteration Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
OBS: Funktionen
g.next()
bör ersättas mednext(g)
ochxrange
medrange
eftersomIterator.next()
ochxrange()
inte finns i Python 3.
Även om båda dessa kan upprepas på liknande sätt:
for i in [x**2 for x in range(10)]:
print(i)
"""
Out:
0
1
4
...
81
"""
for i in (x**2 for x in xrange(10)):
print(i)
"""
Out:
0
1
4
.
.
.
81
"""
Använd fall
Generatoruttryck utvärderas lata, vilket innebär att de genererar och returnerar varje värde endast när generatorn är itererad. Detta är ofta användbart när det går igenom stora datasätt och undviker behovet av att skapa ett duplikat av datasatsen i minnet:
for square in (x**2 for x in range(1000000)):
#do something
Ett annat fall med vanlig användning är att undvika att iterera över en hel iterable om detta inte är nödvändigt. I det här exemplet hämtas ett objekt från ett fjärr-API med varje iteration av get_objects()
. Tusentals objekt kan finnas, måste hämtas en och en, och vi behöver bara veta om ett objekt som matchar ett mönster finns. Genom att använda ett generatoruttryck när vi möter ett objekt som matchar mönstret.
def get_objects():
"""Gets objects from an API one by one"""
while True:
yield get_next_item()
def object_matches_pattern(obj):
# perform potentially complex calculation
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Ställ in förståelser
Setförståelse liknar lista och ordbokförståelse , men det producerar en uppsättning , som är en oordnad samling unika element.
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}
# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}
# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
Tänk på att uppsättningar är oordnade. Detta betyder att ordningen på resultaten i uppsättningen kan skilja sig från den som presenteras i exemplen ovan.
Obs : Set förståelse är tillgängligt sedan python 2.7+, till skillnad från listförståelser, som lades till i 2.0. I Python 2.2 till Python 2.6 kan funktionen set()
användas med ett generatoruttryck för att ge samma resultat:
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}
Undvik upprepade och dyra operationer med villkorad klausul
Tänk på listan förståelse nedan:
>>> def f(x):
... import time
... time.sleep(.1) # Simulate expensive function
... return x**2
>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]
Detta resulterar i två samtal till f(x)
för 1 000 värden på x
: ett samtal för att generera värdet och det andra för att kontrollera if
villkoret. Om f(x)
är en särskilt dyr operation kan detta få betydande prestandakonsekvenser. Värre är att om samtal f()
har biverkningar kan det ha överraskande resultat.
Istället bör du utvärdera den dyra operationen endast en gång för varje värde på x
genom att generera en mellanliggande iterbar ( generatoruttryck ) enligt följande:
>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]
Eller, med hjälp av inbyggda kart likvärdiga:
>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]
Ett annat sätt som kan resultera i en mer läsbar kod är att lägga delresultatet ( v
i föregående exempel) i en iterbar (som en lista eller en tupel) och sedan iterera över det. Eftersom v
kommer att vara det enda elementet i det iterable är resultatet att vi nu har en referens till utgången från vår långsamma funktion beräknad bara en gång:
>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]
Men i praktiken kan kodens logik vara mer komplicerad och det är viktigt att hålla den läsbar. I allmänhet rekommenderas en separat generatorfunktion över en komplex enfodring:
>>> def process_prime_numbers(iterable):
... for x in iterable:
... if is_prime(x):
... yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]
Ett annat sätt för att förhindra att beräkna f(x)
flera gånger är att använda @functools.lru_cache()
(Python 3.2 eller senare) dekoratör på f(x)
. På detta sätt, eftersom utgången från f
för ingången x
redan har beräknats en gång, kommer den andra funktionskallandet av den ursprungliga listförståelsen att vara lika snabb som en ordboksökning. Detta tillvägagångssätt använder memoisering för att förbättra effektiviteten, vilket är jämförbart med att använda generatoruttryck.
Säg att du måste platta en lista
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Några av metoderna kan vara:
reduce(lambda x, y: x+y, l)
sum(l, [])
list(itertools.chain(*l))
Men listförståelse skulle ge den bästa tidskomplexiteten.
[item for sublist in l for item in sublist]
Genvägarna baserade på + (inklusive den underförstådda användningen i summan) är nödvändigtvis O (L ^ 2) när det finns L-sublister - eftersom den mellanliggande resultatlistan blir längre, vid varje steg blir ett nytt mellanliggande resultatlistobjekt tilldelas, och alla objekt i föregående mellanresultat måste kopieras över (liksom några nya tillagda i slutet). Så (för enkelhet och utan faktisk förlust av allmänhet) säger att du har L-underlistor över I-artiklar varje: de första I-artiklarna kopieras fram och tillbaka L-1 gånger, den andra I-artiklarna L-2 gånger, och så vidare; det totala antalet kopior är I gånger summan av x för x från 1 till L utesluten, dvs I * (L ** 2) / 2.
Listförståelsen genererar bara en lista, en gång och kopierar varje objekt över (från dess ursprungliga bostad till resultatlistan) också exakt en gång.
Förståelser som involverar tuples
Den for
klausulen för en listaförståelse kan specificera mer än en variabel:
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]
Detta är precis som vanligt for
öglor:
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
# 3
# 7
# 11
Observera dock att om uttrycket som börjar förståelsen är en tupel måste det vara parenteserat:
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]
Räkna händelser med förståelse
När vi vill räkna antalet objekt i en iterable, som uppfyller vissa villkor, kan vi använda förståelse för att skapa en idiomatisk syntax:
# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
# Out: 95
Det grundläggande konceptet kan sammanfattas som:
- Iterera över elementen inom
range(1000)
. - Sammanfoga alla nödvändiga
if
villkor. - Använd 1 som uttryck för att returnera en 1 för varje objekt som uppfyller villkoren.
- Sammanfatta alla
1
er för att bestämma antalet objekt som uppfyller villkoren.
Obs : Här samlar vi inte 1
s i en lista (notera frånvaron av fyrkantiga parenteser), men vi överför dem direkt till sum
som summerar dem. Detta kallas ett generatoruttryck , vilket liknar en förståelse.
Ändra typer i en lista
Kvantitativa data läses ofta in som strängar som måste konverteras till numeriska typer före bearbetning. De typer av alla listobjekt kan konverteras med antingen en lista Förståelse eller map()
funktion .
# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]
# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0]