Argumenty są to zmienne przekazywane w funkcji w charakterze danych wejściowych – prościej mówiąc, są to te zmienne, które pojawiają się w nawiasie podczas wywołania funkcji. Argumenty nazywa się również parametrami. Dobre opanowanie zasad ich działania pozwala zawsze przekazywać dokładnie te zmienne, których oczekuje nasza funkcja. Duże znaczenie ma tutaj kolejność przekazywanych argumentów. W tym wpisie pokażę kilka tricków na to, aby budować funkcje przyjmujące dowolny rodzaj i liczbę argumentów.
Najważniejsze informacje dotyczące argumentów to:
- są one przekazywane poprzez przypisanie. Są to referencje do obiektów znajdujących się w zasięgu lokalnym. Referencje są implementowane jako wskaźniki, dlatego argumenty przekazywane są właśnie za pomocą wskaźników. Oznacza to, że argumenty nigdy nie są automatycznie kopiowane.
- przypisanie do nazw argumentów wewnątrz funkcji nie wpływa na kod wywołujący. Nazwy argumentów nagłówku funkcji stają się nowymi, zmiennymi lokalnymi w trakcie wykonywania funkcji.
- modyfikacja argumentu mutowalnego z kodu wywołującego, takiego jak tablica czy słownik, może mieć wpływ na obiekt w kodzie wywołującym.
Wynika z tego, że:
- argumenty niemutowalne, takie jak liczby czy łańcuchy znaków, są przekazywane przez wartość. Chociaż tak naprawdę przekazujemy je przez wskaźnik to ze względu na to, że nie są modyfikowane w miejscu, efekt jest taki, jakbyśmy stworzyli ich kopie.
- Argumenty mutowalne są faktycznie przekazywane przez referencję, czyli wskaźnik, ponieważ można je modyfikować w miejscu.

Prosty przykład działania referencji
Rozważmy poniższy kod:
>>> def func(a):
... a=99
...
>>> b=0
>>> func(b)
>>> print(b)
0
Jak widzimy, funkcja nie zmieniła wartości zmiennej b w zakresie globalnym. Przypisanie do nazw argumentów wewnątrz funkcji nie ma zatem rzeczywiście wpływu na kod wywołujący, jako że zmienna b zachowuje swoją wartość. Inaczej dzieje się w przypadku zmiennych mutowalnych, które mają wpływ na kod wywołujący.
>>> def func_mut(a, b):
... a = 2
... b[0] = 'example'
...
>>> X = 1
>>> L = [1, 2]
>>> func_mut(X, L)
>>> print(L)
['example', 2]
Jak widzimy doszło do zmiany L, właśnie ze względu na to, że jest to zmienna mutowalna, a dokładniej zmienna odnosząca się do wartości mutowalnej. Dlatego dochodzi do jej modyfikacji w miejscu. Inaczej można to zobrazować poprzez serię prostych instrukcji przepisania.
W przypadku obiektów niemutowalnych:
>>> X = 1
>>> a = X
>>> a = 2
>>> X
1
W przypadku obiektów mutowalnych:
>>> L = [1, 2]
>>> b = L
>>> b[0] = 'intruz'
>>> L
['intruz', 2]
Jak widać, obiekt jest modyfikowany w miejscu.
Argumenty mutowalne – modyfikować czy nie?
Odo osby piszącej kod zależy, czy będzie używał w nich argumentów mutowalnych lub niemutowalnych. Kiedy warto ich używać? Dzieje się tak na przykład wtedy, gdy:
- chcemy przekazać w programie duży obiekt bez potrzeby wykonywania jego kopii,
- chcemy szybko uaktualniać obiekty.
Co zrobić, gdy nie chcemy modyfikować obiektu w miejscu, lecz mimo wszystko chcemy użyć listy lub słownika? Wówczas należy taki obiekty po prostu skopiować. Możemy tego dokonać w trakcie wywołania funkcji:
>>> L = [1, 2]
>>> func_mut(X, L[:])
>>> L
[1, 2]
Lub też w jej definicji:
>>> def func_mut(a, b):
... b = b[:]
>>> L = [1, 2]
>>> func_mut(1, L)
>>> L
[1, 2]
W obu przypadkach nie dochodzi do przeniesienia zmian wychodzi wywołującym.
Zwracanie wielu wyników działania
W Pythonie możliwe jest napisanie takiej funkcji, która zwraca więcej niż jeden wynik. Wówczas jest on pakowany w krotkę i może być przypisany do kilku zmiennych.
>>> def multiple_results(x, y):
... x = 2
... y = 3
... z = [2, 4]
... return x, y, z
>>> x = 4
>>> y = 7
>>> multiple_results(x, y)
(2, 3, [2, 4])
Specjalne tryby dopasowywania argumentów
Argumenty są zawsze przekazywane poprzez przypisanie. Nazwy z nagłówka funkcji są przypisywane do przekazywanych obiektów. Takie dopasowywanie domyślnie odbywa się za pomocą pozycji, czyli od lewej do prawej, jednak możliwa jest zmiana tego zachowania poprzez dodatkową składnię – istnieje możliwość dopasowania obiektów wskazywanych przez argumenty do nazw w nagłówku funkcji.
Oto sposoby, w jaki można dopasowywać argumenty funkcji:
- za pomocą pozycji, czyli domyślnie: wartości przekazywanych argumentów są dopasowywane do nazw argumentów nagłówku funkcji zgodnie z pozycją, od lewej do prawej,
- za pomocą słów kluczowych, czyli dopasowanie zgodnie z nazwą argumentu: kod określa, jaki argument funkcji ma otrzymać określoną wartość poprzez składnię nazwa=wartość,
- za pomocą wartości domyślnych dla argumentów opcjonalnych, które nie zostały podane: w tym przypadku również wykorzystuje się składnię nazwa=wartość,
- za pomocą zmiennej liczby argumentów (zbieranie): są to argumenty specjalne, poprzedzone znakiem * lub **, stworzone po to, aby akceptować zmienną liczbę argumentów, ta składnia jest określana na etapie definiowania funkcji,
- za pomocą zmiennej liczby argumentów (rozpakowywanie) – są to argumenty specjalne, poprzedzone znakiem * lub **, które w momencie wywołania funkcji są rozpakowywane, a więc przypisywane do kolejnych argumentów.
Dokładna składnia zależy od tego, czy dana funkcja jest definiowana czy wywoływana.
Przypadki odwołujące się do wywołania funkcji to:
- func(wartość) – dopasowanie pozycyjne,
- func(nazwa=wartość) dopasowanie po naziwe (słowo kluczowe),
- func(*obiekt_iterowalny) – przekazanie wszystkich elementów danego obiektu iterowalnego jako pojedynczych argumentów,
- func(**słownik) – przekazanie wszystkich par klucz-wartość ze słownika jako pojedynczych arguemntów słów kluczowych
Przypadki odwołujące się do definicji funkcji:
- def func(nazwa) – normalny argument, dopasowuje przekazane wartości propozycji lub nazwie,
- def func(nazwa=wartość) – przekazanie wartości domyślnej, jeśli wartość nie została przekazana w wywołaniu,
- def func(*nazwa) – dopasowuje i zbiera pozostałe argumenty pozycyjne,
- def func(**nazwa) – dopasowuje i zbiera pozostałe argumenty słowa-klucze,
- def func(*argumenty, nazwa) – argumenty muszą zostać przekazane za pomocą słowa kluczowego
- def func(*, nazwa=wartość) – argumenty muszą zostać przekazane za pomocą słowa kluczowego
Dwa ostatnie przypadki pozwalają określać nam, które argumenty mają zostać przekazane za pomocą słowa kluczowego. Te wszystkie możliwości pozwalają niejako manipulować tym, jakie i ile argumentów zostanie przekazanych do funkcji. Dotyczy to w szczególności ich liczby. Do tego możemy przekazywać wartości domyślne, co jest pomocne w przypadku funkcji, których zmienne nie zawsze są określone. Co do wartości domyślnych, to będą one wykorzystane tylko wtedy, gdy przekażemy zbyt małą liczbę argumentów. Z kolei możemy również przekazać zbyt dużą liczbę argumentów, i jeśli tylko zdecydujemy się na składnię z * lub **.
Korzystając ze specjalnych trybów dopasowywania argumentów, musimy pamiętać o zachowanie odpowiedniej kolejności.
W wywołaniu funkcji argumenty muszą być ustawione w następującej kolejności: argumenty pozycyjne, argumenty ze słowami kluczowymi, obiekt iterowalny z *, słownik z **.
W definicji funkcji, czyli w jej nagłówku, argumenty mogą pojawić się w następującej kolejności: argumenty pozycyjne, argumenty ze słowami kluczowymi, forma *nazwa, argumenty mogące być tylko słowami kluczowymi (nazwa=wartość), a na końcu **nazwa.
W obu przypadkach forma z ** pojawia się na końcu. Wewnętrzne kroki wykonywane przez Pythona w celu dopasowania argumentów to:
- przypisanie argumentów niebędących słowami kluczowymi zgodnie z ich pozycją,
- przypisanie argumentów będących słowami kluczowymi poprzez dopasowanie nazw (nazwa=wartość),
- przypisanie dodatkowych argumentów mm nie będących słowami kluczowymi do krotki **nazwa,
- przypisanie dodatkowych argumentów będących słowami kluczowymi do słownika **nazwa,
- przypisanie nie przypisanych wartości domyślnych z nagłówka do pozostałych argumentów.
Po zakończeniu tego całego procesu do obiektów przekazywanych do funkcji przypisywane są nazwy ustalonych argumentów.
Na szczęście korzystanie z zasad tworzenia i przypisywanie argumentów jest znacznie prostsze w praktyce niż w teorii.
Przypisanie po pozycji
>>> def f(a, b, c): print(a, b, c)
>>> f(1, 2, 3)
1 2 3
Każda wartość jest przypisana do nazwy po jej pozycji.
Słowa kluczowe
Dzięki użyciu słów kluczowych można być bardziej precyzyjnym, czyli dopasowywać argumenty po nazwie, a nie po pozycji.
>>> f(c=3, a=1, b=2)
1 2 3
Argumenty pozycyjne i słowa kluczowe można mieszać w jednym wywołaniu – wówczas po pozycji dopasowane zostaną wszystkie argumenty, które nie są słowami kluczowymi:
>>> f(1, b=2, c=3)
1 2 3
Wartości domyślne
Dzięki wartościom domyślnym niektóre argumenty funkcji mogą być opcjonalne. Jeśli nie przekażemy do nich wartości, to jeszcze przed wykonaniem funkcji zostanie do nich przypisana wartość domyślna. Przykład:
>>> def f(b, c, a=1): print(a, b, c)
>>> f(2, 3)
1 2 3
Zmienna a musiała być w tym przypadku przeniesiona na sam koniec, ponieważ takie są zasady kolejność umieszczania argumentów w nagłówkach.
Słowo kluczowe nadpisuje również wartość domyślną – a ma teraz wartość 0:
>>> f(2, 3, 0)
0 2 3
Składnia nazwa=wartość nie powinna być mylona w nagłówku funkcji vs. w wywołaniu funkcji. W wywołaniu oznacza ona argument dopasowywany po nazwie, natomiast w nagłówku, czyli w definicji funkcji, określa ona wartość domyślną opcjonalnego argumentu.
Słowa kluczowe i wartości domyślne
Słowa kluczowe i wartości domyślne można oczywiście łączyć ze sobą. Przykład:
>>> def func(first, second, third=3, fourth=4):
... print(first, second, third, fourth)
>>> func(second=2, first=1)
1 2 3 4
Argumenty ze słowami kluczowymi w wywołaniu funkcji sprawiają, że kolejność argumentów nie ma znaczenia. Python dopasowuje je według nazwy, a nie według pozycji.
Dowolne argumenty
Dopasowania typu * oraz ** zostały stworzone dla funkcji, które mają przyjmować dowolną liczbę argumentów. Mogą pojawić się w nagłówku, czyli na etapie definiowania funkcji lub w jej wywołaniu. Ich cele są niejako powiązane.
Nagłówek: zbieranie argumentów
Pierwszy zapis pozwala na przekazanie dowolnej liczby argumentów do funkcji:
>>> def func(*args): print(args)
>>> func(1, 2, 'a', 'b', [9, 0])
(1, 2, 'a', 'b', [9, 0])
Jak widać argumenty mogą być różnego typu. Zapis z * w definicji funkcji oznacza, że podczas wywołania funkcji wszystkie argumenty są zbierane w krotkę, po czym zmienna args jest przypisywana do tej krotki. Taki obiekt można na przykład indeksować lub przychodzić po nim za pomocą pętli for.
Podobnie działa funkcja zdefiniowana z argumentami, które są konwertowane na słownik. Dzieje się tak za pomocą składni z **. Przykład:
>>> def func(**args): print(args)
>>> func(a=1, b=2)
{'a': 1, 'b': 2}
W tym przypadku zbierane są tylko argumenty podane jako słowa kluczowe; są one grupowane w słownik, przez który można przechodzić wykorzystując techniki typowe dla słownika. Słownik zostaje przypisany do zmiennej args.
Obie techniki mogą być połączone w jednym nagłówku. Daje to bardzo elastyczne możliwości wywołania z praktycznie nieograniczoną liczbą argumentów.
>>> def f(a, *pargs, **kargs): print(a, pargs, kargs)
>>> f(1, 2, 3, b=4, v=5)
1 (2, 3) {'b': 4, 'v': 5}
Wywołania: rozpakowanie argumentów
Składnię z * oraz ** można również wykorzystać podczas wywołania funkcji. W takim kontekście rozpakowuje ona kolekcję argumentów. Oznacza to, że zbiór przekazanych w ten sposób argumentów zostanie podzielony (rozpakowany) na pojedyncze argumenty.
>>> def func(a, b, c, d): print(a, b, c, d)
>>> args = (1, 2, 3, 4)
>>> func(*args)
1 2 3 4
Podobnie działa składnia z ** wywołaniu funkcji, z tym, że rozpakowuje ona słownik i zawarte w nim pary klucz-wartość.
>>> args = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> func(**args)
1 2 3 4
Obie techniki mogą być połączone w jednym wywołaniu funkcji i to na różne sposoby.
>>> func(*(1, 2), **{'d': 4, 'c': 3})
1 2 3 4
>>> func(1, *(2, 3), **{'d': 4})
1 2 3 4
Kiedy korzystać z tego typu kodu? Gdy nie jesteśmy pewni liczby argumentów w momencie pisania skryptu. Wystarczy wówczas zapisać funkcję z nagłówkiem zbierającym dowolną liczbę argumentów, a następnie przekazać do niej kolekcję składającą się z zebranych argumentów, rozpakowując ją przy tym na pojedyncze argumenty. Daje to jeszcze więcej swobody, na przykład przy wybieraniu tego, która funkcja ma zostać odpalona. Przykładowo:
>>> def func_1(*args, **kargs): print('Opcja 1, przekazane argumenty: ', args, kargs)
>>> def func_2(*args, **kargs): print('Opcja 2, przekazane argumenty: ', args, kargs)
>>> def testing(choice):
... if choice == 1:
... action, args = func_1, (1, )
... action(*args)
... else:
... action, args = func_2, (22, 222)
... action(*args)
...
>>> testing(1)
Opcja 1, przekazane argumenty: (1,) {}
>>> testing(2)
Opcja 2, przekazane argumenty: (22, 222) {}
Ważne jest to, aby zbiór argumentów przekazany do rozpakowania, czyli do podzielenia na pojedyncze argumenty, był zawsze zbiorem, a nie pojedynczą wartością. Oczywiście dla rozpakowań z * powinniśmy używać krotek, natomiast dla rozpakowań z ** – słowników. Innymi słowy składnia wywołań z dowolną liczbą argumentów powinna być używana zawsze wtedy, gdy nie możemy przewidzieć listy argumentów, przez co napisanie lub wywołanie funkcji w trakcie działania programu nie jest możliwe.
Argumenty tylko ze słowami kluczowymi
W Pythonie 3.x istnieje specjalna składnia, która może zmusić kod do tego, aby niektóre argumenty były przekazywane jedynie w postaci argumentów ze słowami kluczowymi, a nie za pomocą pozycji. Argumenty tego typu zapisywane są na liście argumentów po formie *args. Przykładowo w kodzie poniżej argument a może być przekazany za pomocą nazwy lub pozycji, b pozwala zebrać wszystkie nieprzypisane argumenty pozycyjne, z kolei argument c może być przekazane jedynie przez słowo kluczowe.
>>> def func_kargs(a, *b, c): print(a, b, c)
...
>>> func_kargs(1, 2, 3, c=4)
1 (2, 3) 4
>>> func_kargs(1, c=3)
1 () 3
>>> func_kargs(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func_kargs() missing 1 required keyword-only argument: 'c'
Jak widać kod zgłasza błąd, jeśli argument c nie jest przekazany jako argument ze słowem kluczowym.
Zapis z * w nagłówku funkcji nie wymaga nawet użycia nazwanego argumentu.
>>> def func_kargs(a, *, b): print(a, b)
...
>>> func_kargs(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func_kargs() takes 1 positional argument but 2 were given
>>> func_kargs(1, b=2)
1 2
Taki zapis stosuje się, gdy chcemy zakomunikować, że dany argument musi zostać przekazany jako słowo kluczowe. Argumenty mogący być jedynie słowami kluczowymi kluczowymi możemy nadal wykorzystywać wraz z wartościami domyślnymi. Wówczas w razie momentu wywołaniu funkcji zostanie użyta wartość domyślna – można wówczas powiedzieć, że argument będący słowem kluczowym jest opcjonalny.
>>> def func_kargs(a, *, b=2): print(a, b)
>>> func_kargs(1)
1 2
Co dzieje się wówczas z listą argumentów oznaczonych w nagłówku funkcji jako **(czy zbierającym argumenty przekazane nie poprzez ich pozycję, ale w formie słownika)? Jeśli w nagłówku funkcji występuje już forma z *, to forma z ** musi pojawić się na końcu całej listy argumentów. Przykład:
>>> def func(a, b, *c, **d, c=6): print('Źle')
File "<stdin>", line 1
def func(a, b, *c, **d, c=6): print('Źle')
^
SyntaxError: arguments cannot follow var-keyword argument
Poprawna kolejność argumentów w nagłówku wygląda następująco:
>>> def func(a, *b, c=3, **d): print(a, b, c, d)
>>> func(1, 2, x=4)
1 (2,) 3 {'x': 4}
Podobnie dzieje się przy wywołaniu – tutaj również argumenty mogące być tylko słowami kluczowymi muszą pojawić się przed formą **args, ewentualnie mogą zawierać się w rozpakowanym słowniku.
>>> def f(a, *b, c=3, **d): print(a, b, c, d)
...
>>> f(1, *(2, 3), **{'c': 666, 'd': 4})
1 (2, 3) 666 {'d': 4}
Dlaczego stosować argumenty ze słowami kluczowymi?
Głównie dlatego, aby umożliwić funkcji przyjmowanie dowolnej liczby argumentów pozycyjnych przy jednoczesnym ograniczeniu tego, jakie argumenty mogą zostać podane jako na przykład opcje konfiguracyjne. Czyli na przykład, gdy nie chcemy, aby argument c przyjął wartość podaną jako tak naprawdę a lub b, to stosujemy takie właśnie rozwiązanie.
>>> def func_kargs(a, *b, c): print(a, b, c)
Taki zapis pozwala upewnić się, że przypadkowe zmienne nie zostaną przypisane do danego argumentu tylko po ich pozycji.
Wykorzystanie wiedzy w praktyce, czyli funkcja wskazująca wartość minimalną
Możliwość zbierania wielu argumentów w nagłówku funkcji daje doskonałe pole do popisu dla wszelkich funkcji matematycznych. Zacznijmy zatem od funkcji wskazujących wartość minimalną, która oczywiście przyjmie wiele argumentów. Opcji zapisania takiej operacji jest wiele, skorzystajmy z tej, która wykorzystuje metodę sortowania listy: sort().
>>> def min(*args):
... tmp = list(args)
... tmp.sort()
... return tmp[0]
...
>>> min('bb', 'aa', 'cc')
'aa'
>>> min(1, 2, 3, 4)
1
>>> min([1, 0], [1, 1], [1, 2])
[1, 0]
Rozszerzenie funkcji – szukanie maksymalnej i minimalnej wartości
Wyższą funkcję można rozbudować tak, a wyszukała również wartości maksymalnej.
>>> def minmax(test, *args):
... first = args[0]
... for arg in args[1:]:
... if test(arg, first):
... first=arg
... return first
...
>>> print(minmax(greater_than, 1, 2, 3, 4))
4
>>> print(minmax(less_than, 1, 2, 3, 4))
1
>>> print(minmax(greater_than, *('Ala', 'kot', 'ma', 'ale')))
ma
>>> print(minmax(less_than, *('Ala', 'kot', 'ma', 'ale')))
Ala
Teraz przejdźmy do czegoś skomplikowanego, czyli do funkcji działającej na zbiorach, która zwraca elementy wspólne dla wielu zbiorów.
>>> def intersect(*args):
... res = [] #zbieraj podane zmienne
... for x in args[0]: #przejrzyj pierwszą sekwencję
... if x in res: continue #pomiń duplikat
... for other in args[1:]: #dla pozostałych argumentów
... if x not in other: break #jeśli elementu nie ma, opuść pętle
... else:
... res.append(x) #jeśli jest, dodaj element do wyniku
... return res
...
>>> intersect('Teofil', 'Teodor')
['T', 'e', 'o']
Emulacja funkcji print
Dopasowywanie argumentów można poćwiczyć również, budując własną funkcję print, co przy dzisiejszych możliwościach tej funkcji nie ma większego sensu, natomiast sprawdzi się jako ćwiczenie:
>>> def my_print(*args, sep=' ', end='\n', file=sys.stdout):
... output = ''
... first = True
... for arg in args:
... output += ('' if first else sep) + str(arg)
... first = False
... file.write(output + end)
...
>>> my_print('Ala ma kota')
Ala ma kota
…. i to by było na tyle odnośnie argumentów w Pythonie. W kolejnym wpisie przyjrzymy się bardziej zaawansowanym zagadnieniom związanym z funkcjami 🙂
Dodaj komentarz
Musisz się zalogować, aby móc dodać komentarz.