E 'davvero corretto dichiarare tutti gli attributi di istanza in __init__

2

Secondo Pycharm (e quindi assumo secondo PEP) dovrei dichiarare tutti gli attributi di istanza direttamente all'interno di __init__ . Nel mio caso non mi sembra adatto. Ho qualcosa come:

class Handler(object):
    def __init__(self):
        self.start_index = # parse
        # Do stuff

    def parse(self):
        parse_header()
        # Do stuff
        parse_body()

    def parse_header():
        raise NotImplementedError

    def parse_body():
        raise NotImplementedError


class StringHandler(Handler):
    def parse_header():
        self.string_index = # parse
        self.string_count = # parse
        self.style_index = # parse
        self.style_count = # parse
        # More here

    def parse_body():
        self.strings = # parse
        self.styles = # parse
        # More here


class IntHandler(Handler):
    def parse_header():
        self.int_index = # parse
        self.int_count = # parse
        # More here

    def parse_body():
        self.ints = # parse
        # More here

Dato che ci sono oggetti (molti di questi) che derivano tutti dalla classe Handler , la struttura di ogni oggetto è abbastanza ovvia per chiunque legga il codice. Per me sembra un inutile ingombramento per dichiarare __init__ solo per il fatto di chiamare genitore __init__ e dichiarare un paio di parametri come None . Solitamente le funzioni parse -ing analizzano solo ciò che è necessario e lo memorizzano negli attributi, aggiungendo così __init__ a volte raddoppierà quasi la dimensione della classe (in termini di codice).

modifica: ho cercato questo problema, ma non ho trovato risposta adatta, ad esempio durante la ricerca di stackexcahnge, le domande erano troppo ampie per ottenere una risposta utile ( Come faresti a sapere se hai scritto un codice leggibile e facilmente gestibile? ) o ponendo domande diverse ( È buona pratica dichiarare variabili di istanza come None in una classe in Python? )

    
posta Drecker 24.09.2017 - 09:24
fonte

2 risposte

12

Sì, dovresti assegnare tutti gli attributi del tuo oggetto nel metodo __init__ .

Il motivo più importante per farlo è il supporto degli strumenti. Molti IDE Python possono vedere le assegnazioni in __init__ e utilizzare tali attributi per il completamento automatico basato su tipo. Sphinx-doc può elencare quegli attributi nella documentazione di riferimento.

C'è anche una ragione più fondamentale: semplice è buono, quindi non creare oggetti complicati che cambiano forma durante l'esecuzione. Data un'istanza StringHandler , potrei o non riuscirei a leggere l'attributo handler.strings . Se provo che prima è stato eseguito handler.parse() , otterrò un AttributeError . Successivamente otterrei il valore. Per evitare eccezioni, i consumatori della tua classe dovrebbero getattr(handler, 'strings', None) con un valore predefinito che è piuttosto noioso.

In generale, i costruttori dovrebbero creare oggetti completamente inizializzati che siano immediatamente utilizzabili. In alcuni casi è necessario creare oggetti mutabili con stato vuoto, predefinito e transizione successiva a uno stato in cui contengono un valore. Tuttavia, questa dovrebbe essere l'eccezione ed è raramente un buon design. Vedi anche I metodi init () un codice olfattivo? e Start Method vs. Impostazione di tutto nel costruttore che discute questo problema per Java.

Nel tuo caso il metodo parse() è un metodo di inizializzazione a due fasi che deve essere chiamato prima che l'oggetto possa essere realmente utilizzato. Questo solleva le domande:

  • Qual è il punto di questi oggetti, se sono solo un contenitore per tutti i valori prodotti dalla funzione parse() ? Mentre li stai usando, potresti anche usare un dict invece di una classe.

  • È un Handler vuoto significativo in alcun modo?

  • Perché l'inizializzazione in parse() è separata dall'altra inizializzazione?

  • O stai utilizzando questi attributi solo per la comunicazione tra le funzioni parse_x , non per i consumatori esterni?

Possibili progetti alternativi:

  • I gestori specificano come deve essere analizzato un campo, ma in realtà non contengono il valore. Il metodo parse restituisce il valore. Poiché i gestori non contengono dati, potrebbe non essere necessario utilizzare le classi per questo scopo: le semplici funzioni potrebbero essere sufficienti.

  • I gestori rappresentano un valore analizzato. Non esiste uno stato "vuoto". L'analisi si verifica nel costruttore o in funzioni separate. Poiché questi oggetti sono solo strutture stupide, puoi definirli tramite namedtuple() quando l'analisi è separata.

risposta data 24.09.2017 - 12:56
fonte
0

Esempio di contro alla risposta "Sì" prevista esistente:

Quando sviluppi una GUI con, diciamo, 30 widget (dove molti di essi potrebbero essere solo semplici contenitori padre), è troppo brutto per codificare 30 attributi di istanza (istanze di widget) all'interno dell'inizializzatore. In effetti, su molti livelli, non è nemmeno pratico farlo entro __init__() . Io "ritardo" sempre la creazione di widget delegandoli a una funzione dedicata che, a sua volta, può contare su altre funzioni più piccole, ognuna delle quali ha una responsabilità su questo o quel gruppo di widget.

    
risposta data 24.09.2017 - 15:23
fonte

Leggi altre domande sui tag