2013-02-21 29 views
22

Questa è una domanda riguardante la procedura migliore per creare un'istanza di una classe o un tipo da forme diverse degli stessi dati utilizzando python. È meglio usare un metodo di classe o è meglio usare una funzione separata del tutto? Diciamo che ho una classe usata per descrivere la dimensione di un documento. (Nota: questo è semplicemente un esempio vorrei sapere il modo migliore per creare un'istanza della classe non il modo migliore per descrivere le dimensioni di un documento..)Metodo factory per oggetto python - 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 

Il mio metodo __init__ assume un valore di dimensione rappresentato in bit (bit e solo bit e voglio mantenerlo in questo modo) ma diciamo che ho un valore di dimensione in byte e voglio creare un'istanza della mia classe. È meglio usare un metodo di classe o è meglio usare una funzione separata del tutto?

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) 

O

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

Questo può non sembrare un problema e forse entrambi gli esempi sono validi, ma ci penso ogni volta che ho bisogno di implementare qualcosa di simile. Per molto tempo ho preferito l'approccio al metodo di classe perché mi piacciono i vantaggi organizzativi legati alla combinazione tra classe e metodo di produzione. Inoltre, l'uso di un metodo di classe preserva la possibilità di creare istanze di qualsiasi sottoclasse in modo che sia più orientato agli oggetti. D'altra parte, un amico una volta ha detto "In caso di dubbio, fa quello che fa la libreria standard" e io devo ancora trovare un esempio di questo nella libreria standard.

Qualsiasi feedback è molto apprezzato.

Acclamazioni

+0

Vorrei lanciare una moneta. La maggior parte delle librerie python sembra preferire le API procedurali, ma è perché sono pensate per essere consumate in un modo diverso dal codice riutilizzabile interno alla base di codice. – millimoose

+4

PS, non chiamare una variabile 'byte'; questo è un tipo predefinito (in 2.6 e versioni successive). – abarnert

+3

E ovviamente mi sono appena accorto di aver commesso lo stesso errore nella mia risposta: 'Size (bytes = 20)'. Non fare come faccio io, faccio come dico io. :) – abarnert

risposta

22

In primo luogo, il più delle volte pensi di aver bisogno qualcosa come questo, non è necessario; è un segno che stai cercando di trattare Python come Java, e la soluzione è di fare un passo indietro e chiedere perché hai bisogno di una fabbrica.

Spesso, la cosa più semplice da fare è semplicemente avere un costruttore con argomenti predefiniti/facoltativi/parola chiave. Persino i casi in cui non si scriverà mai in questo modo in Java - anche i casi in cui i costruttori sovraccaricati si sentirebbero in errore in C++ o ObjC - potrebbero sembrare perfettamente naturali in Python. Ad esempio, size = Size(bytes=20) o size = Size(20, Size.BYTES) sembrano ragionevoli. Del resto, una classe Bytes(20) che eredita da Size e aggiunge nient'altro che un sovraccarico di __init__ sembra ragionevole. E questi sono banali per definire:

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

Oppure:

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

Ma, a volte si fa funzioni necessità di fabbrica. Allora, cosa fai allora? Bene, ci sono due tipi di cose che sono spesso raggruppate in "fabbriche".

A @classmethod è il modo idiomatica per fare un "costruttore alternate" -ci sono esempi in tutto il stdlib- itertools.chain.from_iterable, datetime.datetime.fromordinal, ecc

Una funzione è la via idiomatica per fare un "io no cura quale sia la vera classe "fabbrica". Esamina, ad esempio, la funzione integrata open. Sai cosa restituisce in 3.3? Ti importa? No. Ecco perché è una funzione, non io.TextIOWrapper.open o qualsiasi altra cosa.

Il tuo esempio dato sembra un caso d'uso perfettamente legittimo e si inserisce abbastanza chiaramente nel cestino "costruttore alternativo" (se non rientra nel bin "costruttore con argomenti aggiuntivi").

+2

Mentre sono d'accordo con questo - è giusto mettere in evidenza che un'altra scelta di design invece di un @classmethod - è rendere '__init __ (kilobytes = 345)' ecc ... –

+0

@JonClements: buon punto-anche se penso che si adatta alla prima frase, che sicuramente non è chiara come scritta, quindi la modificherò. – abarnert

+0

Sì, quello che stavo pensando per questo caso d'uso: http://dpaste.com/958194/ (eccetto che dovrebbe essere n * base * cough *) –