Ich arbeite mit Modellen von Neuronen. Eine Klasse, die ich entwerfe, ist eine Zellklasse, die eine topologische Beschreibung eines Neurons ist (mehrere miteinander verbundene Kompartimente). Es hat viele Parameter, aber alle sind relevant, zum Beispiel:
Anzahl der Axonsegmente, apikale Bifibrikationen, somatische Länge, somatischer Durchmesser, apikale Länge, Verzweigungszufälligkeit, Verzweigungslänge usw. Es gibt insgesamt etwa 15 Parameter!
Ich kann all dies auf einen Standardwert setzen, aber meine Klasse sieht mit mehreren Zeilen für Parameter verrückt aus. So etwas muss gelegentlich auch anderen Menschen passieren. Gibt es einen offensichtlich besseren Weg, dies zu entwerfen, 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, verfügt diese Klasse über eine große Anzahl von Parametern (> 15), die jedoch alle verwendet werden und zum Definieren der Topologie einer Zelle erforderlich sind. Das Problem besteht im Wesentlichen darin, dass das von ihnen erstellte physische Objekt sehr komplex ist. Ich habe eine Bilddarstellung von Objekten beigefügt, die von dieser Klasse hergestellt wurden. Wie würden erfahrene Programmierer dies anders machen, um so viele Parameter in der Definition zu vermeiden?
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
quelle
Antworten:
UPDATE: Dieser Ansatz mag in Ihrem speziellen Fall geeignet sein, hat aber definitiv seine Nachteile. Sehen Sie, ob Kwargs ein Antimuster sind?
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 Folgendes erstellen
Neuron
:n = Neuron(apical_bifibrications="special value")
quelle
for
Schleife kann durch die beiden Zeilen ersetzt werdenself.__dict__.update(prop_defaults); self.__dict__.update(kwargs)
. Alternativ können Sie dieif
Anweisung durch ersetzensetattr(self, prop, kwargs.get(prop, default))
.kwargs
Nicht-Eigenschaften enthalten sind. Ich mag jedoch den zweiten Ansatz!Ich würde sagen, an diesem Ansatz ist nichts auszusetzen. Wenn Sie 15 Parameter benötigen, um etwas zu modellieren, benötigen Sie 15 Parameter. Und wenn es keinen geeigneten Standardwert gibt, müssen Sie beim Erstellen eines Objekts alle 15 Parameter übergeben. Andernfalls können Sie die Standardeinstellung einfach festlegen und später über einen Setter oder direkt ändern.
Ein anderer Ansatz besteht darin, Unterklassen für bestimmte gängige Arten von Neuronen (in Ihrem Beispiel) zu erstellen und gute Standardeinstellungen für bestimmte Werte bereitzustellen oder die Werte aus anderen Parametern abzuleiten.
Oder Sie können Teile des Neurons in separate Klassen einkapseln und diese Teile für die tatsächlichen Neuronen, die Sie modellieren, wiederverwenden. Das heißt, Sie könnten separate Klassen zum Modellieren einer Synapse, eines Axons, des Somas usw. schreiben.
quelle
Sie könnten vielleicht ein Python "dict" -Objekt verwenden? http://docs.python.org/tutorial/datastructures.html#dictionaries
quelle
Wenn so viele Parameter vorhanden sind, deutet dies darauf hin, dass die Klasse wahrscheinlich zu viele Dinge tut.
Ich schlage vor, dass Sie Ihre Klasse in mehrere Klassen aufteilen möchten, von denen jede einige Ihrer Parameter übernimmt. Auf diese Weise ist jede Klasse einfacher und nimmt nicht so viele Parameter an.
Ohne mehr über Ihren Code zu wissen, kann ich nicht genau sagen, wie Sie ihn aufteilen sollen.
quelle
Sieht so aus, als könnten Sie die Anzahl der Argumente verringern, indem Sie Objekte wie z
Axon
.Soma
undDendriticTree
außerhalb des LayerV Konstruktor und Leiten diese Objekte statt.Einige der Parameter werden nur beim Konstruieren verwendet, z. B.
DendriticTree
andere auch an anderen Stellen. Das Problem ist also nicht so eindeutig, aber ich würde diesen Ansatz definitiv versuchen.quelle
Können Sie einen Beispielcode für Ihre Arbeit angeben? Es wäre hilfreich, sich ein Bild davon zu machen, was Sie tun, und Ihnen früher zu helfen.
Wenn es nur die Argumente sind, die Sie an die Klasse übergeben, die es lang machen, müssen Sie nicht alles eingeben
__init__
. Sie können die Parameter festlegen, nachdem Sie die Klasse erstellt haben, oder ein Wörterbuch / eine Klasse mit den 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 __name__ == "__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])
quelle
self.__dict__ = kwargs
ist eine schlechte Idee, wie Sie erwähnen. In meiner Antwort gibt es eine einfache Lösung für dieses Problem.if hasattr(self, key):
self.__dict__.keys()
Nachdem ich Ihren Code durchgesehen und festgestellt habe, dass ich keine Ahnung habe, in welcher Beziehung diese Parameter zueinander stehen (allein aufgrund meines Mangels an Kenntnissen zum Thema Neurowissenschaften), möchte ich Sie auf ein sehr gutes Buch über objektorientiertes Design verweisen. Der Aufbau von Fähigkeiten in objektorientiertem Design von Steven F. Lott ist eine ausgezeichnete Lektüre, und ich denke, sie würde Ihnen und allen anderen bei der Gestaltung objektorientierter Programme helfen.
Es wird unter der Creative Commons-Lizenz veröffentlicht und kann daher kostenlos verwendet werden. Hier finden Sie einen Link im PDF-Format: http://homepage.mac.com/s_lott/books/oodesign/build-python/latex/BuildingSkillsinOODesign. 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 zum Initialisieren, und die meisten Antworten hier enthalten detaillierte andere Initialisierungsmethoden. In vielen Fällen können Sie die Klasse jedoch in einfacher zu handhabende und weniger umständliche Klassen aufteilen .
quelle
Dies ähnelt den anderen Lösungen, die ein Standardwörterbuch durchlaufen, verwendet jedoch eine kompaktere Notation:
class MyClass(object): def __init__(self, **kwargs): self.__dict__.update(dict( arg1=123, arg2=345, arg3=678, ), **kwargs)
quelle
Können Sie einen detaillierteren Anwendungsfall angeben? Vielleicht funktioniert ein Prototypmuster:
Wenn es Ähnlichkeiten in Objektgruppen gibt, kann ein Prototypmuster hilfreich sein. Haben Sie viele Fälle, in denen eine Population von Neuronen genau wie eine andere ist, nur dass sie sich in irgendeiner Weise unterscheidet? (dh anstatt eine kleine Anzahl diskreter Klassen zu haben, haben Sie eine große Anzahl von Klassen, die sich geringfügig voneinander unterscheiden.)
Python ist eine klassenbasierte Sprache, aber genau wie Sie die 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 erstellt und dessen Ivars vom übergeordneten Objekt aus auffüllt. Schreiben Sie die Klonmethode so, dass an sie übergebene Schlüsselwortparameter die "geerbten" Parameter überschreiben, sodass Sie sie wie folgt aufrufen können:
quelle
Ich habe mich nie mit dieser Situation oder diesem Thema befasst. Ihre Beschreibung impliziert für mich, dass Sie bei der Entwicklung des Designs möglicherweise feststellen, dass eine Reihe zusätzlicher Klassen relevant werden - das Fach ist am offensichtlichsten. Wenn diese als eigenständige Klassen auftreten, werden wahrscheinlich einige Ihrer Parameter zu Parametern dieser zusätzlichen Klassen.
quelle
Sie können eine Klasse für Ihre Parameter erstellen.
Anstatt eine Reihe von Parametern zu übergeben, übergeben Sie eine Klasse.
quelle
Meiner Meinung nach besteht in Ihrem Fall die einfache Lösung darin, Objekte höherer Ordnung als Parameter zu übergeben.
Zum Beispiel haben Sie in Ihrer
__init__
eineDendriticTree
, die mehrere Argumente aus Ihrer Hauptklasse verwendetLayerV
: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
LayerV
Sie zu übergeben, würden Sie dasDendriticTree
Objekt direkt übergeben (wodurch 5 Argumente gespeichert werden).Sie möchten wahrscheinlich, dass diese Werte überall verfügbar sind, daher müssen Sie diese speichern
DendriticTree
: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 die Standardeinstellungen
DendriticTree
an die Klasse, die dieser Angelegenheit gewidmet ist, anstatt diese Logik in der Klasse höherer Ordnung zu habenLayerV
.Schließlich, wenn Sie auf das zugreifen müssen, an das
apical_bifibs
Sie übergeben habenLayerV
Sie übergeben haben, greifen Sie einfach über zuself.main_apical_dendrite.bifibs
.Selbst wenn die von Ihnen erstellte Klasse keine klare Zusammensetzung mehrerer Klassen ist, besteht Ihr Ziel im Allgemeinen darin, einen logischen Weg zu finden, um Ihre Parameter aufzuteilen. Nicht nur, um Ihren Code sauberer zu machen, sondern vor allem, um zu verstehen, wofür diese Parameter verwendet werden. In den extremen Fällen, in denen Sie sie nicht teilen können, ist es meiner Meinung nach völlig in Ordnung, eine Klasse mit so vielen Parametern zu haben. Wenn es keine klare Möglichkeit gibt, Argumente zu teilen, erhalten Sie wahrscheinlich etwas, das noch 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 des Guten ist, können Sie einfach
collections.namedtuple
die hier gezeigten Standardwerte verwenden .quelle
Ich möchte wiederholen, was einige Leute gesagt haben. An dieser Anzahl von Parametern ist nichts auszusetzen. Besonders wenn es um wissenschaftliches Rechnen / Programmieren geht
Nehmen Sie zum Beispiel die KMeans ++ - Clustering-Implementierung von sklearn, die 11 Parameter enthält, mit denen Sie initiieren können. So gibt es zahlreiche Beispiele und nichts falsch daran
quelle