Est-ce une meilleure pratique de pré-initialiser les attributs dans une classe, ou de les ajouter en cours de route?


11

Je suis désolé s'il s'agit d'une question ABSOLUMENT sophomorique, mais je suis curieux de savoir quelles sont les meilleures pratiques, et je n'arrive pas à trouver une bonne réponse sur Google.

En Python, j'utilise généralement une classe vide comme conteneur de structure de données super-catchall (un peu comme un fichier JSON), et j'ajoute des attributs en cours de route:

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]

Cela me donne une énorme flexibilité, car l'objet conteneur peut essentiellement stocker n'importe quoi. Donc, si de nouvelles exigences surgissent, je vais simplement l'ajouter comme autre attribut à l'objet DataObj (que je passe dans mon code).

Cependant, récemment, j'ai été impressionné (par les programmeurs FP) que c'est une pratique horrible, car cela rend très difficile la lecture du code. Il faut parcourir tout le code pour comprendre quels attributs DataObj possède réellement.

Question : Comment puis-je réécrire cela pour une plus grande maintenabilité sans sacrifier la flexibilité?

Y a-t-il des idées de programmation fonctionnelle que je peux adopter?

Je recherche les meilleures pratiques.

Remarque : une idée est de pré-initialiser la classe avec tous les attributs que l'on s'attend à rencontrer, par exemple

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]

Est-ce vraiment une bonne idée? Et si je ne connais pas mes attributs a priori?


Vos structures de données sont tellement modifiables que vous semblez soucieux de leur maintenabilité. Dans votre temps libre copieux ™, essayez de lire cet article sur les modèles de données immuables . Cela pourrait complètement changer la façon dont vous raisonnez sur les données.
9000

@ 9000 Un article comme celui-ci convainc de nouveau les déjà convaincus. Pour moi, cela ressemblait plus à un comment qu'à un pourquoi (la liste des pourquoi n'est pas vraiment convaincante à moins que vous ne ressentiez que vous avez ces besoins spécifiques). Pour moi, cela ne convainc pas quelqu'un qui met à jour des factures dans VB qu'il est logique de devoir constamment faire de nouvelles copies de leur objet de facture (ajouter un paiement, un nouvel objet de facture; ajouter une pièce, un nouvel objet de facture).
Paul

Réponses:


10

Comment puis-je réécrire cela pour une meilleure maintenabilité sans sacrifier la flexibilité?

Non. La flexibilité est précisément ce qui cause le problème. Si n'importe quel code n'importe où peut changer les attributs d'un objet, la maintenabilité est déjà en morceaux. Idéalement, chaque classe a un ensemble d'attributs qui sont gravés dans la pierre après __init__et les mêmes pour chaque instance. Pas toujours possible ou sensé, mais cela devrait être le cas chaque fois que vous n'avez pas vraiment de bonnes raisons de l'éviter.

une idée est de pré-initialiser la classe avec tous les attributs que l'on s'attend à rencontrer

Ce n'est pas une bonne idée. Bien sûr, l'attribut est là, mais peut avoir une valeur fausse, ou même une valeur valide qui couvre le code n'affectant pas la valeur (ou une faute d'orthographe). AttributeErrorest effrayant, mais obtenir de mauvais résultats est pire. Les valeurs par défaut sont généralement bonnes, mais pour choisir une valeur par défaut raisonnable (et décider ce qui est requis), vous devez savoir à quoi sert l'objet.

Et si je ne connais pas mes attributs a priori?

Ensuite, vous êtes foutu dans tous les cas et devez utiliser un dict ou une liste au lieu de coder en dur les noms d'attribut. Mais je suppose que vous vouliez dire "... au moment où j'écris la classe des conteneurs". Ensuite, la réponse est: "Vous pouvez modifier des fichiers en même temps, duh." Besoin d'un nouvel attribut? Ajoutez un attribut frigging à la classe de conteneur. Il y a plus de code utilisant cette classe et il n'a pas besoin de cet attribut? Envisagez de diviser les choses en deux classes distinctes (utilisez des mixins pour rester SEC), alors rendez-le facultatif si cela a du sens.

Si vous avez peur d'écrire des classes de conteneurs répétitives: appliquez judicieusement la métaprogrammation, ou utilisez collections.namedtuplesi vous n'avez pas besoin de muter les membres après la création (vos amis FP seront ravis).


7

Vous pouvez toujours utiliser la classe Bunch d' Alex Martelli . Dans ton cas:

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])

De cette façon, au moins il est clair pour le lecteur que les données ne sont qu'un magasin de données stupide et on peut voir immédiatement quelles valeurs sont stockées sous quel nom, car tout se passe sur une seule ligne.

Et oui, faire les choses de cette façon est parfois une bonne idée.


1

J'utiliserais probablement la deuxième approche, éventuellement en utilisant Nonepour indiquer des données invalides. Il est vrai qu'il est difficile de lire / maintenir si vous ajoutez des attributs plus tard. Cependant, plus d'informations sur le but de cette classe / objet donneraient un aperçu de la raison pour laquelle la première idée est une mauvaise conception: où auriez-vous jamais une classe complètement vide sans méthodes ou données par défaut? Pourquoi ne sauriez-vous pas quels sont les attributs de la classe?

Il est possible que processDatacela soit mieux en tant que méthode ( process_datapour suivre les conventions de dénomination python), car elle agit sur la classe. Compte tenu de l'exemple, il semble que cela pourrait être mieux en tant que structure de données (où un dictpeut suffire).

Étant donné un exemple réel, vous pourriez envisager de poser la question à CodeReview , où ils pourraient aider à refactoriser le code.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.