E 'una best practice pre-inizializzare gli attributi in una classe, o per aggiungerli lungo la strada?

11

Mi dispiace se questa è una domanda assolutamente sofisticata, ma sono curioso di sapere quali sono le migliori pratiche disponibili e non riesco a trovare una buona risposta su Google.

In Python, di solito uso una classe vuota come un contenitore di strutture dati super-catchall (un po 'come un file JSON), e aggiungo attributi lungo la strada:

class DataObj:
    "Catch-all data object"
    def __init__(self):
        pass

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Questo mi dà un'enorme flessibilità, perché l'oggetto contenitore può essenzialmente archiviare qualsiasi cosa. Quindi, se emergono nuovi requisiti, lo aggiungerò semplicemente come un altro attributo all'oggetto DataObj (che passo nel mio codice).

Tuttavia, recentemente mi è stato impressionato (dai programmatori della FP) che questa è una pratica terribile, perché rende molto difficile leggere il codice. Uno deve passare attraverso tutto il codice per capire quali attributi ha effettivamente DataObj.

Domanda : come posso riscrivere ciò per maggiore manutenibilità senza sacrificare la flessibilità?

Ci sono idee dalla programmazione funzionale che posso adottare?

Sto cercando le migliori pratiche là fuori.

Nota : un'idea è quella di preinizializzare la classe con tutti gli attributi che ci si aspetta di incontrare, ad es.

class DataObj:
    "Catch-all data object"
    def __init__(self):
        data.a = 0
        data.b = ""
        data.c = []

def processData(inputs):
    data = DataObj()
    data.a = 1
    data.b = "sym"
    data.c = [2,5,2,1]

Questa è davvero una buona idea? Cosa succede se non so quali sono i miei attributi a priori?

    
posta Gilead 13.08.2012 - 19:50
fonte

3 risposte

9

How can I rewrite this for greater maintainability without sacrificing flexibility?

Non lo fai. La flessibilità è esattamente ciò che causa il problema. Se un codice qualsiasi può cambiare gli attributi di un oggetto, la manutenibilità è già in pezzi. Idealmente, ogni classe ha un insieme di attributi che è impostato in pietra dopo __init__ e lo stesso per ogni istanza. Non sempre possibile o ragionevole, ma dovrebbe essere il caso ogni volta che non hai davvero buone ragioni per evitarlo.

one idea is to pre-initialize the class with all the attributes that one expects to encounter

Questa non è una buona idea. Certo, quindi l'attributo è lì, ma può avere un valore falso, o anche un valore valido che copre il codice che non assegna il valore (o uno errato). AttributeError è spaventoso, ma ottenere risultati errati è peggio. I valori predefiniti in generale vanno bene, ma per scegliere un predefinito sensato (e decidere cosa è necessario) è necessario sapere per cosa viene usato l'oggetto.

What if I don't know what my attributes are a priori?

Quindi sei fregato in ogni caso e dovresti usare un dict o un elenco invece dei nomi degli attributi hardcoding. Ma credo che tu intendessi "... al momento in cui scrivo la classe del contenitore". Quindi la risposta è: "È possibile modificare i file in Lockstep, duh". Hai bisogno di un nuovo attributo? Aggiungi un attributo frigging alla classe contenitore. C'è più codice usando quella classe e non ha bisogno di quell'attributo? Prova a suddividere le cose in due classi separate (usa mixins per rimanere DRY), quindi rendilo opzionale se ha senso.

Se hai paura di scrivere classi di contenitori ripetitivi: applica metaprogramming con giudizio, oppure usa collections.namedtuple se non hai bisogno di mutare i membri dopo la creazione (i tuoi amici di FP sarebbero contenti).

    
risposta data 13.08.2012 - 20:09
fonte
6

Potresti sempre utilizzare di Alex Martelli Classe di gruppo . Nel tuo caso:

class DataObj:
    "Catch-all data object"
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

def processData(inputs):
    data = DataObj(a=1, b="sym", c=[2,5,2,1])

In questo modo, almeno per il lettore è chiaro che data è solo un muto data store e si può vedere immediatamente quali valori sono memorizzati come nome, poiché tutto avviene in una riga.

E sì, fare le cose in questo modo è in effetti una buona idea, a volte.

    
risposta data 13.08.2012 - 23:58
fonte
1

Probabilmente userò il secondo approccio, probabilmente usando None per indicare dati non validi. È vero che è difficile da leggere / mantenere se si aggiungono attributi in seguito. Tuttavia, maggiori informazioni sullo scopo di questa classe / oggetto darebbero informazioni sul motivo per cui la prima idea è una cattiva progettazione: dove avresti mai una classe completamente vuota senza metodi o dati predefiniti? Perché non sapresti quali sono gli attributi della classe?

È possibile che processData potrebbe essere migliore come metodo ( process_data per seguire le convenzioni di denominazione di python), poiché agisce sulla classe. Dato l'esempio, sembra che potrebbe essere migliore come una struttura dati (dove un dict può essere sufficiente).

Dato un esempio reale, potresti prendere in considerazione la domanda a CodeReview , dove potrebbero aiutare a rifattorizzare il codice.

    
risposta data 13.08.2012 - 19:59
fonte