Класс со слишком большим количеством параметров: лучшая стратегия дизайна?


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

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

11 ответов:

попробовать такой подход:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

class MyClass(object):

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

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

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

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

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

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

вы можете создать класс для ваших параметров.

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