Класс со слишком многими параметрами: лучшая стратегия дизайна? - PullRequest
64 голосов
/ 05 мая 2011

Я работаю с моделями нейронов.Один класс, который я проектирую, - это класс клеток, который представляет собой топологическое описание нейрона (несколько отделений, соединенных вместе).У него много параметров, но все они релевантны, например:

количество сегментов аксона, апикальные бифибрикации, соматическая длина, соматический диаметр, апикальная длина, случайность ветвления, длина ветвления и т. Д. И т. Д. И т. Д. И т. Д ...всего около 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

Ответы [ 12 ]

62 голосов
/ 05 мая 2011

Попробуйте этот подход:

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")
16 голосов
/ 05 мая 2011

Я бы сказал, что в этом подходе нет ничего плохого - если вам нужно 15 параметров для моделирования чего-либо, вам нужно 15 параметров. И если нет подходящего значения по умолчанию, вы должны передать все 15 параметров при создании объекта. В противном случае вы можете просто установить значение по умолчанию и изменить его позже с помощью установщика или напрямую.

Другой подход заключается в создании подклассов для определенных распространенных типов нейронов (в вашем примере) и обеспечении хороших значений по умолчанию для определенных значений или получения значений из других параметров.

Или вы можете заключить части нейрона в отдельные классы и повторно использовать эти части для реальных нейронов, которые вы моделируете. Т.е. вы могли бы написать отдельные классы для моделирования синапса, аксона, сомы и т. Д.

7 голосов
/ 05 мая 2011

Возможно, вы могли бы использовать объект Python "dict"? http://docs.python.org/tutorial/datastructures.html#dictionaries

6 голосов
/ 05 мая 2011

Наличие большого количества параметров предполагает, что класс, вероятно, делает слишком много вещей.

Я предлагаю вам разделить ваш класс на несколько классов, каждый из которых принимает некоторые из ваших параметров. Таким образом, каждый класс проще и не будет принимать столько параметров.

Не зная больше о вашем коде, я не могу точно сказать, как вы должны его разделить.

5 голосов
/ 09 июня 2017

Похоже, вы могли бы сократить количество аргументов, создав такие объекты, как Axon, Soma и DendriticTree вне конструктора LayerV, и передав эти объекты вместо этого.

Некоторые параметры используются только при построении, например, DendriticTree, другие используются и в других местах, поэтому проблема не столь ясна, но я бы определенно попробовал такой подход.

4 голосов
/ 05 мая 2011

не могли бы вы привести пример кода того, над чем вы работаете?Это помогло бы получить представление о том, что вы делаете, и получить помощь вам раньше.

Если это просто аргументы, которые вы передаете классу, которые делают его длинным, вам не нужно ставить все этов __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])
3 голосов
/ 06 мая 2011

После просмотра вашего кода и понимания того, что я не знаю, как эти параметры связаны друг с другом (просто из-за недостатка знаний по нейробиологии), я бы указал на очень хорошую книгу по объектно-ориентированному дизайну.,Создание навыков в объектно-ориентированном дизайне Стивена Ф. Лотта - отличное чтение, и я думаю, что оно поможет вам и всем остальным в разработке объектно-ориентированных программ.

Он выпущен по лицензии Creative Commons, поэтому является бесплатнымдля вас, вот ссылка на него в формате PDF http://homepage.mac.com/s_lott/books/oodesign/build-python/latex/BuildingSkillsinOODesign.pdf

Я думаю, что ваша проблема сводится к общему дизайну ваших классов.Иногда, хотя и очень редко, вам нужно много аргументов для инициализации, и большинство ответов здесь подробно описывают другие способы инициализации, но во многих случаях вы можете разбить класс на более простые в обращении и менее громоздкие классы..

2 голосов
/ 21 марта 2014

Это похоже на другие решения, которые перебирают словарь по умолчанию, но используют более компактную запись:

class MyClass(object):

    def __init__(self, **kwargs):
        self.__dict__.update(dict(
            arg1=123,
            arg2=345,
            arg3=678,
        ), **kwargs)
1 голос
/ 05 мая 2011

Мне никогда не приходилось сталкиваться с этой ситуацией или этой темой. Ваше описание подразумевает для меня, что при разработке дизайна вы можете обнаружить, что есть ряд дополнительных классов, которые станут актуальными - купе является наиболее очевидным. Если они появляются как классы сами по себе, вероятно, некоторые из ваших параметров станут параметрами этих дополнительных классов.

1 голос
/ 05 мая 2011

Можете ли вы привести более детальный пример использования? Возможно, прототип будет работать:

Если в группах объектов есть некоторые сходства, может помочь шаблон прототипа. Много ли у вас случаев, когда одна популяция нейронов похожа на другую, но отличается в некотором роде? (т.е. вместо того, чтобы иметь небольшое количество дискретных классов, у вас есть большое количество классов, которые немного отличаются друг от друга. )

Python является классом на основе языка, но так же, как вы можете моделировать на основе классов программируя на языке прототипов, таких как Javascript, вы можете моделировать прототипы, предоставив вашему классу метод CLONE, который создает новый объект и заполняет свои ивары от родителя. Напишите метод клонирования, чтобы параметры ключевого слова переданный ему переопределяет "унаследованные" параметры, так что вы можете вызвать его с чем-то как:

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