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.
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.
defparametr_args(argument,*args):print("zawartość args: {}".format(args))print("argument nazwany: {}".format(argument))forarginargs: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.
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.
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.
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!
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.
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
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.
Dekorator contextmanager nadpisuję naszą funkcję w taki sposób, że odwołując się do niej otrzymamy obiekt typu GeneratorContextManager.
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 :)
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.
Moim zdaniem rzeczą, która należy wymienić na początku wśród tych, które sprawiają, że Python jest tak przyjazny poczatkującym użytkownikom jest jego wszechobecna spójność. Już po chwili spędzonej z tym językiem, jesteśmy w stanie domyślić się jak będą działały rzeczy, których jeszcze nie poznaliśmy, tylko na podstawie dotychczasowych doświadczeń. Za przykład może nam posłużyć zadanie sprawdzenia liczby elementów w dowolnej kolekcji. W taki sam sposób jak dla stringa możemy zrobić to funkcją len() w przypadku listy, czy też bardziej zaawansowanej kolekcji jak na przykład OrderedDict. Możemy sprawić, że w ten sam sposób zachowywać będą się klasy stworzone przez nas, dzięki wykorzystaniu magicznych metod.
\n\n\n\n\n\n\n\n
Magiczne metody (inaczej atrybuty specjalne klasy) to metody, które pozwalają na dodanie do definiowanych przez nas klas Pythonowego interfejsu. Maja one ściśle określone nazwy (zawsze otoczone podwójnymi podkreślnikami) odpowiadające poszczególnym zadaniom. Zadania te, wykonywane przez magiczne metody, możemy podzielić na kilka typów i w tym artykule chciałbym przedstawić najpopularniejsze metody dla wybranych typów. Opis wszystkich metod magicznych dostępnych w Pythonie znajdziesz w dokumentacji [https://docs.python.org/3/reference/datamodel.html]
\n\n\n\n
Tworzenie i usuwanie instancji
\n\n\n\n
__new__(cls, ...)
\n\n\n\n
Jest to metoda wywoływana jako pierwsza podczas tworzenia nowych obiektów. Jest wywoływana jeszcze przed najpopularniejszą z magicznych metod czyli __init__ i jej zadaniem jest stworzenie i zwrócenie nowej instancji danej klasy, która to zaraz potem jest przekazywana do metody __init__ jako pierwszy argument (self).
\n\n\n\n
__init__(self, ...)
\n\n\n\n
Tak jak wspomniałem powyżej to najbardziej znana z magicznych metod. Spotkał się z nią każdy kto ma jakiekolwiek doświadczenie z programowaniem obiektowym w Pythonie. Często nazywana jest konstruktorem, którym tak właściwie jest razem z metodą __new__. Jej zadaniem jest inicjalizacja obiektu z pomocą argumentów przekazanych przy wywołaniu klasy.
\n\n\n\n
__del__(self)
\n\n\n\n
Po drugiej stronie od powyższych metod jest metoda __del__. Wywoływana jest ona w momencie gdy obiekt kończy swój cykl życia i za zadanie ma zrobienie porządku po nim. Należy pamiętać, że metoda ta wywoływana jest gdy licznik referencji do obiektu spadnie do 0, co oznacza, że nie nastąpi to zawsze gdy wywołamy del x.
\n\n\n\n
Reprezentacja obiektów
\n\n\n\n
\n\n\n\n
__str__(self)
\n\n\n\n
Jest to metoda wywoływana przy konwersji naszego obiektu do stringa str(x) , oraz przy wywoływaniu funkcji print(x). Jeżeli ją implementujemy to powinna ona zwracać string z czytelną dla ludzi reprezentacją naszego obiektu.
\n\n\n\n
__repr__(self)
\n\n\n\n
Ta metoda natomiast powinna zwrócić oficjalną reprezentację, jej wynik możemy zobaczyć wywołując funkcję repr(), albo wpisując nazwę naszego obiektu w linii poleceń interpretera. Dobrą praktyką jest zwracanie poprawnego wyrażenia pozwalającego na ponowne stworzenie takiego samego obiektu.
\n\n\n\n
__hash__(self)
\n\n\n\n
Metoda __hash__ wykorzystywana jest do haszowania naszego obiektu, czyli zamiany jego wartości na wartość liczbową. Jej implementacja jest potrzebna gdy chcemy ustalić szczególne zachowanie naszych obiektów w roli kluczy słowników. Jeżeli nie chcemy by nasz obiekt był haszowalny, tym samym nie mógł być kluczem słownika, należy ustawić w klasie wartość __hash__ = None. To samo dzieje się automatycznie, również gdy w naszej klasie deklarujemy wyłącznie metodę __eq__. Jeżeli chcemy dostosować zachowanie obiektu jako klucz słownika (na przykład żeby brane pod uwagę było konkretne pole z obiektu) powinniśmy zaimplementować obie te metody.
\n\n\n\n
Konwersja typu
\n\n\n\n
Tę kategorię pozwolę sobie potraktować zbiorczo (bo chyba każdy domyśla się do czego służą metody __int__ i __float__) po to żeby szybko przejść do tych najciekawszych metod.
\n\n\n\n
A jakby ktoś jednak nie wiedział to metody te służą do konwersji na jeden z wbudowanych typów Pythona. I jedyne o czym należy pamiętać to to, żeby zwracały one wartość odpowiedniego typu.
\n\n\n\n
W tej kategorii ciekawostkę stanowi metoda __bool__ jej implementacja powinna zwracać wartości True albo False. Jeżeli jej nie zaimplementujemy to wartość wyrażenia bool(x) zostanie obliczona na podstawie wyniku metody __len__, jeżeli obie metody nie zostaną zaimplementowane to nasz obiekt zawsze będzie miał wartość True.
\n\n\n\n
Metody typowe dla kontenerów
\n\n\n\n
Chcąc stworzyć klasę, która będzie kontenerem w pojęciu Pythonowym, będziemy potrzebowali zaimplementować w niej kilka metod typowych dla kontenerów. Podstawowymi będą __len__ i __getitem__, dodatkowo gdy chcemy by nasze obiekty były mutowalne będziemy potrzebowali metod __setitem__ i __delitem__.
\n\n\n\n
__len__(self)
\n\n\n\n
Metoda potrzebna do obliczenia wartości przy wywołaniu len(x) dla naszego obiektu. Powinna zwracać liczbę całkowitą większa lub równą 0.
\n\n\n\n
__getitem__(self, key)
\n\n\n\n
Implementacja tej metody pozwala na wywołania typu x[klucz]. Dla sekwencji, czyli typów danych podobnych do listy czy tupli, klucz może mieć wartość liczby całkowitej (również ujemnej), oraz być obiektem typu slice - dla wywołań takich jak na przykład x[1:5], które zamienione zostanie na x[slice(1, 5, None)]. Warto pamiętać też o wyjątkach zwracanych gdy podany klucz jest niepoprawny. TypeError gdy klucz jest niewłaściwego typu (np. nie jest liczbą całkowita dla sekwencji), KeyError gdy klucz nie znajduje się w kontenerze (zwłaszcza dla typów mapujących, takich jak słownik), oraz IndexError gdy indeks jest spoza dostępnego zakresu. Ten ostatni wyjątek jest o tyle istotny, że jest wartością oczekiwaną przez pętlę for podczas iterowania po sekwencji.
\n\n\n\n
__setitem__(self, key, value)
\n\n\n\n
Metoda ta pozwala na operację przypisania do obiektu w kontenerze. Powinna być zaimplementowana jedynie wtedy gdy chcemy, by obiekty w naszym kontenerze miały możliwość podmieniania lub dodawania nowych dla typów mapujących. Pozostałe zalecania są identyczne jak w przypadku __getitem__.
\n\n\n\n
__delitem__(self, key)
\n\n\n\n
Metoda ta pozwala na operację usuwania obiektów w kontenerze. Zaimplementuj ją jedynie wtedy gdy chcesz, by obiekty wewnątrz były usuwalne poprzez del x[klucz]. Pozostałe zalecania są identyczne jak w przypadku __getitem__.
\n\n\n\n
Powyższe metody powinny zapewnić nam podstawowy interfejs dla obiektu typu kontener. Warto mieć w pamięci jednak również to, że porządny kontener zapewnia też interfejs w postaci kilku niemagicznych metod takich jak na przykład: append(), index(), insert(), keys(), pop(), values(). A kontenery z wyższej półki dają możliwość używania operatorów dodawania i mnożenia, ale o tym powiemy sobie za chwilę.
\n\n\n\n
Tworząc swój własny kontener danych warto też zajżeć do modułu collections.abc i wybrać jedną z dostępnych tam klas jako naszą klasę bazową.
\n\n\n\n
Wywoływanie obiektu
\n\n\n\n
\n\n\n\n
__call__(self[, args...])
\n\n\n\n
Metoda __call__ jest wywoływana gdy wywołujemy nasz obiekt tak jak funkcję. Stąd właśnie definiowane przez nas funkcje są tak na prawdę obiektem typu function, który posiada swoją główna funkcjonalność w metodzie __call__.
\n\n\n\n
class Foo:\n def init(self, pre, post):\n self.pre = pre\n self.post = post\n \n def call(self, text):\n print(self.pre + text + self.post)\n\n\nfoo_print = Foo(\"!!!! \", \" ?????\")\nfoo_print(\"Ala ma kota\")\n# !!!! Ala ma kota ?????
\n\n\n\n
Kontrola dostępu do obiektu
\n\n\n\n
Metody tej kategorii pozwalają nam na kontrolowanie dostępu do poszczególnych atrybutów naszych obiektów. Przed rozpoczęciem korzystania z poniższych metod watro zaprzyjaźnić się ze specjalnym atrybutem __dict__, który zawiera atrybuty obiektu, oraz mieć na uwadze, że niewłaściwe postepowanie z tymi metodami może doprowadzić do nieskończonej rekurencji.
\n\n\n\n
__getattr__(self, name)
\n\n\n\n
Jest to metoda, która jest wywoływana w momencie gdy standardowy dostęp do atrybutu rzuci wyjątek AttributeError. Dzięki tej metodzie możesz zwrócić obliczoną wartość atrybutu, który nie istnieje. Jeżeli chcesz ją zaimplementować ale nie zwracać żadnej wartości to rzuć w niej wyżej wspomniany wyjątek.
\n\n\n\n
__setattr__(self, name, value)
\n\n\n\n
Wywołanie tej metody następuje w momencie przypisania wartości do atrybutu obiektu. Może się ona przydać gdy np. chcemy zwalidować dane zapisywane do naszego obiektu.
\n\n\n\n
Jeżeli w tej metodzie będziesz zapisywać dane jako atrybut obiektu należy skorzystać z tej metody w klasie bazowej object: object.__setattr__(self, name, value)
\n\n\n\n
__delattr__(self, name)
\n\n\n\n
Podobnie jak w przypadku powyżej metoda ta zastępuje standardowy mechanizm. Implementujemy ją jeżeli chcemy mieć możliwość usuwania zmiennych wewnątrz obiektów w taki sposób: del obiekt.atrybut
\n\n\n\n
__getattribute__(self, name)
\n\n\n\n
Z tą metodą możesz mieć największe problemy. Jest ona wywoływana zawsze w przypadku dostępu do atrybutu. Przez co łatwo wpaść w nieskończoną pętle jej wywołania. Jeżeli chcesz w jej implementacji uzyskać wartość atrybutu to koniecznie zrób to korzystając z klasy bazowej object podobnie jak w przypadku __setattr__.
\n\n\n\n
Jednak przed jej implementacją warto zastanowić się czy to __getattr__ nie jest tą metodą, której potrzebujemy ;)
\n\n\n\n
class Bar:\n i = {}\n def setattr(self, name, value):\n if value > 0:\n object.setattr(self, name, value)\n else:\n self.i[name] = value\n def getattr(self, name):\n value = self.i[name]\n print(\"Value from i\")\n return value\n\nfoo = Bar()\nfoo.a = 14\nfoo.b = -14\nprint(foo.a)\n# 14\nprint(foo.b)\n# Value from i\n# -14
\n\n\n\n
Menadżer kontekstu
\n\n\n\n
O menadżerach kontekstu znajdziesz już jeden artykuł na moim blogu, który dokładnie wyjaśnia to zagadnienie (Jest on tutaj). Tutaj powiemy sobie jedynie o metodach, które zapewniają interfejs menadżera kontekstu i pozwalają na używanie definiowanych przez nas obiektów z wyrażeniem with.
\n\n\n\n
__enter__(self)
\n\n\n\n
Tutaj definiujemy co ma się stać na początku bloku with. Wartość zwrócona z metody __enter__ zostanie zapisana do zmiennej, której nazwę podajemy po słowie as.
\n\n\n\n
__exit__(self, exc_type, exc_value, traceback)
\n\n\n\n
W tej metodzie natomiast definiujemy co ma się stać po skończeniu wykonywania kodu z bloku with (również gdy zostanie rzucony wyjątek). W tej metodzie zazwyczaj będziemy sprzątać, zamykać zasoby i obsługiwać wyjątki, które wystąpiły. Jako argumenty przyjmuje ona informacje dotyczące wyjątku, jeżeli taki został rzucony. Jeżeli metoda __exit__ zwraca wartość True to jest to informacja dla Pythona, że wyjątki, które wystąpiły w bloku with zostały obsłużone.
Na koniec została już kategoria chyba najprostsza, jednak najbardziej obszerna. Co sprawia, że prawdopodobnie będzie potrzebowała najwięcej czasu na dokładne jej poznanie. Implementowanie metod z tej kategorii pozwala nam nadać specjalnego znaczenia dla operatorów w operacjach ze definiowanymi przez nas typami danych. W innych językach programowania często można spotkać się z pojęciem przeciążania operatorów, gdy wykonujemy operacje analogiczne do tych, o których mowa poniżej.
\n\n\n\n
Operatory porównania
\n\n\n\n
Obsługiwanych w Pythonie operatorów jest na tyle dużo, że możemy podzielić je na kilka kategorii. Pierwszą z nich są operatory porównania. W poniższej tabeli znajdziesz operatory i odpowiadające im metody.
\n\n\n\n
Metoda
Operator
__lt__(self, other)
<
__le__(self, other)
<=
__eq__(self, other)
==
__ne__(self, other)
!=
__gt__(self, other)
>
__ge__(self, other)
>=
\n\n\n\n
Operatory numeryczne
\n\n\n\n
Podobnie sprawa wygląda z operatorami numerycznymi, czyli takimi jak operator dodawania, dzielenia itp. W poniższej tabeli znajdziesz najpopularniejsze z nich.
\n\n\n\n
Metoda
Operator
__add__(self, other)
+
__sub__(self, other)
-
__mul__(self, other)
*
__truediv__(self, other)
/
__floordiv__(self, other)
//
__mod__(self, other)
%
__pow__(self, other)
**
\n\n\n\n
Powyższe operatory implementowane są dla lewego składnika działania. Oznacza to, że wywołując działanie x + y zostanie wywołana funkcja: x.__add__(y).
\n\n\n\n
r-operatory
\n\n\n\n
Jeżeli chcemy, żeby zaimplementowane przez nas np. dodawanie było naprzemienne, zaimplementujmy też odpowiednią metodę z kategorii r-operatorów. Może się to przydać zwłaszcza gdy chcemy obsługiwać wbudowane typy danych. Czyli pozwolić zarówno na operację x + 1 jak i na 1 + x.
\n\n\n\n
R-operatory tworzymy analogicznie do zwykłych operatorów numerycznych, dodając jedynie literkę \"r\" na początku. Oznacza to, że metodzie __add__(self, other) odpowiada __radd__(self, other) itd.
\n\n\n\n
W podobny sposób można obsłużyć operatory przypisania takie jak +=. W tym przypadku dodajemy literkę \"i\", Czyli dla wspomnianego operatora += mamy metodę __iadd__(self, other).
\n\n\n\n
Magiczne metody - co dalej?
\n\n\n\n
Mam nadzieję, że mimo dużej liczby opisanych tu metod uda Wam się opanować jak najwięcej z nich. A skoro dotrwaliście do końca i jesteście już tutaj, to dajcie znać w komentarzach czy zdarza Wam się używać magicznych metod. Jeżeli tak, to napiszcie jakich i w jakich przypadkach :)