如何以符合人体工程学的方式迭代具有不同签名的函数

时间:2017-04-21 04:54:35

标签: python function design-patterns functor signature

我有一个"基因型"含有基因的种类这些基因所代表的并不重要,它们只是任意对象,可以全部被引用为"基因对象"。

我需要通过几种方法改变这个基因,但不是所有的功能签名都匹配。给定起始基因,创建新基因,随机选择这些方法之一(或无方法)进行突变。

例如,我有duplicate(gene)replace(gene, othergene)insert(gene, othergene)delete(gene)othermutation(gene, genotype)。所有这些都返回一个基因列表(即使列表只包含一个元素,或零元素),以保持函数签名之间的同质性。

我想将这种情况概括为这些突变函数的列表以及相关的百分比机会。我已经有通过二元搜索和累积分布选择这些基因的方法,我生成一个R并且可以根据来自R的舍入二进制索引检索正确的函数。这大致允许我做以下事情:

def mutate(genotype, mutation_list, cumulative_probabilities)
    mutated_genotype = []
    for gene in genotype:
        r = random.random()
        mutation = mutation_list(cumulative_probabilities(r))
        mutated_genotype.extend(mutation(gene))
    return mutated_genotype

理想情况下,我不需要知道第五行的突变,我只需要在某处具有相关概率的突变列表。如您所见,replace(gene, othergene)需要第二个参数会发生什么?或othermutation(gene, genotype)哪个需要不同但又单独的参数?

为了解决这个问题,我找到了几个解决方案。首先,我可以将所有功能签名同化为完全相同。我的意思是,即使duplicate(gene)不需要othergene,我仍然会将其放在函数定义中,它只是不会使用它,或者它会做一些微不足道的事情用它。但是这个解决方案的缺点是,每次我需要添加一个带有新参数的新函数时,我都需要更改所有函数签名,这对于所有函数都会以类的方式违反SRP并且会令人讨厌处理。但我可以将它包装为一个参数对象,我在每次迭代时设置所需的参数。如果需要一个带有新类型参数的新函数,我可以简单地将该参数添加到"变量参数"对象,并将此对象传递给每个函数,每个函数只能得到它所需要的东西,例如:

for gene in genotype:
    #note other gene is created from some other function or is a generator itself
    mutation_parameter = MutationParameter(gene, genotype, othergene)
    r = random.random()
    mutation = mutation_list(cumulative_probabilities(r))
    mutated_genotype.extend(mutation(mutation_parameter))

这里的粘合剂是MutationParameter的成员不一定都是彼此相关的,我们必须知道突变签名是什么,你不会只是能够添加新签名,需要更新多个代码段。

我可以解决这个问题的另一种方法是我可以使函数参数通用,但是这样我就会被迫添加一个额外的数据结构来处理将数据提取到函数签名中(这样所有函数都需要{{ 1}}或*args)这可能意味着每个签名的定制函数和性能下降,因为需要线性化或将累积概率与散列表/字典相关联。

我可以通过编写函数来处理这些函数,这些函数存储调用函数中的参数的一些数据'本身(例如"其他基因"如同随机产生基因的发生器)。这将要求我为每个函数创建一个新类,以便为每个需要唯一参数的函数处理这种情况。即使在那时,我也无法将当前的基因列表**kwargs放入genotype而不会在执行函数本身时创建函数列表(所以不会所有的仿函数都不能作为othermutation中的mutation_list传入。

如果我想要一个处理泛型mutate的好方法,我可能只需点击子弹并将一些需要某些类型信息的突变类型分开,我可以直接传入两个突变列表的类型。

我还可以做一个类似的事情,例如,将othergene参数作为一个静态函数/变量给所有实例化的仿函数。

总结三种方法:

  1. 传递via参数对象中的所有参数,函数选择并选择所需的参数;

  2. 使用* args有效地做同样的事情,但是你还需要一个相关的函数来为每个签名提取数据;

  3. 或使用仿函数(带或不带静态变量)来保存数据,使用另一个单独传入的变异函数突变列表,其中在mutation_list被调用之前无法确定额外的参数

  4. 我想避免让我的mutate函数关注潜在的突变是什么,这样它就可以是通用的。

3 个答案:

答案 0 :(得分:0)

一个合理的策略是用另一个提供但不使用另一个参数的函数包装一个参数的函数:

def dup2(gene, othergene):
    return duplicate(gene)

def del2(gene, othergene):
    return delete(gene)

这将使两个较短的签名与其他功能匹配:replace(gene, othergene), insert(gene, othergene), othermutation(gene, genotype)

另一种策略是使用try/except来测试可能性:

try:
    return func(gene, othergene)     # try two-arguments
except TypeError:
    return func(gene)                # try one-argument

答案 1 :(得分:0)

我看到的问题是:你怎么知道为每个函数调用提供什么参数,因为它们没有相同的签名?我认为有一种很好的方法可以避免提供你不需要的参数,并以一种可理解的方式构造程序:使用类而不是简单的函数。类提供了一种检查工作函数所需的参数数量和类型的方法。您可以将此代码分解为基类。例如:

class MutatorBase:
   def __init__(self, needs_othergene=False, needs_genotype=False):
        self.needs_othergene = needs_othergene
        self.needs_genotype = needs_genotype

   def f(self, gene, othergene=None, genotype=None):
        """This is the function that does the actual calculations"""
        raise NotImplementedError

现在,您可以创建一组实现函数f的派生类。在构造函数中,您可以设置变量needs_othergeneneeds_genotype。例如:

class Duplicator(MutatorBase):
    def __init__(self):
        super().__init__()

    def f(self, gene):
        return duplicate(gene)

class Replacer(MutatorBase):
    def __init__(self):
        super().__init__(needs_othergene=True)

    def f(self, gene, othergene=g):
        return replace(gene, g)

在调用其中一个之前,说它名为replacer.f之前,检查变量以查看需要哪种参数。您可以适当地设置函数调用。您可以将概率与一组类相关联,而不是将概率与一组函数相关联。在Python中,它们同样容易。

答案 2 :(得分:0)

我要回答我自己的问题,因为我发现目前的答案不尽如人意(并且与我建议的答案几乎完全相同),但我发现这个解决方案是最多的"符合人体工程学的"对于我的具体情况(最通用的等,形成良好和可扩展)。

我的第一个建议,

  

1:传递via参数对象中的所有参数,函数pick和   选择他们需要的参数

我发现对我来说是最好的,甚至可能在Python之外和非动态语言中。这基本上是这样的:

class MutationParameters:
    def __init__(self, param1, param2, param3 ...):
        self.param1 = param1
        self.param2 = param2
        self.param3 = param3
        ...

def mutate(genes, mutator_generator, otherparams...):
    # paraminit...
    new_genes = []
    mutation_params = MutationParameters(param1, param2, param3...)
    for gene in genes:
        # runtime param init ...
        mutation_params.paramn = paramn #runtime param
        new_genes = next(mutator_generator)(mutation_params)
        new_genes.extend(new_genes)
    return StackGenotype(new_genes)

如果需要新的参数?更新MutationParameters,更新mutate一次,不需要更改所有其他函数的参数(因此为什么使用参数并忽略一些不是一个好主意,类似于functor)。添加新功能?只需要一个变量,并在其中查找所需的参数。这也适用于c ++。

我相信,除非有人发布更好的答案,否则这是我问题的最佳解决方案,我会将此标记为答案,直到出现更好的答案。