web-dev-qa-db-fra.com

Classe avec trop de paramètres: meilleure stratégie de conception?

Je travaille avec des modèles de neurones. Une classe que je conçois est une classe de cellules qui est une description topologique d'un neurone (plusieurs compartiments reliés entre eux). Il a beaucoup de paramètres mais ils sont tous pertinents, par exemple:

nombre de segments axonaux, bifibrications apicales, longueur somatique, diamètre somatique, longueur apicale, caractère aléatoire des branches, longueur des branches, etc., etc. Il y a environ 15 paramètres au total!

Je peux définir toutes ces valeurs par défaut, mais ma classe a l'air folle avec plusieurs lignes de paramètres. Ce genre de chose doit aussi arriver occasionnellement à d'autres personnes. Existe-t-il un meilleur moyen évident de le concevoir ou est-ce que je fais ce qu'il convient?

UPDATE: Comme certains d'entre vous l'ont demandé, j'ai joint mon code pour la classe, comme vous pouvez le constater, cette classe possède un grand nombre de paramètres (> 15), mais ils sont tous utilisés et doivent être définis la topologie d'une cellule. Le problème est essentiellement que l'objet physique qu'ils créent est très complexe. J'ai joint une représentation d'image d'objets produits par cette classe. Comment des programmeurs expérimentés procéderaient-ils différemment pour éviter autant de paramètres dans la définition?

enter image description here

class LayerV(__Cell):

    def __init__(self,somatic_dendrites=10,oblique_dendrites=10,
                somatic_bifibs=3,apical_bifibs=10,oblique_bifibs=3,
                L_sigma=0.0,apical_branch_prob=1.0,
                somatic_branch_prob=1.0,oblique_branch_prob=1.0,
                soma_L=30,soma_d=25,axon_segs=5,myelin_L=100,
                apical_sec1_L=200,oblique_sec1_L=40,somadend_sec1_L=60,
                ldecf=0.98):

        import random
        import math

        #make main the regions:
        axon=Axon(n_axon_seg=axon_segs)

        soma=Soma(diam=soma_d,length=soma_L)

        main_apical_dendrite=DendriticTree(bifibs=
                apical_bifibs,first_sec_L=apical_sec1_L,
                L_sigma=L_sigma,L_decrease_factor=ldecf,
                first_sec_d=9,branch_prob=apical_branch_prob)

        #make the somatic denrites

        somatic_dends=self.dendrite_list(num_dends=somatic_dendrites,
                       bifibs=somatic_bifibs,first_sec_L=somadend_sec1_L,
                       first_sec_d=1.5,L_sigma=L_sigma,
                       branch_prob=somatic_branch_prob,L_decrease_factor=ldecf)

        #make oblique dendrites:

        oblique_dends=self.dendrite_list(num_dends=oblique_dendrites,
                       bifibs=oblique_bifibs,first_sec_L=oblique_sec1_L,
                       first_sec_d=1.5,L_sigma=L_sigma,
                       branch_prob=oblique_branch_prob,L_decrease_factor=ldecf)

        #connect axon to soma:
        axon_section=axon.get_connecting_section()
        self.soma_body=soma.body
        soma.connect(axon_section,region_end=1)

        #connect apical dendrite to soma:
        apical_dendrite_firstsec=main_apical_dendrite.get_connecting_section()
        soma.connect(apical_dendrite_firstsec,region_end=0)

        #connect oblique dendrites to apical first section:
        for dendrite in oblique_dends:
            apical_location=math.exp(-5*random.random()) #for now connecting randomly but need to do this on some linspace
            apsec=dendrite.get_connecting_section()
            apsec.connect(apical_dendrite_firstsec,apical_location,0)

        #connect dendrites to soma:
        for dend in somatic_dends:
            dendsec=dend.get_connecting_section()
            soma.connect(dendsec,region_end=random.random()) #for now connecting randomly but need to do this on some linspace

        #assign public sections
        self.axon_iseg=axon.iseg
        self.axon_hill=axon.hill
        self.axon_nodes=axon.nodes
        self.axon_myelin=axon.myelin
        self.axon_sections=[axon.hill]+[axon.iseg]+axon.nodes+axon.myelin
        self.soma_sections=[soma.body]
        self.apical_dendrites=main_apical_dendrite.all_sections+self.seclist(oblique_dends)
        self.somatic_dendrites=self.seclist(somatic_dends)
        self.dendrites=self.apical_dendrites+self.somatic_dendrites
        self.all_sections=self.axon_sections+[self.soma_sections]+self.dendrites
59
Mike Vella

Essayez cette approche:

class Neuron(object):

    def __init__(self, **kwargs):
        prop_defaults = {
            "num_axon_segments": 0, 
            "apical_bifibrications": "fancy default",
            ...
        }

        for (prop, default) in prop_defaults.iteritems():
            setattr(self, prop, kwargs.get(prop, default))

Vous pouvez ensuite créer une Neuron comme ceci:

n = Neuron(apical_bifibrications="special value")
59
blubb

Je dirais qu'il n'y a rien de mal à cette approche - si vous avez besoin de 15 paramètres pour modéliser quelque chose, vous avez besoin de 15 paramètres. Et s’il n’ya pas de valeur par défaut appropriée, vous devez entrer les 15 paramètres lors de la création d’un objet. Sinon, vous pouvez simplement définir la valeur par défaut et la modifier ultérieurement via un configurateur ou directement.

Une autre approche consiste à créer des sous-classes pour certains types de neurones courants (dans votre exemple) et à définir de bonnes valeurs par défaut pour certaines valeurs, ou de déduire les valeurs d'autres paramètres.

Vous pouvez également encapsuler des parties du neurone dans des classes séparées et les réutiliser pour les neurones que vous modélisez. C'est-à-dire que vous pourriez écrire des classes séparées pour modéliser une synapse, un axone, le soma, etc.

16
onitake

Vous pourriez peut-être utiliser un objet "dict" Python? http://docs.python.org/tutorial/datastructures.html#dictionaries

7

Avoir autant de paramètres suggère que la classe fait probablement trop de choses.

Je suggère que vous souhaitiez diviser votre classe en plusieurs classes, chacune prenant certains de vos paramètres. De cette façon, chaque classe est plus simple et ne prend pas autant de paramètres.

Sans en savoir plus sur votre code, je ne peux pas dire exactement comment vous devez le scinder.

6
Winston Ewert

On pourrait réduire le nombre d'arguments en construisant des objets tels que Axon, Soma et DendriticTree en dehors du constructeur LayerV et en transmettant ces objets à la place.

Certains paramètres ne sont utilisés que dans la construction, par exemple. DendriticTree, d'autres sont utilisés ailleurs, alors le problème n'est pas aussi net, mais j'essaierais certainement cette approche.

5
Marcello Romani

pourriez-vous fournir un exemple de code sur lequel vous travaillez? Il serait utile d’avoir une idée de ce que vous faites et de vous aider plus tôt.

Si ce ne sont que les arguments que vous transmettez à la classe qui la rendent longue, vous n'avez pas à tout mettre dans __init__. Vous pouvez définir les paramètres après avoir créé la classe ou passer un dictionnaire/une classe contenant tous les paramètres en argument.

class MyClass(object):

    def __init__(self, **kwargs):
        arg1 = None
        arg2 = None
        arg3 = None

        for (key, value) in kwargs.iteritems():
            if hasattr(self, key):
                setattr(self, key, value)

if __== "__main__":

    a_class = MyClass()
    a_class.arg1 = "A string"
    a_class.arg2 = 105
    a_class.arg3 = ["List", 100, 50.4]

    b_class = MyClass(arg1 = "Astring", arg2 = 105, arg3 = ["List", 100, 50.4])
4
Nate

Après avoir examiné votre code et réalisé que je ne comprenais pas comment ces paramètres étaient liés (uniquement à cause de mon manque de connaissances sur le sujet des neurosciences), je vous indiquerais un très bon livre sur la conception orientée objet. Construire des compétences en conception orientée objet de Steven F. Lott est une excellente lecture et je pense que cela vous aiderait, ainsi que quiconque, à mettre en place des programmes orientés objet.

Il est publié sous licence Creative Commons, il est donc gratuit, voici un lien au format PDF http://homepage.mac.com/s_lott/books/oodesign/build- python/latex/BuildingSkillsinOODesign.pdf

Je pense que votre problème se résume à la conception générale de vos cours. Parfois, bien que très rarement, vous avez besoin de beaucoup d’arguments pour initialiser, et la plupart des réponses contiennent d’autres méthodes d’initialisation détaillées, mais dans de nombreux cas, vous pouvez diviser la classe en classes plus faciles à gérer et moins lourdes. .

3
Nate

Ceci est similaire aux autres solutions qui parcourent un dictionnaire par défaut, mais utilise une notation plus compacte:

class MyClass(object):

    def __init__(self, **kwargs):
        self.__dict__.update(dict(
            arg1=123,
            arg2=345,
            arg3=678,
        ), **kwargs)
2
Cerin

Pouvez-vous donner un cas d'utilisation plus détaillé? Peut-être qu'un modèle de prototype fonctionnera:

S'il existe des similitudes dans des groupes d'objets, un modèle de prototype peut aider. Avez-vous souvent des cas où une population de neurones est semblable à une autre, sauf qu’elle est différente? (c.-à-d. plutôt que d'avoir un petit nombre de classes discrètes, vous avez un grand nombre de classes légèrement différentes les unes des autres.) 

Python est un langage basé sur les classes, mais comme vous pouvez simuler une programmation basée sur des classes dans un langage prototype tel que Javascript, vous pouvez simuler des prototypes en donnant à votre classe une méthode CLONE qui crée un nouvel objet et populate ses ivars du parent. Ecrivez la méthode clone pour que les paramètres de mot clé Qui lui sont transmis remplacent les paramètres "hérités", de sorte que vous puissiez l'appeler avec quelque choselike: 

new_neuron = old_neuron.clone( branching_length=n1, branching_randomness=r2 )
1

Je n'ai jamais eu à faire face à cette situation ou à ce sujet. Votre description implique pour moi que vous constaterez peut-être, au fur et à mesure que vous développez la conception, qu’un certain nombre de classes supplémentaires deviendront pertinentes, le compartiment étant le plus évident. Si ceux-ci apparaissent comme des classes à part entière, il est probable que certains de vos paramètres deviennent des paramètres de ces classes supplémentaires.

1
Chris Walton

À mon avis, dans votre cas, la solution de facilité est de passer en paramètre des objets d’ordre supérieur. 

Par exemple, dans votre __init__, vous avez une DendriticTree qui utilise plusieurs arguments de votre classe principale LayerV

main_apical_dendrite = DendriticTree(
    bifibs=apical_bifibs,
    first_sec_L=apical_sec1_L,
    L_sigma=L_sigma,
    L_decrease_factor=ldecf,
    first_sec_d=9, 
    branch_prob=apical_branch_prob
)

Au lieu de transmettre ces 6 arguments à votre LayerV, vous passeriez directement l'objet DendriticTree (en économisant ainsi 5 arguments).

Vous voulez probablement que ces valeurs soient accessibles partout, vous devrez donc sauvegarder cette DendriticTree

class LayerV(__Cell):
    def __init__(self, main_apical_dendrite, ...):
        self.main_apical_dendrite = main_apical_dendrite        

Si vous voulez aussi avoir une valeur par défaut, vous pouvez avoir: 

class LayerV(__Cell):
    def __init__(self, main_apical_dendrite=None, ...):
        self.main_apical_dendrite = main_apical_dendrite or DendriticTree()

De cette façon, vous déléguez ce que la valeur par défaut DendriticTree devrait être à la classe dédiée à cette question au lieu d’avoir cette logique dans la classe d’ordre supérieur que LayerV.

Enfin, lorsque vous devez accéder au apical_bifibs que vous utilisiez auparavant pour passer à LayerV, vous y accédez simplement via self.main_apical_dendrite.bifibs.

En général, même si la classe que vous créez n'est pas une composition claire de plusieurs classes, votre objectif est de trouver un moyen logique de fractionner vos paramètres. Non seulement pour rendre votre code plus propre, mais surtout pour aider les gens à comprendre à quoi serviront ces paramètres. Dans les cas extrêmes où vous ne pouvez pas les séparer, je pense que c'est totalement correct d'avoir une classe avec autant de paramètres. S'il n'y a pas de moyen clair de scinder les arguments, vous obtiendrez probablement quelque chose d'encore moins clair qu'une liste de 15 arguments. 

Si vous avez envie de créer une classe pour regrouper des paramètres ensemble, vous pouvez simplement utiliser collections.namedtuple qui peut avoir des valeurs par défaut telles que comme indiqué ici

0
cglacet

Vous pouvez créer une classe pour vos paramètres.

Au lieu de passer un tas de paramètres, vous passez une classe.

0
Luc M