È preferibile pre-inizializzare gli attributi in una classe o aggiungerli lungo il percorso?


12

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

In Python, di solito uso una classe vuota come contenitore di strutture di dati super-catchall (una specie di 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 sorgono nuovi requisiti, lo aggiungerò semplicemente come un altro attributo all'oggetto DataObj (che passo nel mio codice).

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

Domanda : come posso riscriverlo per una 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 pre-inizializzare 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]

È davvero una buona idea? E se non sapessi quali sono i miei attributi a priori?


Le tue strutture di dati sono così mutabili che sembra preoccuparti della loro manutenibilità. Nel tuo abbondante tempo libero ™, prova a leggere questo articolo su modelli di dati immutabili . Potrebbe cambiare completamente il modo in cui ragionate sui dati.
9000,

@ 9000 Un articolo del genere convince nuovamente il già convinto. A me è sembrato più un how-to che un perché (l'elenco dei perché non è davvero convincente a meno che tu non senta di avere quei bisogni specifici). Per me non convince qualcuno che aggiorna le fatture in VB che dover fare costantemente nuove copie del loro oggetto fattura abbia senso (aggiungi pagamento, nuovo oggetto fattura; aggiungi parte, nuovo oggetto fattura).
Paolo

Risposte:


10

Come posso riscriverlo per una maggiore manutenibilità senza sacrificare la flessibilità?

Non La flessibilità è precisamente ciò che causa il problema. Se un qualsiasi codice in qualsiasi parte può modificare 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 succedere ogni volta che non si hanno buoni motivi per evitarlo.

un'idea è di pre-inizializzare la classe con tutti gli attributi che ci si aspetta di incontrare

Non è una buona idea. Certo, allora l'attributo è lì, ma può avere un valore falso, o anche uno valido che copre il codice che non assegna il valore (o uno errato). AttributeErrorfa paura, ma ottenere risultati sbagliati è peggio. I valori predefiniti in generale vanno bene, ma per scegliere un valore predefinito ragionevole (e decidere cosa è richiesto) è necessario sapere a cosa serve l'oggetto.

E se non sapessi quali sono i miei attributi a priori?

Quindi sei fregato in ogni caso e dovresti usare un dict o un elenco anziché i nomi degli attributi hardcoding. Ma suppongo che volevi dire "... al momento in cui scrivo la classe container". Quindi la risposta è: "Puoi 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? Valuta di dividere le cose in due classi separate (usa i mixin per rimanere SECCO), quindi rendilo facoltativo se ha senso.

Se hai paura di scrivere classi ripetitive di container: applica metaprogrammazione in modo giudizioso o usa collections.namedtuplese non hai bisogno di mutare i membri dopo la creazione (i tuoi amici di FP sarebbero contenti).


7

Puoi sempre usare la classe Bunch di Alex Martelli . 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 è chiaro per il lettore che i dati sono solo un muto archivio di dati e si può vedere immediatamente quali valori sono memorizzati sotto quale nome, poiché tutto accade in una riga.

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


1

Probabilmente userei il secondo approccio, eventualmente usando Noneper indicare dati non validi. È vero che è difficile leggere / mantenere se si aggiungono attributi in seguito. Tuttavia, ulteriori informazioni sullo scopo di questa classe / oggetto darebbero un'idea del perché la prima idea è una cattiva progettazione: dove avresti mai una classe completamente vuota senza metodi o dati predefiniti? Perché non sai quali attributi ha la classe?

È possibile che processDatasia meglio come metodo ( process_dataper seguire le convenzioni di denominazione di Python), dal momento che agisce sulla classe. Dato l'esempio, sembra che potrebbe essere migliore come struttura di dati (dove a dictpuò bastare).

Dato un esempio reale, potresti prendere in considerazione l' idea di portare la domanda a CodeReview , dove potrebbero aiutare a riformattare il codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.