Trzy rodzaje metod w Pythonie

2011-10-03 23:05

Obiekty mają metody. Tak, w tym stwierdzeniu nie należy doszukiwać głębokiego sensu – jest ono po prostu prawdziwe :) Gdy mówimy o metodach obiektów czy też klas, zwykle mamy jednak na myśli tylko jeden ich rodzaj: metody instancyjne. W wielu językach programowania nie jest to aczkolwiek ich jedyny rodzaj – z takim przypadkiem mamy do czynienia chociażby w Pythonie.

Jak zawsze metody instancyjne są domyślnym typem, który tutaj jest dodatkowo zaznaczony obecnością specjalnego parametru – self – występującego zawsze jako pierwszy argument. To odpowiednik this z języków C++, C# czy Java i reprezentuje instancję obiektu, na rzecz której wywoływana jest metoda:

  1. class Counter(object):
  2.     def __init__(self, value = 0):
  3.         self._value = value
  4.     def increment(self, by = 1):
  5.         self._value += by

Fakt, że musi być on jawnie przekazany, wynika z zasad tworzenia zmiennych w Pythonie. Nie muszą być one jawnie deklarowane. Dlatego też odwołanie do pola obiektu jest zawsze kwalifikowane, gdyż przypisanie do _value zamiast self._value stworzyłoby po prostu zmienną lokalną.

Istnieją jednak takie metody, które nie operują na konkretnej instancji klasy. Typowo nazywa się je statycznymi. W Pythonie nie posiadają one parametru self, lecz są opatrzone dekoratorem @staticmethod:

  1. @staticmethod
  2. def format_string():
  3.     return "%d"

Statyczną metodę można wywołać zarówno przy pomocy nazwy klasy (Counter.format_string()), jak i jej obiektu (Counter().format_string()), ale w obu przypadkach rezultat będzie ten sam. Technicznie jest to bowiem zwyczajna funkcja umieszczona po prostu w zasięgu klasy zamiast zasięgu globalnego.

Mamy wreszcie trzeci typ, mieszczący się w pewnym sensie pomiędzy dwoma opisanymi powyżej. Nie wydaje mi się jednak, żeby występował on w żadnym innym, popularnym języku. Chodzi o metody klasowe (class methods). Nazywają się tak, bo są wywoływane na rzecz całej klasy (a nie jakiejś jej instancji) i przyjmują ową klasę jako swój pierwszy parametr. (Argument ten jest często nazywany cls, ale jest to o wiele słabsza konwencja niż ta dotycząca self).
W celu odróżnienia od innych rodzajów, metody klasowe oznaczone są dekoratorem @classmethod:

  1. @classmethod
  2. def from_other(cls, counter):
  3.     return cls(counter._value)

Podobnie jak metody statyczne, można je wywoływać na dwa sposoby – przy pomocy klasy lub obiektu – ale w obu przypadkach do cls trafi wyłącznie klasa. Tutaj akurat będzie to Counter, lecz w ogólności może to być także klasa pochodna:

  1. class BoundedCounter(Counter):
  2.     MAX = 100
  3.  
  4.     def __init__(self, value = 0):
  5.         if value > self.MAX:
  6.             raise ValueError, "Initial value cannot exceed maximum"
  7.         super(BoundedCounter, self).__init__(value)
  8.  
  9.     def increment(self, by = 1):
  10.         super(BoundedCounter, self).increment(by)
  11.         self._value = min(self._value, self.MAX)
  12.  
  13.     @classmethod
  14.     def from_other(cls, counter):
  15.         value = min(counter._value, cls.MAX)
  16.         return cls(value)

Powyższy kod – będący przykładem dodatkowego sposobu inicjalizacji obiektu – to dość typowy przypadek użycia metod klasowych. Korzystanie z nich wymaga aczkolwiek nieco wprawy w operowaniu pojęciami instancji klasy i samej klasy oraz ich poprawnego rozróżniania.
W gruncie rzeczy nie jest to jednak nic trudnego.

Tags: , , ,
Author: Xion, posted under Programming »


4 comments for post “Trzy rodzaje metod w Pythonie”.
  1. SebaS86:
    October 4th, 2011 o 18:32

    Trzeci typ funkcji wydaje mi się przerostem formy nad treścią, różni się to czymś od zwykłej metody statycznej z jawnym podaniem nazwy klasy? Lub inaczej, daje to jeszcze jakieś inne ciekawsze opcje?

  2. Assa:
    October 4th, 2011 o 20:00

    Metody klasowe występują w prawie każdym języku dynamicznym.
    Na pewno mają je języki Scala i Ruby.

  3. Xion:
    October 6th, 2011 o 10:15

    @SebaS86: Praktyczny przykład będzie tu dobry, aczkolwiek będzie wymagał trochę dodatkowego info.

    Oto w Google App Engine mamy rodzaj obiektowej bazy danych, do której pythonowy interfejs wygląda mniej więcej tak:

    1. from google.appengine.ext import db
    2.  
    3. class Person(db.Model):
    4.     first_name = db.StringProperty()
    5.     last_name = db.StringProperty()
    6.  
    7. persons_named_jack = Person.all().filter('first_name', 'Jack')

    Zamiast SQL mamy więc wywołania metod klasowych (Person.all), które w środku przekładają się na tworzenie obiektu klasy db.Query. Jeśli teraz chcemy zrobić trochę sprytniejszą wersję funkcji all, która implementuje typowy wzorzec “flagi dla obiektu usuniętego” dla naszych encji, to możemy to zrobić tak:

    1. class BaseModel(db.Model):
    2.     active = db.BooleanProperty(default = True)
    3.  
    4.     @classmethod
    5.     def all(cls, active_only=True, keys_only=False):
    6.         query = db.Query(cls, keys_only = keys_only)
    7.         if active_only:
    8.             query = query.filter('active', True)
    9.         return query
    10. # keys_only to szczegół App Engine'owego modelu bazy danych, nieważny tutaj

    W ten sposób wszystkie modele dziedziczące z BaseModel będą automatycznie traktować swoje obiekty z flagą active == False jako logicznie usunięte. W przypadku metody statycznej nie byłoby to możliwe bez ponownego implementowania metody all w każdej klasie pochodnej.

    (Disclaimer: kod pisałem w zasadzie z pamięci, więc nie ręczę za poprawność)

  4. SebaS86:
    October 10th, 2011 o 1:19

    Czyli w skrócie taki polimorfizm na poziomie klasy i statyczne funkcje wirtualne?

    Obj-C umożliwa zrobienie czegoś takiego równie łatwo, nie ma oczywiście funkcji klasowej jako takiej, można uzyskać jednak ten sam efekt korzystając ze zwykłych metod statycznych, podstawowych reguł przesłaniania, wykorzystując dodatkowo metody class, self i super.

    W sumie chyba kiedyś brakowało mi tego w C++ nie pamiętam jednak do czego… niestety tam utrudnieniem jest dość słabo rozwinięte RTTI.

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.