Pajton je jedan od najčešće upotrebljavanih programskih jezika. Danas ćete naučiti kako koristiti jednu od njegovih osnovnih – ali često zanemarenih – funkcija, a to je raspakivanje (unpacking) u Pajtonu.
Verovatno ste viđali simbole * i ** u tuđem kodu, ili ste ih čak i sami koristili, a da zapravo niste bili sigurni koja je njihova prava namena. Proći ćemo kroz koncept raspakivanja i kako ga primeniti za pisanje elegantnijeg i više „pajtonovskog“ koda.
Evo liste koncepata koji će vam biti od pomoći dok budete čitali ovaj vodič:
- Iterabilno (Iterable): Bilo koja sekvenca koja se može ponavljati pomoću for petlje, kao što su skupovi (sets), liste, torke (tuples) i rečnici.
- Poziv (Callable): Pajton objekat koji se može pozvati pomoću dvostrukih zagrada (), na primer, moja_funkcija().
- Školjka (Shell): Interaktivno okruženje za izvršavanje koje nam omogućava da pokrećemo Pajton kod. Možemo ga aktivirati pokretanjem komande „python“ u terminalu.
- Promenljiva (Variable): Simboličko ime koje čuva objekat i ima rezervisanu memorijsku lokaciju.
Počnimo od najčešće zabune: Asteriksi u Pajtonu su ujedno i aritmetički operatori. Jedna zvezdica
se koristi za množenje, dok dve (**) označavaju stepenovanje.
>>> 3*3 9 >>> 3**3 27
Ovo možemo proveriti otvaranjem Pajton školjke i kucanjem:
Napomena: Morate imati instaliran Pajton 3 da biste pratili ovaj vodič. Ako ga nemate instaliranog, pogledajte naš vodič za instalaciju Pajtona.
Kao što vidite, koristimo zvezdicu posle prvog broja i pre drugog. Kada vidite ovo, to znači da koristimo aritmetičke operatore.
>>> *range(1, 6), (1, 2, 3, 4, 5) >>> {**{'vanilla':3, 'chocolate':2}, 'strawberry':2} {'vanilla': 3, 'chocolate': 2, 'strawberry': 2}
S druge strane, koristimo zvezdice (*, **) pre iterabilnih objekata da bismo ih raspakovali – na primer:
Ne brinite ako vam sve nije potpuno jasno, ovo je samo uvod u raspakivanje u Pajtonu. Zato, samo napred i pročitajte ceo vodič!
Šta se zapravo raspakuje?
Raspakivanje je proces izdvajanja elemenata – iterabilnih objekata kao što su liste, torke i rečnici. Zamislite to kao otvaranje kutije i vađenje raznih predmeta kao što su kablovi, slušalice ili USB.
Raspakivanje u Pajtonu je slično raspakivanju kutije u stvarnom životu.
>>> mybox = ['cables', 'headphones', 'USB'] >>> item1, item2, item3 = mybox
Prevedimo ovaj primer u kod radi boljeg razumevanja:
Kao što možete videti, tri stavke unutar `mybox` liste dodeljujemo trima promenljivima `item1`, `item2`, i `item3`. Ova vrsta dodeljivanja promenljivih je osnovni koncept raspakivanja u Pajtonu.
>>> item1 'cables' >>> item2 'headphones' >>> item3 'USB'
Ako pokušate da dobijete vrednost svake stavke, primetićete da se `item1` odnosi na „cables“, `item2` se odnosi na „headphones“ i tako dalje.
>>> newbox = ['cables', 'headphones', 'USB', 'mouse'] >>> item1, item2, item3 = newbox Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: too many values to unpack (expected 3)
Do sada se činilo da je sve u redu sa ovim kodom, ali šta ako želimo da raspakujemo listu sa više elemenata u njoj – zadržavajući isti broj dodeljenih promenljivih?
Verovatno ste i očekivali ovakvu grešku. U suštini, mi dodeljujemo 4 stavke liste trima promenljivima, kako bi Pajton uspeo da dodeli ispravne vrednosti? Naravno da ne uspeva, i zato dobijamo `ValueError`
sa porukom „too many values to unpack“. Ovo se dešava zato što imamo tri promenljive sa leve strane i četiri vrednosti (koje odgovaraju listi `newbox`) sa desne strane.
>>> lastbox = ['cables', 'headphones'] >>> item1, item2, item3 = lastbox Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: not enough values to unpack (expected 3, got 2)
Ako pokušate da uradite sličan proces, ali sa više promenljivih nego što ima vrednosti za raspakivanje, dobićete još jednu grešku sa malo drugačijom porukom:
Napomena: Radili smo sa listama, ali ovaj oblik raspakivanja možete koristiti sa bilo kojim iterabilnim objektom (listama, skupovima, torkama, rečnicima)
Pa kako da prevaziđemo ovu situaciju? Da li postoji način da se raspakuju sve stavke iterabilnog objekta u nekoliko promenljivih bez grešaka?
Naravno da postoji, i zove se operator raspakivanja ili operator zvezdice (*, **). Hajde da vidimo kako ga možemo koristiti u Pajtonu.
Kako raspakovati liste pomoću * operatora
Operator zvezdica
>>> first, *unused, last = [1, 2, 3, 5, 7] >>> first 1 >>> last 7 >>> unused [2, 3, 5]
se koristi za raspakivanje svih vrednosti iterabilnog objekta koje još nisu dodeljene.
>>> first, *_, last = [1, 2, 3, 5, 7] >>> _ [2, 3, 5]
Pretpostavimo da želite da dobijete prvi i poslednji element liste bez korišćenja indeksa, mogli bismo to da uradimo sa operatorom zvezdica:
>>> first, *_, last = [1, 2] >>> first 1 >>> last 2 >>> _ []
Kao što možete primetiti, sve neiskorišćene vrednosti dobijamo pomoću operatora zvezdica. Preferirani način za odbacivanje vrednosti je korišćenje donje crte (_), koja se ponekad koristi kao „lažna promenljiva“.
I dalje možemo koristiti ovaj trik čak i ako lista ima samo dva elementa:
U ovom slučaju, promenljiva donje crte (lažna promenljiva) čuva praznu listu, tako da druge dve promenljive oko nje mogu da pristupe dostupnim vrednostima liste.
>>> *string = 'PythonIsTheBest'
Uobičajeno rešavanje problema
>>> *string = 'PythonIsTheBest' File "<stdin>", line 1 SyntaxError: starred assignment target must be in a list or tuple
Možemo raspakovati jedinstveni element iterabilnog objekta. Na primer, pomislili biste na nešto ovako: Međutim, gornji kod će vratiti `SyntaxError`: Ovo je zato što prema
PEP specifikaciji:
>>> *string, = 'PythonIsTheBest' >>> string ['P', 'y', 't', 'h', 'o', 'n', 'I', 's', 'T', 'h', 'e', 'B', 'e', 's', 't']
Torka (ili lista) na levoj strani jednostavnog zadatka
>>> *numbers, = range(5) >>> numbers [0, 1, 2, 3, 4]
Ako želimo da raspakujemo sve vrednosti iterabilnog objekta u jednu promenljivu, moramo postaviti torku, pa će dodavanje zareza biti dovoljno:
Drugi primer bi bio korišćenje funkcije `range`, koja vraća niz brojeva.
Sada kada znate kako da raspakujete liste i torke sa zvezdicom, vreme je da pređemo na raspakivanje rečnika.
Kako raspakovati rečnike pomoću ** operatora
>>> **greetings, = {'hello': 'HELLO', 'bye':'BYE'} ... SyntaxError: invalid syntax
Dok se jedna zvezdica koristi za raspakivanje lista i torki, dvostruka zvezdica (**) se koristi za raspakivanje rečnika.
>>> food = {'fish':3, 'meat':5, 'pasta':9} >>> colors = {'red': 'intensity', 'yellow':'happiness'} >>> merged_dict = {**food, **colors} >>> merged_dict {'fish': 3, 'meat': 5, 'pasta': 9, 'red': 'intensity', 'yellow': 'happiness'}
Nažalost, ne možemo raspakovati rečnik u jednu promenljivu kao što smo to radili sa torkama i listama. To znači da će sledeće izbaciti grešku:
Međutim, možemo koristiti ** operator unutar poziva funkcija i drugih rečnika. Na primer, ako želimo da napravimo spojeni rečnik, sastavljen od drugih rečnika, mogli bismo da koristimo kod ispod:
Ovo je prilično brz način da se kreiraju složeni rečnici, međutim, ovo nije primarna svrha raspakivanja u Pajtonu.
Hajde da vidimo kako možemo da koristimo raspakivanje sa pozivima funkcija.
Pakovanje u funkcije: `*args` i `**kwargs`
Verovatno ste i ranije viđali `*args` i `**kwargs` implementirane na klasama ili funkcijama. Hajde da vidimo zašto ih treba koristiti zajedno sa pozivima funkcija.
>>> def product(n1, n2): ... return n1 * n2 ... >>> numbers = [12, 1] >>> product(*numbers) 12
Pakovanje sa `*` operatorom (`args`)
>>> product(12, 1) 12
Pretpostavimo da imamo funkciju koja izračunava proizvod dva broja.
>>> numbers = [12, 1, 3, 4] >>> product(*numbers) ... TypeError: product() takes 2 positional arguments but 4 were given
Kao što vidite, mi raspakujemo listu `numbers` u funkciju, tako da zapravo pokrećemo sledeće:
>>> def product(*args): ... result = 1 ... for i in args: ... result *= i ... return result ... >>> product(*numbers) 144
Do sada je sve funkcionisalo dobro, ali šta ako želimo da prosledimo dužu listu? To će sigurno izazvati grešku, jer funkcija prihvata više argumenata nego što je sposobna da obradi.
Sve ovo možemo rešiti jednostavnim pakovanjem liste direktno u funkciju, što kreira iterabilan objekat unutar nje i omogućava nam da prosledimo bilo koji broj argumenata funkciji.
Ovde parametar `args` tretiramo kao iterabilan objekat, prolazimo kroz njegove elemente i vraćamo proizvod svih brojeva. Obratite pažnju da početna vrednost rezultata mora biti jedan, jer ako počnemo sa nulom, funkcija će uvek vraćati nulu. Napomena: `args` je samo konvencija, možete koristiti bilo koje drugo ime parametra. Takođe, mogli bismo da prosledimo proizvoljne brojeve funkciji bez korišćenja liste, baš kao i sa ugrađenom
>>> product(5, 5, 5) 125 >>> print(5, 5, 5) 5 5 5
funkcijom `print`.
>>> def test_type(*args): ... print(type(args)) ... print(args) ... >>> test_type(1, 2, 4, 'a string') <class 'tuple'> (1, 2, 4, 'a string')
.
Na kraju, hajde da dobijemo tip objekta `args` unutar funkcije.
Kao što je prikazano u gornjem kodu, tip `args` će uvek biti torka, a njen sadržaj će biti svi argumenti koji se prosleđuju funkciji, a nemaju ključ.
Pakovanje sa `**` operatorom (`kwargs`)
>>> def make_person(name, **kwargs): ... result = name + ': ' ... for key, value in kwargs.items(): ... result += f'{key} = {value}, ' ... return result ... >>> make_person('Melissa', id=12112, location='london', net_worth=12000) 'Melissa: id = 12112, location = london, net_worth = 12000, '
Kao što smo već videli, operator `**` se koristi isključivo za rečnike. To znači da sa ovim operatorom možemo da prosledimo parove ključ/vrednost funkciji kao parametar.
Hajde da napravimo funkciju `make_person`, koja prihvata pozicioni argument „name“ i neograničen broj argumenata sa ključnim rečima.
Kao što vidite, izjava `**kwargs` konvertuje sve argumente sa ključnim rečima u rečnik, koji možemo ponavljati unutar funkcije.
>>> def test_kwargs(**kwargs): ... print(type(kwargs)) ... print(kwargs) ... >>> test_kwargs(random=12, parameters=21) <class 'dict'> {'random': 12, 'parameters': 21}
Napomena: `kwargs` je samo konvencija, i možete imenovati ovaj parametar kako god želite.
Možemo da proverimo tip `kwargs` na isti način kao što smo to uradili sa `args`:
>>> def my_final_function(*args, **kwargs): ... print('Type args: ', type(args)) ... print('args: ', args) ... print('Type kwargs: ', type(kwargs)) ... print('kwargs: ', kwargs) ... >>> my_final_function('Python', 'The', 'Best', language="Python", users="A lot") Type args: <class 'tuple'> args: ('Python', 'The', 'Best') Type kwargs: <class 'dict'> kwargs: {'language': 'Python', 'users': 'A lot'}
Interna promenljiva `kwargs` se uvek pretvara u rečnik, koji čuva parove ključ-vrednost prosleđene funkciji.
Konačno, iskoristimo `*args` i `**kwargs` u istoj funkciji:
Zaključak
- Operatori za raspakivanje su zaista korisni u svakodnevnim zadacima, i sada znate kako da ih koristite u pojedinačnim izjavama i parametrima funkcija.
- U ovom vodiču ste naučili:
- Koristite `*` za torke i liste i `**` za rečnike.
- Možete koristiti operatore za raspakivanje u konstruktorima funkcija i klasa.
`*args` se koriste za prosleđivanje parametara bez ključa funkcijama, dok se `**kwargs` koriste za prosleđivanje parametara sa ključnim rečima funkcijama.