python - oriented - Clase con demasiados parámetros: ¿mejor estrategia de diseño?
oop python 3 (11)
¿Podrías quizás usar un objeto "dict" de Python? http://docs.python.org/tutorial/datastructures.html#dictionaries
Estoy trabajando con modelos de neuronas. Una clase que estoy diseñando es una clase celular que es una descripción topológica de una neurona (varios compartimentos conectados entre sí). Tiene muchos parámetros, pero todos son relevantes, por ejemplo:
número de segmentos de axón, bifibricaciones apicales, longitud somática, diámetro somático, longitud apical, aleatoriedad de ramificación, longitud de ramificación y así sucesivamente ... ¡hay aproximadamente 15 parámetros en total!
Puedo establecer todos estos valores predeterminados, pero mi clase se ve loca con varias líneas para los parámetros. Este tipo de cosas debe ocurrir ocasionalmente a otras personas también, ¿hay alguna manera mejor obvia de diseñar esto o estoy haciendo lo correcto?
ACTUALIZACIÓN: Como algunos de ustedes me han preguntado, he adjuntado mi código para la clase, como pueden ver, esta clase tiene una gran cantidad de parámetros (> 15) pero todos son utilizados y son necesarios para definir la topología de una celda. El problema es esencialmente que el objeto físico que crean es muy complejo. Adjunto una representación de imagen de objetos producidos por esta clase. ¿Cómo podrían los programadores experimentados hacer esto de manera diferente para evitar tantos parámetros en la definición?
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
¿Puedes dar un caso de uso más detallado? Tal vez un prototipo de patrón funcione:
Si hay algunas similitudes en grupos de objetos, un patrón prototipo podría ayudar. ¿Tiene muchos casos en que una población de neuronas es como otra, excepto diferente de alguna manera? (es decir, en lugar de tener un pequeño número de clases discretas, tiene una gran cantidad de clases que difieren ligeramente entre sí).
Python es un lenguaje basado en clases, pero así como puedes simular la programación basada en clases en un lenguaje basado en prototipos como Javascript, puedes simular prototipos dando a tu clase un método CLONE, que crea un nuevo objeto y llena sus ivars del padre. Escriba el método de clonación para que los parámetros de palabra clave que se le pasen anulen los parámetros "heredados", de modo que pueda llamarlo de la siguiente manera:
new_neuron = old_neuron.clone( branching_length=n1, branching_randomness=r2 )
¿podría proporcionarnos algún código de ejemplo de lo que está trabajando? Sería útil hacerse una idea de lo que está haciendo y recibir ayuda antes.
Si solo son los argumentos que le pasas a la clase lo que hace que sea largo, no tienes que ponerlo todo en __init__
. Puede establecer los parámetros después de crear la clase o pasar un diccionario / clase lleno de parámetros como argumento.
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])
Después de revisar su código y darme cuenta de que no tengo idea de cómo se relacionan esos parámetros entre sí (solo por mi falta de conocimiento sobre el tema de la neurociencia), les señalaría un muy buen libro sobre diseño orientado a objetos. Building Skills in Object Oriented Design por Steven F. Lott es una excelente lectura y creo que le ayudaría a usted, y a cualquier otra persona, a diseñar programas orientados a objetos.
Se distribuye bajo la licencia de Creative Commons, por lo que es gratuito para usted, aquí hay un enlace en formato PDF http://homepage.mac.com/s_lott/books/oodesign/build-python/latex/BuildingSkillsinOODesign.pdf
Creo que tu problema se reduce al diseño general de tus clases. A veces, aunque muy raramente, necesita una gran cantidad de argumentos para inicializar, y la mayoría de las respuestas aquí detallan otras formas de inicialización, pero en muchos casos puede dividir la clase en clases más fáciles de manejar y menos engorrosas. .
Diría que no hay nada de malo en este enfoque: si necesita 15 parámetros para modelar algo, necesita 15 parámetros. Y si no hay un valor predeterminado adecuado, debe ingresar los 15 parámetros al crear un objeto. De lo contrario, podría establecer el valor predeterminado y cambiarlo más tarde a través de un setter o directamente.
Otro enfoque es crear subclases para ciertos tipos comunes de neuronas (en su ejemplo) y proporcionar buenos valores predeterminados para ciertos valores, o derivar los valores de otros parámetros.
O podrías encapsular partes de la neurona en clases separadas y reutilizar estas partes para las neuronas reales que modelas. Es decir, podría escribir clases separadas para modelar una sinapsis, un axón, el soma, etc.
Esto es similar a las otras soluciones que iteran a través de un diccionario predeterminado, pero usa una notación más compacta:
class MyClass(object):
def __init__(self, **kwargs):
self.__dict__.update(dict(
arg1=123,
arg2=345,
arg3=678,
), **kwargs)
Nunca he tenido que lidiar con esta situación o este tema. Su descripción implica para mí que, a medida que desarrolla el diseño, puede encontrar que hay una serie de clases adicionales que serán relevantes: el compartimento es el más obvio. Si estos surgen como clases por derecho propio, es probable que algunos de sus parámetros se conviertan en parámetros de estas clases adicionales.
Parece que podría reducir el número de argumentos al construir objetos como Axon
, Soma
y DendriticTree
fuera del constructor LayerV, y pasar esos objetos en su lugar.
Algunos de los parámetros solo se usan para construir, por ejemplo, DendriticTree
, otros se usan en otros lugares también, por lo que el problema no es tan claro, pero definitivamente probaría ese enfoque.
Podrías crear una clase para tus parámetros.
En lugar de pasar un montón de parámetros, pasas una clase.
Pruebe este enfoque:
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))
A continuación, puede crear una Neuron
como esta:
n = Neuron(apical_bifibrications="special value")
Tener tantos parámetros sugiere que la clase probablemente esté haciendo demasiadas cosas.
Sugiero que quiera dividir su clase en varias clases, cada una de las cuales toma algunos de sus parámetros. De esta forma, cada clase es más simple y no tomará tantos parámetros.
Sin saber más sobre tu código, no puedo decir exactamente cómo debes dividirlo.