Python设计模式实现函数的不同变体

时间:2018-01-05 21:22:30

标签: python design-patterns

我有一个通用的平滑功能,并且基于一个配置文件(将作为字典加载的yaml),将调用不同的实现(boxcar或gaussian)。这些实现具有不同数量的参数,例如boxcar需要winsize,而高斯需要winsize和variance。

这是我目前的实施:

def smoothing(dataDf, selected_columns, kwargs):

    method = kwargs['method']

    if method == 'boxcar':
        boxcar(dataDf, selected_columns, kwargs['arguments'])
    elif method == 'gaussian':
        gaussian(dataDf, selected_columns, kwargs['arguments'])
    else:
        raise NotImplementedError

有没有更好的方法来实现这个?

4 个答案:

答案 0 :(得分:2)

我会考虑两个选项

  1. 使用函数字典:

    methods = {
        'boxcar': function1,
        'gaussian': function2
    }
    
    try:
        method = methods[kwargs['method']]
        ...
    except KeyError:
        raise NotImplementedError
    

    你可以使它更加用户友好

    def smoothing(dataDf, selected_columns, method, *args, **kwargs):
        try:
            return methods[kwargs['method']](
                dataDf, selected_columns, *args, **kwargs
            )
        except KeyError:
            raise NotImplementedError('{} is not a valid method'.format(method))
    
  2. 使用multiple dispatch。它允许您分发函数签名和类型

    In [1]: from multipledispatch import dispatch
    
    In [2]: @dispatch(int, int)
       ...: def f(x, y):
       ...:     return x, y
       ...: 
    
    In [3]: @dispatch(int)
       ...: def f(x):
       ...:     return x
       ...: 
    
    In [5]: f(1)
    Out[5]: 1
    
    In [6]: f(1, 2)
    Out[6]: (1, 2)
    

    在你的情况下

    @dispatch(...list some/all argument types here...)
    def smoothing(...signature for boxcar...):
        pass
    
    @dispatch(...list some/all argument types here...)
    def smoothing(...signature for gaussian...)
        pass
    

答案 1 :(得分:1)

我会说这个问题是基于主要意见的,但我有点......很无聊,所以这是我的看法:

在这些情况下,我总是倾向于优先考虑其他用户的可读性。我们都知道代码应该被正确评论,解释,周围有很多文档,对吗?但我也应该去健身房然而,在这里,我是从沙发的舒适性中写出来的(我不打算放弃,直到4月中旬,或多或少,当天气好转时。)

对我来说,如果其他人要阅读您的代码,我认为利用Python这一事实非常重要,如果编写得当,可以非常非常清楚(这几乎就像运行的伪代码一样,对吧?)

所以在你的情况下,我甚至不会创建这种包装函数。我会有一个包含所有平滑函数的模块smoothing.py。不仅如此,我还会导入模块(import smoothing)而不是from smoothing import boxcar, gaussian),因此我可以非常明确调用我的电话:

if method == 'boxcar':
   smoothing.boxcar(whatever whatever...)  
   # Someone reading this will be able to figure out that is an smoothing
   # function from a module called `"smoothing"`. Also, no magic done with
   # things like my_func = getattr(smoothing, method)... none of that: be
   # clear and explicit. 
   # For instance, many IDEs allow you to navigate to the function's
   # definition, but for that to work properly, the code needs to be explicit 
elif method == 'gaussian':
   smoothing.gaussian(whatever whatever...)
else:
   raise ValueError(
          'Unknown smoothing method "%s".'
          ' Please see available method in "%s" or add'
          ' a new smoothing entry into %s' % (
                 method, 
                 os.path.abspath(smoothing.__file__), 
                 os.path.abspath(__file__)
          )
   )

这样的事情。如果有人收到错误,可以快速了解它发生的地点和原因。

否则,如果你仍然希望保留你的结构,我会说,因为你总是需要你的'方法',所以不要把它放到你的kwargs中。使其成为位置:

def smoothing(method, dataDf, selected_columns, kwargs):
    if method == 'boxcar':
        boxcar(dataDf, selected_columns, kwargs['arguments'])
    elif method == 'gaussian':
        gaussian(dataDf, selected_columns, kwargs['arguments'])
    else:
        raise NotImplementedError

你可以做的另一件事是,而不是在kwargs dict中有可能有坏参数,强制它包含正确的参数(如果有人传入kwarg['arguments']参数{{ 1}}但是为method=boxcar提供了kwarg['arguments']?不要让它成为可能(让它尽快崩溃):

gaussian

并始终在您的例外中提供正确的讯息(对您的def smoothing(method, dataDf, selected_columns, **kwargs): if method == 'boxcar': assert 'variance' not in kwargs # If `boxcar` shouldn't have a "variance" boxcar(dataDf, selected_columns, kwargs['windsize']) elif method == 'gaussian': gaussian(dataDf, selected_columns, kwargs['windsize'], kwargs['variance']) else: raise NotImplementedError

提供正确的解释)

你可以用Python做很多的“魔法”。这并不意味着你必须这样做。例如,通过编写类似这样的内容,您可以获得与NotImplementedError函数的实现相似的行为:

smoothing

但是如果有人读到那个......那么......祝福那个人: - P

答案 2 :(得分:1)

methods = {
    'boxcar': boxcar,
    'gaussian': gaussian,
}

MESSAGES = {
    'MISSING_METHOD': 'No method named {} was found.'
}

def smooth(dataDf, selected_columns, **kwargs):
    """Smooth dataframe columns."""
    # Here, we are providing a default if the
    # user doesn't provide a keyword argument
    # for `method`. You're accessing it with
    # brackets and if it's not provided it will
    # raise a KeyError. If it's mandatory, put
    # it as such. But you can do better by
    # providing a default. Like below

    method_name = kwargs.get('method', 'gaussian')
    method = methods.get(method_name)

    if method is None:
        msg = MESSAGES['MISSING_METHOD'].format(method_name)
        raise NotImplementedError(msg)
    return method(dataDf, selected_columns, **kwargs)

如果method是调用的一个相当重要的部分,并且用户知道该参数,则可以按如下方式编写:

def smooth(dataDf, selected_columns, method='gaussian', **kwargs):
    """Smooth dataframe columns."""
    # Note that kwargs no longer contains `method`;
    # our call to the 'real' method is not 'polluted'.

    _method = methods.get(method)

    if _method is None:
        msg = MESSAGES['MISSING_METHOD'].format(method)
        raise NotImplementedError(msg)
    return _method(dataDf, selected_columns, **kwargs)

但是,methods字典中存在问题:

  • 它的键是函数名,值是函数对象,这要求我们可以访问函数。

您希望获得一个字典,其中键和值是字符串(例如您从YAML文件中获取的字符串)。

我将做的假设是当前上下文中存在函数,无论您是定义它们还是导入它们。

from third.party.library import really_ugly_name_you_didnt_choose_but_imported

def gaussian(dataDf, selected_columns, **kwargs):
    pass

_methods = globals()

# This is a dictionary where keys and values
# only contain strings, like from a YAML file.

methods = {
    'boxcar': 'boxcar',
    'gaussian': 'gaussian',
    'roundcar': 'really_ugly_name_you_didnt_choose_but_imported',
}

MESSAGES = {
    'MISSING_METHOD': 'No method named {} was found.'
}

def smooth(dataDf, selected_columns, method='gaussian', **kwargs):
    """Smooth dataframe columns."""

    # Lets check if it is in authorized methods
    # We're using globals() and we don't want
    # the user to accidentally use a function
    # that has no relation to smoothing.

    # So we're looking at the dictionary from
    # the YAML file.

    # Let's get the "real name" of the function
    # so if `method` were (str) 'roundcar', `method_name`
    # would be (str) 'really_ugly_name_you_didnt_choose_but_imported'

    method_name = methods.get(method)

    # Now that we have the real name, let's look for
    # the function object, in _methods.

    _method = _methods.get(method_name)

    if None in (_method, method_name):
        msg = MESSAGES['MISSING_METHOD'].format(method)
        # Note that we raise the exception for the function
        # name the user required, i.e: roundcar, not
        # the real function name the user might be unaware
        # of, 'really_ugly_name_you_didnt_choose_but_imported'.
        raise NotImplementedError(msg)
    return _method(dataDf, selected_columns, **kwargs)

答案 3 :(得分:0)

您的算法函数为Strategies。您可以将它们存储在字典中以便于查找。

使用defaultdict“未实施”策略来删除算法:

def algorithm_not_implemented(*args, **kwargs):
    raise NotImplementedError

algorithms = defaultdict(algorithm_not_implemented)

这意味着如果您尝试访问不存在的算法,它将返回algorithm_not_implemented,当您调用它时,它将引发NotImplementedError

>>> algorithms['pete'](1, 2, 3)
Traceback (most recent call last):
NotImplementedError

您可以添加算法:

algorithms['boxcar'] = boxcar
algorithms['gaussian'] = gaussian

你可以打电话给他们:

def smoothing(dataDf, selected_columns, kwargs):
    method = kwargs['method']
    arguments = kwargs['arguments']

    algorithms[method](dataDf, selected_columns, arguments)