Jakiś czas temu napisałem artykuł o setterach i getterach w PHP5. Może warto poruszyć podobny temat dotyczący Pythona? :)

Przede wszystkim, model obiektowy Pythona jest zupełnie inny niż PHP. Nie będę go tu omawiał, ponieważ to temat na dość obszerną książkę :) Generalnie obiektówka Pythona jest pełniejsza, ma większe możliwości przeciążania zarówno metod, jak i operatorów (których w PHP przeciążyć nie można).

Podstawą do pisania własnych setterów i getterów są w Pythonie dwie metody magiczne: __getattr__() i __setattr__(). Istnieje także metoda __delattr__() służąca do implementacji usuwania konkretnego atrybutu. Dobra, koniec smęcenia, może jakiś przykład:

class Test (object):
  x = 1
  def __init__ (self):
    super (Test, self).__setattr__ ('data', dict ())
  def __setattr__ (self, var, value):
    self.data[var] = value
  def __getattr__ (self, var):
    return self.data[var]

Wtrącę jednak małe omówienie obiektówki Pythonowej: w Pythonie 2.x są dwa modele klas: nowy i stary. Deklaracja klasy nowego typu oznacza jawne dziedziczenie z obiektu object, co zobrazowane jest w linii pierwszej (zalecane jest korzystanie z nowego typu klas).

Inną ciekawostką jest deklarowanie właściwości obiektu (które są współdzielone między instancjami - zmiana jej wartości w jednej instancji powoduje też jej zmianę w drugiej instancji) i właściwości instancji (które są indywidualne dla każdej instancji): pierwsze deklaruje się w ciele klasy, co pokazane jest w linii drugiej. Drugie “dodaje się” w konstruktorze czy też dowolnej innej metodzie (z konwencji robi się to w konstruktorze, który w Pythonie nazywa się __init__ ()). W naszym przykładzie sytuacja się nieco komplikuje, jako że my nadpisujemy wbudowaną metodą __setattr__ (). Aby dodać sobie właściwość instancji, musimy skorzystać z metody __setattr__ () superklasy klasy Test (czyli object), co pokazuje nam linia czwarta. W linii tej deklarujemy sobie jakiś słownik (w PHP nazywa się tą strukturę tablicą asocjacyjną), w którym będziemy przechowywać nasze dane. Tutaj występuje pewna różnica w stosunku do PHP, gdzie metoda __set wykonywana jest w momencie gdy właściwość którą próbujemy ustawić nie jest zadeklarowana w obiekcie, lub gdy jest do niej ograniczony dostęp za pomocą akcesora private. W Pythonie metoda __setattr__ () używana jest przy dostępie do jakiejkolwiek właściwości (zadeklarowanej wcześniej lub też nie). Jeśli sam sobie w definicji __setattr__ () nie zapewnisz dostępu do jakiejś właściwości, to nie będziesz jej miał. Trzeba pamiętać przy tym, że sama metoda __setattr__ () nie korzysta z samej siebie, czyli wewnątrz niej mamy dostęp do zadeklarowanych wcześniej właściwości.

Wróćmy do przykładu. Nasz setter dodaje do słownika data (lub zmienia wartość) klucz var, przypisując mu wartośc value. Getter po prostu zwraca szukaną właściwość, lub też, jeśli nie została jeszcze zdefiniowana, rzuci wyjątkiem (przydałoby się przechwycenie go i rzucenie właściwego wyjątku, ale to już inna kwestia).

A co jeśli trzeba dorobić sprawdzanie poprawności niektórych własności? No cóż, trzeba to zrobić podobnie jak w PHP, czyli zadeklarować dla nich konkretne metody sprawdzające.

class Test2 (object):
  def __init__ (self):
    super (Test2, self).__setattr__ ('data', dict ())
  def __setattr__ (self, var, value):
    if hasattr (self, '_check__' + var):
      self.data[var] = getattr (self, '_check__' + var) (value):
    self.data[var] = value
  def __getattr__ (self, var):
    return self.data[var]
  def _check__X (self, value):
    if isinstance (value, int) or (isinstance (value, str) and value.isdigit ()):
      return int (value)
    raise ValueError (u'Podana wartość nie jest liczbą!')

Ten kod pozwoli na przypisanie dowolnej wartości wszystkim własnościwościom poza X, której jedyną możliwą wartością do przypisania jest integer, lub napis zawierający same cyfry. W przypadku podania innej wartości zostanie rzucony wyjątek ValueError.

Powyżej została opisana jedna z możliwości stosowania własnych setterów i getterów. W Pythonie 2.2 została dodana jeszcze jedna możliwość, ta bardziej lubiana przeze mnie: funkcja (którą od wersji Pythona 2.4 można użyć jako dekorator) property (). Funkcja ta bierze od jednego do czterech parametrów. Pierwszym z nich jest metoda pełniąca funkcję gettera, druga - settera, trzecia - funkcji usuwającej właściwość, a czwarta - napis dokumentujący. Jeden ze sposobów użycia:

class Test3 (object):
  def __init__ (self):
    self.data = dict ()
  def _set__X (self, value):
    if isinstance (value, int) or (isinstance (value, str) and value.isdigit ()):
      self.data['X'] = int (value)
      return
    raise ValueError (u'Niewłaściwa wartość')
  def _get__X (self):
    return self.data['X']
  X = property (_get__X, _set__X)

Powyższy przykład robi dokładnie to samo co wcześniejszy Test2, z wyjątkiem umieszczania wszystkich własności w słowniku data. Różnice widoczne sa tylko w implementacji, dla użytkownika klasy różnice nie występują.

Funkcja property pozwala także na stworzenie atrybutu tylko do odczytu:

class Test4 (object):
  def __init__ (self, x):
    self.__x = x
  @property
  def x (self):
    return self.__x
t = Test4 (5)
print t.x
t.x = 6

Wykonanie kodu z ostatniego przykładu spowoduje rzucenie wyjątku AttributeError.


Liczba komentarzy: 2

  1. 1 Był piątek, 21 marzec 2008 roku gdy o godzinie 03:12 przyszedł Greensky i stwierdził:

    standardowy smaczek:
    self.dupa[var] = value

    z pierwszego przykładu tak dla jasności :)

  2. 2 Był piątek, 21 marzec 2008 roku gdy o godzinie 07:16 przyszedł MySZ i stwierdził:

    @Greensky: Dzięki, już poprawiłem ;) Pierwotny przykład był robiony kilka dni temu, i później zapomniałem poprawić ;)

A Ty? Co o tym myślisz?

Możesz używać w komentarzach następujących znaczników:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Sblam! Antyspam