webentwicklung-frage-antwort-db.com.de

Klasse mit zu vielen Parametern: bessere Designstrategie?

Ich arbeite mit Neuronenmodellen. Eine Klasse, die ich entwerfe, ist eine Zellklasse, die eine topologische Beschreibung eines Neurons (mehrere miteinander verbundene Kompartimente) darstellt. Es hat viele Parameter, aber alle sind relevant, zum Beispiel:

anzahl der Axon-Segmente, apikale Bifibricationen, somatische Länge, somatischer Durchmesser, apikale Länge, zufällige Verzweigung, verzweigte Länge und so weiter ... es gibt insgesamt etwa 15 Parameter!

Ich kann all diese Werte auf einen Standardwert setzen, aber meine Klasse sieht verrückt aus, mit mehreren Zeilen für Parameter. So etwas muss gelegentlich auch anderen Leuten passieren. Gibt es eine offensichtliche bessere Möglichkeit, dies zu gestalten, oder mache ich das Richtige?

UPDATE: Wie einige von Ihnen gefragt haben, habe ich meinen Code für die Klasse angehängt, wie Sie sehen, hat diese Klasse eine große Anzahl von Parametern (> 15), die jedoch alle verwendet werden und definiert werden müssen die Topologie einer Zelle. Das Problem besteht im Wesentlichen darin, dass das physische Objekt, das sie erstellen, sehr komplex ist. Ich habe eine Bilddarstellung von Objekten angehängt, die von dieser Klasse erzeugt wurden. Wie würden erfahrene Programmierer das anders machen, um so viele Parameter in der Definition zu vermeiden?

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

Versuchen Sie diesen Ansatz:

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

Sie können dann eine Neuron wie folgt erstellen:

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

Ich würde sagen, dass an diesem Ansatz nichts falsch ist - wenn Sie 15 Parameter benötigen, um etwas zu modellieren, benötigen Sie 15 Parameter. Wenn es keinen geeigneten Standardwert gibt, müssen Sie beim Erstellen eines Objekts alle 15 Parameter übergeben. Ansonsten können Sie einfach den Standard festlegen und ihn später über einen Setter oder direkt ändern.

Ein anderer Ansatz ist das Erstellen von Unterklassen für bestimmte Arten von Neuronen (in Ihrem Beispiel) und die Bereitstellung guter Werte für bestimmte Werte oder das Ableiten der Werte von anderen Parametern.

Oder Sie könnten Teile des Neurons in separaten Klassen einkapseln und diese Teile für die von Ihnen modellierten Neuronen wiederverwenden. Das heißt, Sie könnten separate Klassen schreiben, um eine Synapse, ein Axon, das Soma usw. zu modellieren.

16
onitake

Sie könnten vielleicht ein Python-Objekt "dict" verwenden? http://docs.python.org/tutorial/datastructures.html#dictionary

7

Mit so vielen Parametern kann man sagen, dass die Klasse wahrscheinlich zu viele Dinge tut.

Ich schlage vor, Sie möchten Ihre Klasse in mehrere Klassen unterteilen, von denen jede einige Ihrer Parameter übernimmt. Auf diese Weise ist jede Klasse einfacher und braucht nicht so viele Parameter.

Ohne mehr über Ihren Code zu wissen, kann ich nicht genau sagen, wie Sie ihn aufteilen sollten.

6
Winston Ewert

Sie könnten die Anzahl der Argumente verringern, indem Sie Objekte wie Axon, Soma und DendriticTree außerhalb des LayerV-Konstruktors erstellen und stattdessen diese Objekte übergeben.

Einige der Parameter werden nur beim Konstruieren von z. DendriticTree, andere werden auch an anderen Orten verwendet, daher ist das Problem nicht so eindeutig, aber ich würde es definitiv versuchen.

5
Marcello Romani

können Sie einen Beispielcode für das, woran Sie gerade arbeiten, zur Verfügung stellen? Es würde helfen, sich ein Bild von dem zu machen, was Sie tun, und Ihnen früher Hilfe zukommen zu lassen.

Wenn es nur die Argumente sind, die Sie an die Klasse übergeben, die es lang machen, müssen Sie nicht alles in __init__ setzen. Sie können die Parameter festlegen, nachdem Sie die Klasse erstellt haben, oder ein Wörterbuch/eine Klasse mit Parametern als Argument übergeben.

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

Nachdem ich Ihren Code durchgesehen und gemerkt habe, dass ich keine Ahnung habe, in welcher Beziehung diese Parameter zueinander stehen (nur wegen meines Mangels an Wissen über die Neurowissenschaften), möchte ich Sie auf ein sehr gutes Buch über objektorientiertes Design verweisen. Fertigkeiten im objektorientierten Design von Steven F. Lott aufbauen ist eine hervorragende Lektüre, und ich denke, ich würde Ihnen und allen anderen helfen, objektorientierte Programme zu erstellen.

Es steht unter der Creative Commons-Lizenz, ist also für Sie kostenlos zu verwenden. Hier ist ein Link im Format PDF http://homepage.mac.com/s_lott/books/oodesign/build- Python/Latex/GebäudeSkillsinOODesign.pdf

Ich denke, Ihr Problem läuft auf das Gesamtdesign Ihrer Klassen hinaus. Manchmal, wenn auch sehr selten, benötigen Sie eine ganze Reihe von Argumenten für die Initialisierung, und die meisten Antworten haben andere Initialisierungsmöglichkeiten. In vielen Fällen können Sie die Klasse jedoch in einfacher zu handhabende und weniger umständliche Klassen unterteilen .

3
Nate

Dies ähnelt den anderen Lösungen, die ein Standardwörterbuch durchlaufen, verwendet jedoch eine kompaktere Schreibweise:

class MyClass(object):

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

Können Sie einen detaillierteren Anwendungsfall angeben? Vielleicht funktioniert ein Prototypmuster:

Wenn in Objektgruppen einige Ähnlichkeiten bestehen, kann ein Prototypmuster hilfreich sein. Haben Sie eine Menge Fälle, in denen eine Neuronenpopulation genau wie eine andere ist, außer in irgendeiner Weise anders? (d. h., anstatt eine kleine Anzahl von diskreten Klassen zu haben, haben Sie eine große Anzahl von Klassen, die sich geringfügig voneinander unterscheiden.) 

Python ist eine klassifizierte Sprache, aber genauso wie Sie klassenbasierte Programmierung in einer prototypbasierten Sprache wie Javascript simulieren können, können Sie Prototypen simulieren, indem Sie Ihrer Klasse eine CLONE-Methode geben, die ein neues Objekt und populiert seine Ivars vom übergeordneten Element. Schreiben Sie die Klonmethode so, dass die übergebenen Schlüsselwortparameter Die "geerbten" Parameter überschreiben, sodass Sie sie mit etwas aufrufen können wie: 

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

Ich habe mich nie mit dieser Situation oder diesem Thema befassen müssen. Ihre Beschreibung impliziert für mich, dass Sie bei der Entwicklung des Designs möglicherweise feststellen, dass eine Reihe weiterer Klassen relevant werden wird - das Kompartiment ist am offensichtlichsten. Wenn diese als eigenständige Klassen auftauchen, ist es wahrscheinlich, dass einige Ihrer Parameter zu Parametern dieser zusätzlichen Klassen werden.

1
Chris Walton

Meiner Meinung nach besteht die einfache Lösung in Ihrem Fall darin, Objekte höherer Ordnung als Parameter zu übergeben. 

In Ihrem __init__ haben Sie beispielsweise ein DendriticTree, das mehrere Argumente aus Ihrer Hauptklasse LayerV verwendet: 

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
)

Anstatt diese 6 Argumente an Ihr LayerV zu übergeben, würden Sie das DendriticTree-Objekt direkt übergeben (wodurch 5 Argumente gespeichert werden).

Wahrscheinlich möchten Sie, dass diese Werte überall verfügbar sind. Daher müssen Sie diese DendriticTree-Datei speichern: 

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

Wenn Sie auch einen Standardwert haben möchten, können Sie Folgendes haben: 

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

Auf diese Weise delegieren Sie, was der Standardwert DendriticTree sein sollte, an die Klasse, die dieser Angelegenheit gewidmet ist, anstatt diese Logik in der übergeordneten Klasse LayerV zu haben.

Wenn Sie auf den apical_bifibs zugreifen müssen, den Sie zuvor an LayerV übergeben haben, können Sie nur über self.main_apical_dendrite.bifibs darauf zugreifen.

Im Allgemeinen besteht Ihr Ziel darin, einen logischen Weg zu finden, selbst wenn die Klasse, die Sie erstellen, keine klare Zusammensetzung mehrerer Klassen ist. Nicht nur, um Ihren Code sauberer zu machen, sondern vor allem, um die Benutzer zu verstehen, wofür diese Parameter verwendet werden. In den extremen Fällen, in denen man sie nicht teilen kann, ist es absolut in Ordnung, eine Klasse mit so vielen Parametern zu haben. Wenn es keine eindeutige Möglichkeit gibt, Argumente zu teilen, erhalten Sie wahrscheinlich etwas, das weniger klar ist als eine Liste mit 15 Argumenten. 

Wenn Sie der Meinung sind, dass das Erstellen einer Klasse zum Gruppieren von Parametern zu viel ist, können Sie einfach collections.namedtuple verwenden, das als hier angezeigt voreingestellt sein kann. 

0
cglacet

Sie könnten eine Klasse für Ihre Parameter erstellen.

Statt mehrere Parameter zu übergeben, übergeben Sie eine Klasse.

0
Luc M