Przejdź do treści

2025

Zadanie z gwiazdką, czyli rzecz o args i *kwargs

Często spotykamy się z funkcjami, które wśród argumentów zawierają \"magiczne\" wyrażenia *args i **kwargs. Jednak początkujący programiści Pythona miewają problemy ze zrozumieniem działania tego mechanizmu.

Zacznijmy od odpowiedzenia sobie na pytanie czym są te "argsy i kwargsy" i czemu poprzedzają je gwiazdki.
Wyrażenie args bierze się od słowa arguments czyli argumenty i jest to zazwyczaj zmienna, która zawiera tuple argumentów pozycyjnych.
Natomiast kwargs bierze się od keyword arguments czyli argumenty nazwane i zmienna taka zawiera pary nazwa-wartość argumentu.

Jednak należy zaznaczyć, że nazwy te nie są wcale najważniejsze. Największa magia kryje się w gwiazdkach je poprzedzających, a args i kwargs możemy zastąpić dowolnymi innymi nazwami zmiennych.


Umieszczając w definicji funkcji wyrażenie z jedną lub dwoma gwiazdkami pozwalamy na przekazywanie do niej argumentów w dowolnej liczbie i nazwie.
Bez konieczności konkretnego określenia tych parametrów. Jest to przydatne w momencie gdy chcemy, aby nasza funkcja była nieco bardziej uniwersalna.

Po pierwsze: *args

Wyrażenia z jedną gwiazdką (*) używamy gdy do funkcji chcemy przekazać dowolną liczbę argumentów pozycyjnych.
Czyli takich dla których przy wywołaniu funkcji nie podajemy ich nazwy, a przypisanie wartości bazuje na kolejności argumentów.
Z tego powodu parametr *args umieszczamy na końcu listy parametrów w definicji funkcji.

def parametr_args(argument, *args):
    print("zawartość args: {}".format(args))
    print("argument nazwany: {}".format(argument))
    for arg in args:
        print("argument z *args: {}".format(arg))

parametr_args('python', 'spam', 'eggs', 'test')

Możesz zauważyć, że wywołując powyższą funkcję przekazaliśmy w jej wywołaniu więcej argumentów niż zadeklarowaliśmy. Nadmiarowe argumenty zostały umieszczone w tupli po której z łatwością możemy iterować, aby dostać się do tych argumentów.

Po drugie: **kwargs

W przypadku gdy do naszej funkcji chcemy przekazywać argumenty, które wyróżniają się nazwą możemy użyć parametru z dwoma gwiazdkami (**). Przekazane w ten sposób argumenty są dostępne w funkcji w postaci słownika. Jego pary klucz-wartość stanowią nazwę i wartość przekazanego argumentu.

def parametr_kwargs(argument, **kwargs): 
    print("argument: {}".format(argument))
    print("zawartość kwargs: {}".format(kwargs))

parametr_kwargs(dodatkowy=48, nastepny=111, argument=12) 

# argument: 12 
# zawartość kwargs: {'dodatkowy': 48, 'nastepny': 111}

Jak widzisz w powyższym przykładzie argumenty, które nie zostały zdefiniowane w funkcji trafiły do słownika o nazwie kwargs.

Jak używać gwiazdek w *args i **kwargs?

Możesz również używać obu poznanych przed chwilą parametrów. W tym przypadku pamiętaj jedynie o kolejności – wyrażenie z dwoma gwiazdkami musi być na końcu, z jedną gwiazdką wcześniej, a pozostałe zdefiniowane argumenty na początku.

Gwiazdek możesz również używać w wywołaniu funkcji. Jest to tak zwane rozpakowywanie list i słowników z argumentami. Jeżeli funkcja przyjmuje kilka argumentów i posiadasz listę, która zawiera argumenty, które chcesz przekazać do tej funkcji wystarczy poprzedzić nazwę listy gwiazdką zamiast podawać kolejne argumenty ręcznie.

lista_argumentow = [1, 3, 5] 

def rozpakowywanie(pierwszy, drugi, trzeci):
    print(pierwszy)
    print(drugi)
    print(trzeci) 

rozpakowywanie(*lista_argumentow) 

# 1 
# 3 
# 5

Przyznasz, że ten sposób wygląda dużo lepiej niż:

rozpakowywanie(lista_argumentow[0], lista_argumentow[1], lista_argumentow[2])

Mam nadzieję, że ten artykuł rozjaśnił Ci działanie gwiazdek w Pythonie. Jeżeli masz jeszcze jakieś pytanie dotyczące działania tego mechanizmu zapraszam do zostawienia komentarza.

Jeżeli dopiero zaczynasz swoją przygodę z Pythonem lub jesteś bardziej zaawansowany sprawdź jak może pomóc Ci Jupyter Notebook.


A może potrzebujesz pomocy w nauce programowania i nie wiesz do kogo zwrócić się z pytaniami? Zajrzyj na stronę O Blogu – znajdziesz tam więcej szczegółów na temat prowadzonego przeze mnie mentoringu.

Spodobał Ci się ten artykuł? Sprawdź też pozostałe wpisy na moim blogu!

Context

Tworząc oprogramowanie często spotykamy się z sytuacją gdy chcemy uzyskać dostęp oraz zarządzać pewnymi zasobami. Takimi jak na przykład operacje na plikach czy połączenie do bazy danych. Zasoby te często posiadają pewne ograniczenia więc dobrą praktyką jest ich zwolnienie zaraz po zakończeniu ich używania. Python pozwala nam na łatwe zarządzanie tymi zasobami dostarczając nam pomoc, którą jest menadżer kontekstu.

\n\n\n\n\n\n\n\n

Dobrym przykładem opisanych we wstępie ograniczeń jest próba otwarcia wielu plików na raz. Spróbuj wykonać następujący program:

\n\n\n\n

fh = []\nfor i in range(10000):\n    fh.append(open(\'log.txt\', \'w\'))

\n\n\n\n

W tym momencie prawdopodobnie zobaczysz następujący wyjątek:

\n\n\n\n

Traceback (most recent call last):
File \"\", line 2, in
OSError: [Errno 24] Too many open files: \'log.txt\'

\n\n\n\n

Jest to spowodowane tym, że system nakłada na procesy ograniczenia tego ile plików może na raz otworzyć jeden proces. W tym momencie jednak pozostaliśmy z listą wielu już otwartych plików, które i tak powinny zostać zamknięte. Moglibyśmy dodać do naszego kodu strukturę try-except-finally, jednak może ona wpłynąć negatywnie na czytelność naszego kodu.

\n\n\n\n

Dlatego często otwierając pliki korzystamy właśnie z menadżerów kontekst. Menadżer kontekstu udostępniany przez funkcję open() jest jednym z najpopularniejszych, dlatego prawdopodobnie mieliście już okazję się z nim spotkać nawet jeżeli jesteście początkującymi użytkownikami Pythona.

\n\n\n\n

Zastosowanie tego narzędzia może wyglądać w następujący sposób:

\n\n\n\n

with open(\"log.txt\") as f:    \n    data = f.read() 

\n\n\n\n

Poza wykorzystaniem już istniejących menadżerów kontekstu przy pomocy klauzuli with, możemy też oczywiście napisać nasz własny menadżer. Istnieją dwa sposoby w jaki możemy to zrobić, napisać klasę lub funkcję.

\n\n\n\n

Menadżer kontekstu jako klasa

\n\n\n\n

Pierwszym ze sposobów na napisanie własnego menadżera kontekstu jest napisanie odpowiedniej klasy. Musi ona zawierać metody__enter__ i __exit__. Opowiadają one za kolejno to co wydarzy się przed oraz po wykonaniu kodu znajdującego się w bloku with. Metoda __enter__ powinna zwrócić zasoby, na których będziemy operować wewnątrz bloki with, a metoda __exit__ nie musi nic zwracać, a jej zadaniem jest posprzątanie na koniec.

\n\n\n\n

Na początek spróbujmy napisać prosty menadżer, który może być przydatny podczas wykonywania operacji na plikach. Będzie on dział na podobnej zasadzie jak ten z poprzedniego przykładu jednak rozszerzy jego funkcjonalność o dodatkowe logowanie stanu.

\n\n\n\n

class FileManager(): \n    def __init__(self, filename, mode):\n        print(\"Metoda __init__\")\n        self.filename = filename \n        self.mode = mode \n        self.file = None\n          \n    def __enter__(self): \n        print(\"Metoda __enter__\")\n        self.file = open(self.filename, self.mode) \n        return self.file\n      \n    def __exit__(self, type, value, traceback):\n        print(\"Metoda __exit__\")\n        self.file.close() \n  \nwith FileManager(\"log.txt\", \'r\') as f:\n    print(\"Blok with\")\n    data = f.read() 

\n\n\n\n

W metodzie __init__ tworzymy zmienne, które przechowują informacje dotyczące zasobów którymi chcemy zarządzać. Metoda __enter__ odpowiada za otwarcie i udostępnienie zasobów (w tym przypadku uchwytu do otwartego pliku), a metoda __exit__ tak jak już była o tym mowa wcześniej - sprząta, czyli w naszym przykładzie zamyka plik. Dzięki dodatkowym funkcjom print możesz łatwo zobaczyć w jaki sposób wywoływane są kolejne funkcje.

\n\n\n\n

W następnym kroku spróbuj wywołać wyjątek wewnątrz bloku with z powyższego przykładu (możesz to zrobić na przykład dodając linijkę raise Exception). Zobaczysz, że metoda __exit__ zostanie wywołana, a dopiero w dalszej kolejności pojawi się informacja o wyjątku.

\n\n\n\n

Jeżeli chcesz, żeby menadżer kontekstu sam obsługiwał wyjątki możesz zrobić to wewnątrz metody __exit__ korzystając z trzech argumentów które do niej przekazujemy. W naszym przykładzie to: type, value i traceback. Gdy skorzystasz z tej możliwości, aby zapobiec przesłaniu wyjątku poza blok with upewnij się, że funkcja __exit__ zwróci wartość True.

\n\n\n\n

Menadżer kontekstu jako funkcja

\n\n\n\n

Drugim sposobem na stworzenie menadżera kontekstu jest napisanie generatora, czyli funkcji zawierającej instrukcję yield. W tym celu będziemy musieli wykorzystać dekorator contextmanager który znajduje się w bibliotece contextlib. Metoda ta nieco upraszcza zapis i z pewnością dobrze nadaje się do tworzenia prostych menadżerów.

\n\n\n\n

Na początek spójrzmy na przykładowy kod:

\n\n\n\n

from contextlib import contextmanager\n\n@contextmanager\ndef file_manager(name, mode=\'w\'):\n    f = open(name, mode)\n    yield f\n    f.close()

\n\n\n\n

Składa się on z kilku części, które są warte są krótkiego wytłumaczenia:

\n\n\n\n

  1. Instrukcja yield sprawia, że Python traktuje tą funkcję jako generator, dzięki czemu możliwy jest powrót do jej dalszej części po zwróceniu wartości.
  2. Dekorator contextmanager nadpisuję naszą funkcję w taki sposób, że odwołując się do niej otrzymamy obiekt typu
    GeneratorContextManager.
  3. Dzięki temu możemy wykorzystać naszą funkcję jako menadżer kontekstu używając instrukcji with.

\n\n\n\n

\n\n\n\n

Teraz prawdopodobnie wiesz już wszystko co potrzebne jest do używania menadżerów kontekstu. Jeżeli masz jakiekolwiek pytania zapraszam do komentowania i życzę Ci powodzenia w dalszej nauce Pythona :)

\n\n\n\n

Źródła:
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers

https://docs.python.org/3/library/contextlib.html

\n\n\n\n

Jeżeli chcesz dowiedzieć się więcej o programowaniu w Pythonie zapraszam Cię do przeczytania artykułu na temat tego jak używać args i kwargs w naszych funkcjach.

\n\n\n

Pytest

Jedną z najważniejszych rzeczy w trakcie pracy nad aplikacją jest zapewnienie tego, by miała ona jak najmniej błędów oraz żeby kolejne zmiany w kodzie nie zepsuły naszych dotychczasowych funkcjonalności. W tym artykule chciałbym przybliżyć Ci nieco filozofię testowania kodu przy pomocy biblioteki Pytest.