Friday, December 26, 2008

Python - gdy Gruszka jest Pomarańczą

W tym wpisie chciałbym przybliżyć działanie metody __new__(). Jest to metoda statyczna wywoływana w trakcie tworzenia obiektu, zanim sam obiekt jeszcze powstanie. Jej zadaniem jest właśnie zwórcenie obiektu. Czyli co ? Co to dla nas oznacza ? Oznacza na przykład to iż istnieje metoda, którą mogę zaimplementować (coś w niej zrobić) wywoływana zanim jeszcze istnieje obiekt. Mogę więc wpłynąć na to jak ten obiekt będzie na prawdę tworzony i co więcej ! To ja mogę zdecydować jaki obiekt będzie stworzony i zwrócony.Tworząc obiekt jednego typu mogę zwrócić obiekt innego ? Na przykład. Możesz przekazać troszkę inną listę parametrów jego metodzie __init__ albo zainicjalizować jakieś elementy przed jego stworzeniem. Zobaczymy przykład.
class Pomarancz(object):
    pass

class Gruszka(object):

def __new__(type):
    return Pomarancz()

if __name__ == '__main__':
    gruszka = Gruszka()
    print type(gruszka)
Co tu się dzieje. Stworzyliśmy sobie dwie klasy: Pomarancz i Gruszka. Gruszka jest jakaś podejżana, ale to zaraz do tego wrócimy. Co w ramach programu ? Tworzomy obiekt gruszka klasy Gruszka. Na razie na tym się zatrzymajmy. W każdym "normalnym" języku programowania gdy tworzymy obiekt jakiejś klasy dostajemy ... tak instancję tej klasy :) Jak tworzymy obiekt klasy Samolot dostajemy samolot. Jak dostajemy obiekt klasy Dziecko dostajemy dziecko. Tak samo na codzień zachowuje się Python możemy jednak, dzięki metodzie __new__ zwrócić podczas tworzenia obiektu "coś innego".Sprawdźmy co my zwróciliśmy podczas tworzenia Gruszki. print type(gruszka) (funkcja type podaje typ obiektu, klasę obiektu który jest jej parametrem) zwróciło nam pomarańcz Pomarancz ? Wow :D To prawie jak kamień alchemiczny. Teraz zamieniamy Gruszki w Pomarańcze (to jak Ołów w Złoto).

Przyjżyjmy się metodzie __new__(). Skoro to ona ma zwracać obiekt klasy :) to znaczy, że to co zwróci nam przez return to dostaniemy przy użyciu Gruszka(). Jak byk stoi tam zwrócenie świerzo stworzonego obiektu pomarańczy :) Teraz wszystko się zgadza.

Już słyszę głosy oburzonych programistów: "przecież Python miał być czytelny. A tu takie konstrukcje ?". Z mojego skromnego punktu widzenia służą one językowi. Jak ?
Wyobraź sobie, że chciałbyś mieć klasę UlubionyOwoc, która w zależności do tego jaki użytkownik tworzy jej instancję dostaje to co lubi najbardziej. Dzięki metodzie __new__() możesz tego w prosty sposób dokonać nie tracąc przy tym na elegancji składni i prostocie kodu (po prostu tworzysz obiekt), gdzie bez takiego rozwiązania musiałbyś tworzyć jakąś metodę statyczną w stylu UlubionyOwoc.zwroc_owoc().

Teraz jeszcze kilka szczegółów. W naszym przykładzie jak tylko interpreter natrafi na instrukcję return Pomarancz() zacznie tworzyć instancję klasy Pomarańcz. Dla nas oznacza to iż zostanie wywołane Pomarancz.__new__(), które jest domyśle a po nim Pomarancz.__init__() :) Być może dla wielu z was to już jest oczywiste :] ale piszę.

A co z argumentami, które są przekazywane podczas tworzenia obiektu. Powiedzmy Gruszka(2, jasio='imie') ? Metoda w postaci
def __new__(type, *args, **kwargs)
Pytania może rodzić kwestia: "No dobrze. A jak w metodzie __new__ klasy gruszka zwrócić obiekt klasy Gruszka ? Przecież jak zapiszę Gruszka() znowu wyląduję w __new__ i zacznę kręcić się w kółko". Słuszna uwaga. Uniwersalna metoda to:
def __new__(type, *args, **kwargs):
    return object.__new__(type, *args, **kwargs)

Gdzie type oznacza typ klasy, który chcemy "stworzyć". Przy użyciu formy Gruszka() czy Pomarancz() jest on przekazywany automatycznie jednak można tworzyć obiekty również tak object.__new__(tutaj_podaj_typ, argumenty, argumenty nazwane).

Na koniec dodam tylko, że jak już nasza klasa zwróci obiekt swojego typu to wywoływana jest metoda Gruszka.__init__ z całym kompletem parametrów przekazanym do __new__ czyli przekazanym podczas tworzenia obiektu :)

I to by było na tyle. Miłej zabawy.

3 comments:

Secator said...

A tak z ciekawości... poco to?

(Rozumiem zamieniać ołów w złoto (obv) ale gruszki w pomarańcze? Ja lubie gruszki :]:])

Johny JKJK said...

Pisałem. Nie musisz tworzyć brzydkich metod statycznych :]

Zobacz jak elegancko dzięki temu wygląda w Pythonie singletone: http://dziubdziub.blogspot.com/2008/10/python-singletone.html

Inny przykład zastosowania masz np. w poradniku do Pylons w modelu User.

Secator said...

Ano tak, nie skojarzyłem z tym singletonem :] thx!