2013-02-21 14 views
22

To jest pytanie dotyczące najlepszej praktyki tworzenia instancji klasy lub typu z różnych form tych samych danych przy użyciu Pythona. Czy lepiej jest używać metody klasy lub czy lepiej jest używać osobnej funkcji? Powiedzmy, że mam klasę używaną do opisu rozmiaru dokumentu. (Uwaga: To jest po prostu przykładem Chcę wiedzieć, że najlepszym sposobem, aby utworzyć instancję klasy nie jest najlepszym sposobem na opisanie rozmiaru dokumentu..)Metoda fabryki dla obiektu Pythona - najlepsza praktyka

class Size(object): 
    """ 
    Utility object used to describe the size of a document. 
    """ 

    BYTE = 8 
    KILO = 1024 

    def __init__(self, bits): 
     self._bits = bits 

    @property 
    def bits(self): 
     return float(self._bits) 

    @property 
    def bytes(self): 
     return self.bits/self.BYTE 

    @property 
    def kilobits(self): 
     return self.bits/self.KILO 

    @property 
    def kilobytes(self): 
     return self.bytes/self.KILO 

    @property 
    def megabits(self): 
     return self.kilobits/self.KILO 

    @property 
    def megabytes(self): 
     return self.kilobytes/self.KILO 

Moja __init__ metoda przyjmuje wartość rozmiarze reprezentowane w bitach (bitach i tylko bitach i chcę zachować to w ten sposób), ale powiedzmy, że mam wielkość w bajtach i chcę utworzyć instancję mojej klasy. Czy lepiej jest używać metody klasy lub czy lepiej jest używać osobnej funkcji?

class Size(object): 
    """ 
    Utility object used to describe the size of a document. 
    """ 

    BYTE = 8 
    KILO = 1024 

    @classmethod 
    def from_bytes(cls, bytes): 
     bits = bytes * cls.BYTE 
     return cls(bits) 

LUB

def create_instance_from_bytes(bytes): 
    bits = bytes * Size.BYTE 
    return Size(bits) 

To może nie wydawać się problem, a może oba przykłady są ważne, ale myślę o tym za każdym razem, kiedy trzeba zaimplementować coś takiego. Od dłuższego czasu preferuję podejście oparte na metodzie klasowej, ponieważ podoba mi się korzyści organizacyjne związane z powiązaniem klasy i metody fabryki. Ponadto użycie metody klasy zachowuje możliwość tworzenia wystąpień dowolnych podklas, dzięki czemu jest bardziej zorientowana na obiekt. Z drugiej strony znajomy powiedział kiedyś: "Kiedy masz wątpliwości, rób to, co robi standardowa biblioteka", a jeszcze nie znalazłem tego przykładu w standardowej bibliotece.

Wszelkie uwagi są mile widziane.

Cheers

+0

Rzuciłbym monetą. Większość bibliotek Pythona wydaje się preferować interfejsy API proceduralne, ale to dlatego, że są one przeznaczone do spożywania w inny sposób niż wielokrotnego użytku, który jest wewnątrz kodu. – millimoose

+4

PS, nie wywołuj zmiennej 'bytes'; jest to typ wbudowany (w wersji 2.6 i późniejszych). – abarnert

+3

I oczywiście właśnie zdałem sobie sprawę, że popełniłem ten sam błąd w mojej odpowiedzi: "Rozmiar (bajty = 20)". Nie rób tak jak ja, rób tak, jak mówię. :) – abarnert

Odpowiedz

22

Po pierwsze, większość czasu uważasz, że potrzebujesz czegoś takiego, nie robić; to znak, że próbujesz traktować Pythona jak Javę, a rozwiązaniem jest cofnięcie się i pytanie, dlaczego potrzebujesz fabryki.

Często najprostszą rzeczą do zrobienia jest posiadanie konstruktora z argumentami defaulted/optional/keyword. Nawet przypadki, w których nigdy nie pisałeś w ten sposób w Javie - nawet w przypadkach, gdy przeciążone konstruktory źle by się czuły w C++ lub ObjC - mogą wyglądać zupełnie naturalnie w Pythonie. Na przykład: size = Size(bytes=20) lub size = Size(20, Size.BYTES) wyglądają rozsądnie. W tym przypadku klasa Bytes(20), która dziedziczy po Size i dodaje absolutnie nic poza przeciążeniem __init__, wydaje się być uzasadniona. A te są trywialne do zdefiniowania:

def __init__(self, *, bits=None, bytes=None, kilobits=None, kilobytes=None): 

Lub:

BITS, BYTES, KILOBITS, KILOBYTES = 1, 8, 1024, 8192 # or object(), object(), object(), object() 
def __init__(self, count, unit=Size.BITS): 

Ale czasami zrobić funkcje fabryczne potrzeba. Co więc robisz? Cóż, istnieją dwa rodzaje rzeczy, które często łączą się w "fabryki".

@classmethod jest idiomatyczne sposób zrobić „alternate konstruktor” -Są przykłady całego stdlib- itertools.chain.from_iterable, datetime.datetime.fromordinal itp

Funkcja jest idiomatyczne sposób zrobić „ja nie zadbaj o to, jaka jest rzeczywista klasa "fabryka. Spójrz na, na przykład, wbudowaną funkcję open. Czy wiesz, co zwraca w 3.3? Czy ci zależy? Nie. Dlatego jest to funkcja, a nie jakaś inna.

Podany przykład wydaje się być całkowicie uzasadnionym przypadkiem użycia i całkiem dobrze pasuje do koszyka "alternate constructor" (jeśli nie mieści się w "konstruktorze z dodatkowymi argumentami").

+2

Zgadzam się z tym - wystarczy tylko zaznaczyć, że inny wybór projektu zamiast metody @classmethod to "__init __ (kilobajty = 345)" itd. ... –

+0

@JonClements: Dobra uwaga - choć myślę, że tak naprawdę pasuje do pierwszego zdania, które zdecydowanie nie jest jasne, jak zostało napisane, więc zmienię je. – abarnert

+0

Yup - co myślałem o tym przypadku użycia: http://dpaste.com/958194/ (z wyjątkiem tego, że powinno być n * base * kaszel *) –

Powiązane problemy