Python, jeden z najpopularniejszych języków programowania, oferuje wiele potężnych narzędzi. Dziś skupimy się na jednej z jego fundamentalnych, choć często pomijanych, właściwości – rozpakowywaniu.
Prawdopodobnie natknąłeś się już w kodzie na symbole *
i **
, być może nawet ich użyłeś, nie do końca zdając sobie sprawę z ich przeznaczenia. W tym artykule wyjaśnimy, czym jest rozpakowywanie i jak możesz je wykorzystać, by pisać bardziej efektywny kod w Pythonie.
Przed lekturą tego przewodnika, warto zaznajomić się z kilkoma pojęciami:
- Iterowalny: Każda sekwencja, przez którą można przejść za pomocą pętli
for
, np. zbiory, listy, krotki i słowniki. - Wywoływalny: Obiekt w Pythonie, który można uruchomić, używając nawiasów
()
, np.moja_funkcja()
. - Shell: Interaktywne środowisko, w którym można wykonywać kod Pythona, uruchamiane poleceniem
python
w terminalu. - Zmienna: Nazwa, która odnosi się do obiektu i zajmuje określoną przestrzeń w pamięci.
Zacznijmy od rozwiania pewnych niejasności. Gwiazdka w Pythonie pełni również funkcję operatora arytmetycznego. Pojedyncza gwiazdka (*
) służy do mnożenia, natomiast dwie gwiazdki (**
) oznaczają potęgowanie.
>>> 3*3 9 >>> 3**3 27
Możemy to łatwo sprawdzić, otwierając konsolę Pythona i wpisując powyższe linie.
Pamiętaj: aby skorzystać z tego samouczka, musisz mieć zainstalowaną wersję Pythona 3. Jeżeli nie masz jej zainstalowanej, skorzystaj z naszego poradnika instalacyjnego.
Jak widzisz, w powyższych przykładach gwiazdki są używane pomiędzy liczbami, co wskazuje na operacje arytmetyczne.
>>> *range(1, 6), (1, 2, 3, 4, 5) >>> {**{'vanilla':3, 'chocolate':2}, 'strawberry':2} {'vanilla': 3, 'chocolate': 2, 'strawberry': 2}
Z drugiej strony, kiedy umieszczamy gwiazdki (*
, **
) przed iterowalnymi, używamy ich do rozpakowywania. Oto przykład:
Jeżeli coś nie jest dla Ciebie jasne, nie przejmuj się – to dopiero wprowadzenie do rozpakowywania. Zapraszamy do dalszej lektury!
Na czym polega rozpakowywanie?
Rozpakowywanie to proces wydobywania elementów z iterowalnych struktur danych, takich jak listy, krotki i słowniki. Wyobraź sobie, że otwierasz pudełko i wyjmujesz z niego różne przedmioty: kable, słuchawki czy pendrive’y.
Rozpakowywanie w Pythonie przypomina wyjmowanie rzeczy z pudełka w rzeczywistości.
>>> mybox = ['kable', 'słuchawki', 'USB'] >>> item1, item2, item3 = mybox
Zilustrujmy to na przykładzie kodu, aby lepiej zrozumieć ten mechanizm:
Jak widzisz, przypisujemy trzy elementy z listy mybox
do trzech zmiennych: item1
, item2
i item3
. Ten rodzaj przypisywania wartości do zmiennych jest podstawą rozpakowywania w Pythonie.
>>> item1 'kable' >>> item2 'słuchawki' >>> item3 'USB'
Gdy spróbujesz wyświetlić wartość każdej zmiennej, zauważysz, że item1
przechowuje 'kable’, item2
'słuchawki’ i tak dalej.
>>> newbox = ['kable', 'słuchawki', 'USB', 'myszka'] >>> item1, item2, item3 = newbox Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: zbyt wiele wartości do rozpakowania (oczekiwano 3)
Do tej pory wszystko działało bez zarzutu, ale co się stanie, jeśli spróbujemy rozpakować listę zawierającą więcej elementów, niż mamy zmiennych do przypisania?
Prawdopodobnie spodziewałeś się takiego błędu. Próbujemy przypisać cztery elementy listy do trzech zmiennych. Jak Python miałby poprawnie przypisać wartości? Właśnie dlatego pojawia się błąd ValueError
z komunikatem „zbyt wiele wartości do rozpakowania”. Dzieje się tak, ponieważ po lewej stronie mamy trzy zmienne, a po prawej cztery wartości (z listy newbox
).
>>> lastbox = ['kable', 'słuchawki'] >>> item1, item2, item3 = lastbox Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: za mało wartości do rozpakowania (oczekiwano 3, otrzymano 2)
Podobna sytuacja wystąpi, gdy spróbujesz rozpakować iterowalną z mniejszą liczbą elementów niż zmiennych po lewej stronie. W takim przypadku również otrzymasz błąd ValueError
, ale z nieco innym komunikatem.
Pamiętaj: Chociaż pracowaliśmy na przykładach z listami, możesz stosować rozpakowywanie do dowolnych iterowalnych (list, zbiorów, krotek, słowników).
Jak zatem rozwiązać te problemy? Czy istnieje sposób, by rozpakować wszystkie elementy iterowalnej do mniejszej liczby zmiennych bez generowania błędów?
Oczywiście, że tak! W Pythonie istnieje operator rozpakowywania, czyli operator gwiazdki (*
, **
). Zobaczmy, jak go użyć.
Rozpakowywanie list za pomocą operatora *
Operator gwiazdki (*
)
>>> first, *unused, last = [1, 2, 3, 5, 7] >>> first 1 >>> last 7 >>> unused [2, 3, 5]
pozwala zebrać wszystkie wartości z iterowalnej, które nie zostały jeszcze przypisane do oddzielnych zmiennych.
>>> first, *_, last = [1, 2, 3, 5, 7] >>> _ [2, 3, 5]
Załóżmy, że chcesz pobrać pierwszy i ostatni element z listy, nie używając indeksów. Możesz to osiągnąć, stosując operator gwiazdki:
>>> first, *_, last = [1, 2] >>> first 1 >>> last 2 >>> _ []
Jak widzisz, wszystkie nieprzypisane wartości są gromadzone za pomocą gwiazdki. Zmienna podkreślenia (_
) jest często używana jako tak zwana „zmienna fikcyjna”, służąca do ignorowania niepotrzebnych wartości.
Ta metoda działa, nawet jeśli lista zawiera tylko dwa elementy:
W tym przypadku zmienna podkreślenia (zmienna fikcyjna) przechowuje pustą listę. Pozostałe dwie zmienne mają dostęp do pozostałych wartości listy.
>>> *string = 'PythonJestNajlepszy'
Typowe problemy
>>> *string = 'PythonJestNajlepszy' File "<stdin>", line 1 SyntaxError: cel przypisania z gwiazdką musi być listą lub krotką
Możemy rozpakować elementy iterowalnej do zmiennej. Możesz pomyśleć o czymś takim: Jednak powyższy kod zwróci błąd SyntaxError
: Dzieje się tak, ponieważ zgodnie z
specyfikacją PEP
>>> *string, = 'PythonJestNajlepszy' >>> string ['P', 'y', 't', 'h', 'o', 'n', 'J', 'e', 's', 't', 'N', 'a', 'j', 'l', 'e', 'p', 's', 'z', 'y']
krotka (lub lista) po lewej stronie prostego przypisania
>>> *numbers, = range(5) >>> numbers [0, 1, 2, 3, 4]
Jeśli chcemy rozpakować wszystkie wartości iterowalnej do jednej zmiennej, musimy użyć krotki. Wystarczy dodać przecinek po zmiennej.
Inny przykład to użycie funkcji range
, która zwraca sekwencję liczb.
Teraz gdy wiesz, jak rozpakowywać listy i krotki za pomocą gwiazdki, możemy przejść do rozpakowywania słowników.
Rozpakowywanie słowników za pomocą operatora **
>>> **greetings, = {'hello': 'WITAJ', 'bye':'POŻEGNANIE'} ... SyntaxError: nieprawidłowa składnia
Podczas gdy pojedyncza gwiazdka (*
) jest wykorzystywana do rozpakowywania list i krotek, podwójna gwiazdka (**
) służy do rozpakowywania słowników.
>>> food = {'ryba':3, 'mięso':5, 'makaron':9} >>> colors = {'czerwony': 'intensywność', 'żółty':'szczęście'} >>> merged_dict = {**food, **colors} >>> merged_dict {'ryba': 3, 'mięso': 5, 'makaron': 9, 'czerwony': 'intensywność', 'żółty': 'szczęście'}
Niestety, nie możemy rozpakować słownika do pojedynczej zmiennej, tak jak robiliśmy to z krotkami i listami. To oznacza, że następujący kod zwróci błąd:
Możemy jednak użyć operatora **
wewnątrz wywołań i innych słowników. Na przykład, jeśli chcemy utworzyć nowy słownik z połączenia innych słowników, możemy użyć tego kodu:
Jest to wygodny sposób na tworzenie złożonych słowników, jednak nie jest to główne zastosowanie rozpakowywania w Pythonie.
Zobaczmy, jak możemy wykorzystać rozpakowywanie w połączeniu z wywołaniami.
Pakowanie w funkcjach: argumenty *args
i **kwargs
Prawdopodobnie spotkałeś się już z argumentami *args
i **kwargs
, implementowanymi w klasach lub funkcjach. Przeanalizujmy, dlaczego używamy ich wraz z wywołaniami.
>>> def product(n1, n2): ... return n1 * n2 ... >>> numbers = [12, 1] >>> product(*numbers) 12
Pakowanie za pomocą operatora *
(args
)
>>> product(12, 1) 12
Załóżmy, że mamy funkcję obliczającą iloczyn dwóch liczb.
>>> numbers = [12, 1, 3, 4] >>> product(*numbers) ... TypeError: product() przyjmuje 2 argumenty pozycyjne, a podano 4
Jak widać, rozpakowujemy listę numbers
, a więc tak naprawdę wywołujemy:
>>> def product(*args): ... result = 1 ... for i in args: ... result *= i ... return result ... >>> product(*numbers) 144
Na razie wszystko działa poprawnie, ale co się stanie, gdy zechcemy przekazać dłuższą listę? Oczywiście otrzymamy błąd, ponieważ funkcja dostaje więcej argumentów, niż jest w stanie przyjąć.
Ten problem można rozwiązać, umieszczając listę bezpośrednio w funkcji. Wówczas tworzymy wewnątrz niej iterowalną, która pozwala przekazać dowolną liczbę argumentów.
W tym przypadku parametr args
jest traktowany jako iterowalny. Przechodzimy przez jego elementy i zwracamy iloczyn wszystkich liczb. Pamiętaj, że początkowa wartość zmiennej result
musi wynosić 1. W przeciwnym razie, funkcja zawsze zwróci zero. Uwaga: args
to tylko konwencja. Możesz użyć dowolnej innej nazwy parametru. Możemy także przekazać do funkcji dowolną liczbę argumentów, bez korzystania z listy, tak jak w przypadku funkcji wbudowanej
>>> product(5, 5, 5) 125 >>> print(5, 5, 5) 5 5 5
funkcji print
.
>>> def test_type(*args): ... print(type(args)) ... print(args) ... >>> test_type(1, 2, 4, 'napis') <class 'tuple'> (1, 2, 4, 'napis')
.
Sprawdźmy typ obiektu argumentów funkcji.
Jak widzisz w powyższym kodzie, typem args
zawsze będzie krotka. Będzie ona zawierać wszystkie argumenty pozycyjne przekazane do funkcji.
Pakowanie za pomocą operatora **
(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='londyn', net_worth=12000) 'Melissa: id = 12112, location = londyn, net_worth = 12000, '
Jak już wcześniej wspomnieliśmy, operator **
jest używany wyłącznie w połączeniu ze słownikami. Oznacza to, że za pomocą tego operatora możemy przekazywać pary klucz-wartość do funkcji jako parametr.
Stwórzmy funkcję make_person
, która przyjmie argument pozycyjny name
oraz nieokreśloną liczbę argumentów ze słowami kluczowymi.
Jak widać, instrukcja **kwargs
konwertuje wszystkie argumenty ze słowami kluczowymi na słownik. Następnie możemy przeiterować po tym słowniku wewnątrz funkcji.
>>> def test_kwargs(**kwargs): ... print(type(kwargs)) ... print(kwargs) ... >>> test_kwargs(random=12, parameters=21) <class 'dict'> {'random': 12, 'parameters': 21}
Pamiętaj: kwargs
to tylko przyjęta konwencja, możesz nazwać ten parametr, jak tylko chcesz.
Możemy sprawdzić typ kwargs
w taki sam sposób, jak zrobiliśmy to z argumentami *args
:
>>> def my_final_function(*args, **kwargs): ... print('Typ args: ', type(args)) ... print('args: ', args) ... print('Typ kwargs: ', type(kwargs)) ... print('kwargs: ', kwargs) ... >>> my_final_function('Python', 'Jest', 'Najlepszy', language="Python", users="Dużo") Type args: <class 'tuple'> args: ('Python', 'Jest', 'Najlepszy') Type kwargs: <class 'dict'> kwargs: {'language': 'Python', 'users': 'Dużo'}
Zmienna wewnętrzna kwargs
zawsze staje się słownikiem, w którym przechowywane są pary klucz-wartość przekazane do funkcji.
Na koniec, użyjmy *args
i **kwargs
w tej samej funkcji:
Podsumowanie
- Operatory rozpakowywania są bardzo przydatne w codziennej pracy. Teraz wiesz, jak ich używać zarówno w pojedynczych instrukcjach, jak i w parametrach funkcji.
- W tym artykule dowiedziałeś się:
- Jak używać
*
dla krotek i list oraz**
dla słowników. - Operatorów rozpakowywania można używać w konstruktorach funkcji i klas.
*args
służy do przekazywania do funkcji argumentów pozycyjnych (innych niż słowa kluczowe). **kwargs
służy do przekazywania argumentów ze słowami kluczowymi.