2013-02-21 12 views
22

Dies ist eine Frage in Bezug auf die Best Practice zum Erstellen einer Instanz einer Klasse oder eines Typs aus verschiedenen Formen der gleichen Daten mit Python. Ist es besser, eine Klassenmethode zu verwenden, oder ist es besser, eine separate Funktion zu verwenden? Sagen wir, ich habe eine Klasse verwendet, um die Größe eines Dokuments zu beschreiben. (Hinweis: Dies ist nur ein Beispiel, das ich den besten Weg, wissen will, eine Instanz der Klasse zu erstellen, nicht den besten Weg, um die Größe eines Dokument zu beschreiben..)Factory-Methode für Python-Objekt - Best Practice

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 

Meine __init__ Methode nimmt einen Größenwert dargestellt in Bits (Bits und nur Bits und ich möchte es so behalten) aber lassen Sie uns sagen, ich habe einen Größenwert in Bytes und ich möchte eine Instanz meiner Klasse erstellen. Ist es besser, eine Klassenmethode zu verwenden, oder ist es besser, eine separate Funktion zu verwenden?

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) 

ODER

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

Dies kann nicht wie ein Problem scheinen und vielleicht beiden Beispiele sind gültig, aber ich denke darüber jedes Mal, wenn ich etwas implementieren muß. Seit langem bevorzuge ich den Ansatz der Klassenmethode, weil ich die organisatorischen Vorteile der Verknüpfung der Klasse und der Fabrikmethode mag. Außerdem wird durch die Verwendung einer Klassenmethode die Möglichkeit zum Erstellen von Instanzen aller Unterklassen beibehalten, sodass sie objektorientierter ist. Auf der anderen Seite hat ein Freund einmal gesagt: "Im Zweifelsfall mach, was die Standardbibliothek tut", und ich finde noch ein Beispiel dafür in der Standardbibliothek.

Jede Rückmeldung wird sehr geschätzt.

Prost

+0

Ich würde eine Münze werfen. Die meisten Python-Bibliotheken scheinen prozedurale APIs zu bevorzugen, aber das liegt daran, dass sie auf andere Weise als wiederverwendbarer Code konsumiert werden sollen, der in Ihrer Codebasis intern ist. – millimoose

+4

PS, nenne keine Variable 'bytes'; das ist ein eingebauter Typ (in 2.6 und später). – abarnert

+3

Und natürlich habe ich gerade gemerkt, dass ich in meiner Antwort den gleichen Fehler gemacht habe: 'Größe (Bytes = 20)'. Tun Sie nicht wie ich, tun Sie, was ich sage. :) – abarnert

Antwort

22

Zuerst Sie die meiste Zeit denken Sie so etwas wie dieses benötigen, können Sie dies nicht tun; Es ist ein Zeichen, dass Sie versuchen, Python wie Java zu behandeln, und die Lösung ist, zurückzutreten und zu fragen, warum Sie eine Fabrik benötigen.

Oft ist es am einfachsten, nur einen Konstruktor mit default/optional/Schlüsselwortargumenten zu haben. Selbst Fälle, in denen Sie niemals in Java schreiben würden - selbst wenn sich überlastete Konstruktoren in C++ oder ObjC falsch fühlen würden - könnten in Python vollkommen natürlich aussehen. Zum Beispiel, size = Size(bytes=20) oder size = Size(20, Size.BYTES) sehen vernünftig aus. In der Tat, eine Bytes(20) Klasse, die von Size erbt und fügt absolut nichts als eine __init__ Überlastung sieht vernünftig. Und diese sind trivial zu definieren:

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

Oder:

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

Aber manchmal Sie Notwendigkeit Fabrik Funktionen tun. Also, was machst du dann? Nun, es gibt zwei Arten von Dingen, die oft zu "Fabriken" zusammengefasst werden.

A @classmethod der idiomatische Weg ist es, einen „alternativen Konstruktor“ -en sind Beispiele der ganzen stdlib- itertools.chain.from_iterable, datetime.datetime.fromordinal usw.

Eine Funktion ist die idiomatische Weise zu tun, eine zu tun „Ich weiß nicht egal was die eigentliche Klasse ist "Fabrik. Sehen Sie sich beispielsweise die eingebaute open-Funktion an. Weißt du, was es in 3.3 zurückgibt? Kümmert es dich? Nee. Deshalb ist es eine Funktion, nicht io.TextIOWrapper.open oder was auch immer.

Ihr gegebenes Beispiel scheint ein vollkommen legitimer Anwendungsfall zu sein und passt ziemlich klar in den "alternate constructor" Bin (wenn es nicht in den "Konstruktor mit zusätzlichen Argumenten" passt).

+2

Während ich damit einverstanden bin - es ist nur fair, darauf hinzuweisen, dass eine andere Design-Wahl statt einer @ class Methode - ist "__init __ (kilobytes = 345)" etc ... –

+0

@ JonClements: Guter Punkt - obwohl ich das wirklich denke passt in den ersten Satz, der definitiv nicht klar ist wie geschrieben, also werde ich es bearbeiten. – abarnert

+0

Yup - was ich für diesen Anwendungsfall dachte: http://dpaste.com/958194/ (außer dass es n * base * hust * sein sollte) –