81

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

轴突节数、顶端分叉、体细胞长度、体细胞直径、顶端长度、分枝随机性、分枝长度等等……总共有大约15个参数!

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

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

在此处输入图像描述

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
4

14 回答 14

74

更新:这种方法可能适合您的特定情况,但它肯定有其缺点,看看kwargs 是一种反模式吗?

试试这个方法:

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")
于 2011-05-05T14:26:13.300 回答
20

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

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

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

于 2011-05-05T14:26:22.897 回答
7

您也许可以使用 Python"dict" 对象? http://docs.python.org/tutorial/datastructures.html#dictionaries

于 2011-05-05T14:22:04.443 回答
7

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

我建议你想把你的类分成几个类,每个类都接受你的一些参数。这样每个类都更简单,并且不会使用太多参数。

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

于 2011-05-05T15:01:33.920 回答
6

看起来您可以通过在 LayerV 构造函数之外构造 , 等对象来减少参数的数量,并改为传递这些Axon对象SomaDendriticTree

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

于 2017-06-09T11:07:06.627 回答
5

你能提供一些你正在做什么的示例代码吗?这将有助于了解您在做什么并更快地获得帮助。

如果只是你传递给类的参数使它变长,你不必把它全部放在__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])
于 2011-05-05T14:28:44.960 回答
3

在查看了您的代码并意识到我不知道这些参数中的任何一个是如何相互关联的(仅因为我对神经科学这一主题缺乏了解),我会为您指出一本非常好的面向对象设计的书。Steven F. Lott 的《Building Skills in Object Oriented Design》是一本优秀的读物,我认为这对你和其他任何人设计面向对象程序都有帮助。

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

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

于 2011-05-06T12:22:13.000 回答
3

这类似于遍历默认字典的其他解决方案,但它使用更紧凑的符号:

class MyClass(object):

    def __init__(self, **kwargs):
        self.__dict__.update(dict(
            arg1=123,
            arg2=345,
            arg3=678,
        ), **kwargs)
于 2014-03-21T18:07:42.837 回答
1

你能给出更详细的用例吗?也许原型模式会起作用:

如果对象组有一些相似之处,原型模式可能会有所帮助。您是否有很多情况下,一个神经元群除了在某些方面有所不同之外,就和另一个一样?(即不是有少量的离散类,而是有大量彼此略有不同的类。)

Python 是一种基于类的语言,但就像您可以在基于原型的语言(如 Javascript)中模拟基于类的编程一样,您可以通过为您的类提供一个 CLONE 方法来模拟原型,该方法创建一个新对象并从父对象填充其 ivars。编写 clone 方法,以便传递给它的关键字参数覆盖“继承”参数,因此您可以使用以下方式调用它:

new_neuron = old_neuron.clone( branching_length=n1, branching_randomness=r2 )
于 2011-05-05T14:53:00.890 回答
1

我从来没有处理过这种情况,或者这个话题。您的描述向我暗示,在您开发设计时,您可能会发现有许多其他类将变得相关 - 隔间是最明显的。如果这些确实以类的形式出现,那么您的某些参数很可能成为这些附加类的参数。

于 2011-05-05T15:32:19.603 回答
0

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

而不是传递一堆参数,你传递一个类。

于 2011-05-05T14:21:06.487 回答
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
)

与其将这 6 个参数传递给您,不如直接LayerV传递DendriticTree对象(从而节省 5 个参数)。

您可能希望在任何地方都可以访问此值,因此您必须保存它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您曾经传递给LayerV您的内容时,只需通过self.main_apical_dendrite.bifibs.

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

如果您觉得创建一个类来将参数分组在一起是多余的,那么您可以简单地使用collections.namedtuple可以具有默认值的类,如此处所示

于 2019-02-17T11:15:59.237 回答
0

想重申一些人所说的话。这么多参数没有错。尤其是在科学计算/编程方面

以 sklearn 的KMeans++ 集群实现为例,它有 11 个可以初始化的参数。像这样,有很多例子,它们没有错

于 2020-04-28T20:34:05.530 回答
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")
毕竟,如果你有很多“必要”和“no_default”参数,总要想想是不是这个类(方法)做了太多的事情。
如果您的答案不是,请继续

于 2021-04-12T07:46:07.813 回答