Python中的函数模式/谓词匹配

时间:2015-12-22 22:39:37

标签: python libraries dynamic-dispatch

我希望能够根据第一个参数的类型,而不是基于任意谓词来分派函数的不同实现。目前我必须这样做:

HKSample

这里有一些本着我能够做的事情的精神:

def f(param):
    try:
        if param > 0:
            # do something
    except TypeError:
        pass
    try:
        if all(isinstance(item, str) for item in param):
            # do something else
    except TypeError:
        raise TypeError('Illegal input.')

它类似于Python 3的@generic def f(param): raise TypeError('Illegal input.') # default @f.when(lambda param: param > 0) def f_when_param_positive(param): # do something @f.when(lambda param: all(isinstance(item, str) for item in param)) def f_when_param_iterable_of_strings(param): # do something else ,但singledispatch仅支持对类型的调度,而不支持任意谓词。

TL; DR:是否有一个库允许基于任意谓词(不仅是参数类型)的基于谓词的函数调度?

4 个答案:

答案 0 :(得分:3)

感谢回复者。在提出这个问题之后,似乎没有现成的模块可以做到这一点。所以我写了自己的:)它的灵感来自@Elazar的建议。

请随时查看。 It's on PyPI您可以使用以下命令安装它:

pip install genericfuncs

它也是hosted on GitHub,我计划在尝试保持API简单的同时继续开发和添加功能。欢迎提供捐助。

答案 1 :(得分:2)

这是从@ Yam调整的解决方案,以适合您的语法,并用作库。决定(这是一个常见的)是第一个谓词获胜:

class guarded:
    def __init__(self, default):
        self.funcs = []
        self.default = default

    def when(self, pred):
        def add(func):
            self.funcs.append( (pred, func) )
            return func
        return add

    def __call__(self, *args, **kwargs):
        for pred, func in self.funcs:
            try:  
                match = pred(*args, **kwargs)
            except Exception:
                match = False
            if match:
                return func(*args, **kwargs)
        return self.default(*args, **kwargs)

用户代码:

@guarded
def f(param):
    raise TypeError('Illegal input')

@f.when(lambda param: param > 0)
def f_when_param_positive(param):
    return 'param_positive'

@f.when(lambda param: all(isinstance(item, str) for item in param))
def f_when_param_iterable_of_strings(param):
    return 'param_iterable_of_strings'

尝试一下,我们得到类似的东西:

>>> print(f(123))
param_positive
>>> print(f(['a', 'b']))
param_iterable_of_strings
>>> print(f(-123))
Traceback (most recent call last):
...
TypeError: Illegal input

答案 2 :(得分:1)

我不知道库,但这是一个基本的实现框架。在我看来,阻止这种实际解决方案的真正问题是我不知道如何在这里进行专门的解决方案 1 。在这种情况下,它可能会导致很多维护困难。

#!/usr/bin/python3  

class when(object):
  funcs = {}

  def __init__(self, pred):
    self.pred = pred

  def __call__(self, func):
    if func.__qualname__ not in when.funcs:
        when.funcs[func.__qualname__] = {}

    when.funcs[func.__qualname__][self.pred] = func

    return lambda *args, **kwargs: when.__match(func, *args, **kwargs)

  @staticmethod
  def __match(f, *args, **kwargs):
    for pred, func in when.funcs[f.__qualname__].items():
      if pred(*args, **kwargs):
          return func(*args, **kwargs)
    raise NotImplementedError()


@when(lambda x: x < 0)
def my_func(x):
  return "smaller!"

@when(lambda x: x > 0)
def my_func(x):
  return "greater!"


print(my_func(-123))
print(my_func(123))

[1]:解决方案的问题在于,要做到正确并不容易。以下是一些需要考虑的替代方案,所有这些方法都缺乏实施和使用的充分理由。

  1. 应用的专用谓词可能很复杂,并且最好放在用户手中,以便手动定义每个谓词的等级/权重。这是笨拙的,通常是维护/样板头痛,不值得这种机制的初始魅力。
  2. 用户总是可以在程序运行时添加更多重载(Python被解释),这可能会导致令人惊讶的时间行为。在你的代码库中传播它是自满的。当它没有传播时,为什么不只是if/else并且完成?
  3. 您可以以某种方式限制使用,并强制执行它,以便只有一个谓词必须为给定的调用返回True。这在许多方面都是奇怪的,低效的和无用的,例如如果你想捕获A或其子类的所有实例,但是以特殊方式处理子类C,该怎么办?或者,如果您想进一步专门化具有额外条件的谓词。你打算如何对这种模型进行分类?

答案 3 :(得分:-1)

您可以使用isintance并将其与ABC结合使用来检查输入的特征,如下所示:

from collections.abc import Iterable

def foo(param):
    if isinstance(param,int) and param > 0:
        #do something 
    elif isinstance(param,Iterable) and all(isinstance(item, str) for item in param):
        # do something else
    else:
        raise TypeError('Illegal input.')

ABC会告诉你这个参数有什么样的界面,所以如果你不关心它是不是特定类型,你可以根据自己的行为使用合适的界面,这样定义就像那个参数一样可能是setlisttuple字符串,并且始终会经过第二次检查,因此您可以相应地处理它。还有,numbers的ABC也是你想要在这种情况下也是一般的。