参数太多的类:更好的设计策略?

时间:2011-05-05 14:16:30

标签: python oop neuroscience

我正在使用神经元模型。我正在设计的一个类是一个细胞类,它是神经元的拓扑描述(几个连接在一起的隔室)。它有许多参数,但它们都是相关的,例如:

轴突段数,顶端双分裂,体细胞长度,体细胞直径,顶端长度,分枝随机性,分枝长度等等...总共有大约15个参数!

我可以将所有这些设置为某个默认值,但我的课看起来很疯狂,有几行参数。这种事情也偶尔会发生在其他人身上,是否有一些明显更好的方法来设计这个或者我做对了吗?

更新 正如你们中的一些人已经要求我为该类附加了我的代码,因为你可以看到这个类有大量的参数(> 15)但它们都被使用并且是定义单元格拓扑所必需的。问题主要在于他们创建的物理对象非常复杂。我附上了这个类产生的对象的图像表示。有经验的程序员如何以不同的方式做到这一点,以避免定义中有如此多的参数?

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

14 个答案:

答案 0 :(得分:62)

尝试这种方法:

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

然后您可以像这样创建Neuron

n = Neuron(apical_bifibrications="special value")

答案 1 :(得分:16)

我认为这种方法没有任何问题 - 如果你需要15个参数来建模,你需要15个参数。如果没有合适的默认值,则在创建对象时必须传入所有15个参数。否则,您可以设置默认值,稍后通过设置器或直接更改它。

另一种方法是为某些常见类型的神经元创建子类(在您的示例中),并为某些值提供良好的默认值,或从其他参数中获取值。

或者您可以将神经元的某些部分封装在不同的类中,并将这些部分重用于您建模的实际神经元。即,你可以编写单独的类来建模突触,轴突,躯体等。

答案 2 :(得分:7)

您可以使用Python“dict”对象吗? http://docs.python.org/tutorial/datastructures.html#dictionaries

答案 3 :(得分:6)

有这么多参数表明这个课可能做了太多事情。

我建议你想把你的班级划分为几个班级,每个班级都需要你的一些参数。这样每个类都更简单,不会占用太多参数。

在不了解您的代码的情况下,我无法确切地说您应该如何拆分它。

答案 4 :(得分:5)

看起来你可以通过在LayerV构造函数之外构造诸如AxonSomaDendriticTree之类的对象来减少参数的数量,并改为传递这些对象。

某些参数仅用于构建例如DendriticTree,其他地方也使用其他地方,所以问题不是那么明确,但我肯定会尝试这种方法。

答案 5 :(得分:4)

您能提供一些您正在处理的示例代码吗?这将有助于了解您正在做什么,并尽快得到帮助。

如果只是你传递给类的参数使它变长,你就不必把它全部放在__init__中。您可以在创建类之后设置参数,或者将充满参数的字典/类作为参数传递。

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

答案 6 :(得分:3)

在查看你的代码并意识到我不知道这些参数之间是如何相互关联的(因为我对神经科学的主题缺乏知识而烦恼)我会指出一本关于面向对象设计的非常好的书。 Steven F. Lott的面向对象设计中的建筑技巧是一本很好的读物,我认为它可以帮助你和其他任何人设计面向对象的程序。

它是根据知识共享许可证发布的,因此您可以免费使用,以下是PDF格式的链接http://homepage.mac.com/s_lott/books/oodesign/build-python/latex/BuildingSkillsinOODesign.pdf

我认为您的问题可以归结为课程的整体设计。有时,虽然很少,但是你需要大量的参数来初始化,而且这里的大多数响应都有详细的其他初始化方法,但是在很多情况下你可以把类分解成更容易处理和不那么繁琐的类

答案 7 :(得分:2)

这类似于迭代默认字典的其他解决方案,但它使用更紧凑的表示法:

class MyClass(object):

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

答案 8 :(得分:1)

您能提供更详细的用例吗?也许原型模式可行:

如果对象组中存在某些相似之处,原型模式可能有所帮助。 你是否有很多情况下,一群神经元就像另一群神经元在某种程度上不同? (即,而不是少数离散类, 你有大量的课程彼此略有不同。 )

Python是一种基于分类的语言,但正如您可以模拟基于类的语言一样 在基于原型的语言(如Javascript)中编程,您可以模拟 原型通过为您的类提供一个CLONE方法,创建一个新对象和 从父母那里填充其ivars。编写克隆方法以便关键字参数 传递给它覆盖“继承”参数,所以你可以用它来调用它 喜欢:

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

答案 9 :(得分:1)

我从来没有处理过这种情况或这个话题。您的描述意味着,在您开发设计时,您可能会发现有许多其他类别会变得相关 - 隔离区是最明显的。如果它们本身就是作为类出现的,那么很可能你的一些参数成为这些附加类的参数。

答案 10 :(得分:0)

您可以为参数创建一个类。

相反,传递一堆参数,你传递一个类。

答案 11 :(得分:0)

我认为,在您的情况下,简单的解决方案是将高阶对象作为参数传递。

例如,在您的__init__中,您有一个DendriticTree,它使用来自主类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
)

您可以直接传递LayerV对象(从而节省5个参数),而不是将这6个参数传递给DendriticTree

您可能希望在任何地方都可以访问此值,因此必须保存此DendriticTree

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

如果您也要使用默认值,则可以设置:

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

通过这种方式,您可以将默认的DendriticTree委派给专门用于解决此问题的类,而不是将此逻辑放在LayerV的高阶类中。

最后,当您需要访问曾经传递给apical_bifibs的{​​{1}}时,只需通过LayerV对其进行访问即可。

通常,即使您要创建的类不是由几个类组成的清晰组合,您的目标仍然是找到一种逻辑方法来拆分参数。不仅使您的代码更整洁,而且主要是为了帮助人们了解这些参数的用途。在无法拆分它们的极端情况下,我认为拥有一个具有这么多参数的类是完全可以的。如果没有清晰的方法来拆分参数,那么最终可能会得到比15个参数列表更不清晰的结果。

如果您觉得创建一个将参数分组在一起的类是过大的选择,则可以简单地使用collections.namedtuple,它可以将默认值设置为shown here

答案 12 :(得分:0)

想重申一些人所说的话。如此多的参数没有错。特别是在科学计算/编程方面

例如,sklearn的KMeans++ clustering implementation具有11个可以初始化的参数。那样,有很多例子,没有错

答案 13 :(得分:0)

如果确保您需要这些参数,我会说没有错。如果你真的想让它更具可读性,我会推荐以下风格。
我不会说这是最佳实践或什么,它只是让其他人很容易地知道这个对象需要什么以及什么是选项。

class LayerV(__Cell):
    # author: {name, url} who made this info
    def __init__(self, no_default_params, some_necessary_params):
        self.necessary_param = some_necessary_params
        self.no_default_param = no_default_params
        self.something_else = "default"
        self.some_option = "default"
    
    def b_option(self, value):
        self.some_option = value
        return self

    def b_else(self, value):
        self.something_else = value
        return self

我认为这种风格的好处是:

  1. 您可以轻松知道__init__ 方法中所需的参数
  2. setter 不同,如果需要设置选项值,则不需要两行来构造对象。


缺点是,您在类中创建了比以前更多的方法。

示例: la = LayerV("no_default", "necessary").b_else("sample_else")
毕竟,如果你有很多“necessary”和“no_default”参数,总想着是不是这个类(方法)做的事情太多了。
如果您的答案不是,请继续