函数 pattern/predicate 匹配 Python
Function pattern/predicate matching in Python
我希望能够分派函数的不同实现,不仅基于第一个参数的类型,而且基于任意谓词。目前我必须这样做:
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.')
以下是我希望能够做的事情的精神:
@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
它类似于 Python 3 的 singledispatch
,但是 singledispatch
只支持类型分派,不支持任意谓词。
TL;DR:是否有一个库允许基于任意谓词(不仅是参数的类型)进行基于谓词的函数分派?
您可以使用 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 告诉你参数有什么样的接口,所以如果你不关心它是否是特定类型,你可以根据你的操作使用适当的接口,所以像那个参数一样定义可能是字符串的 set
、list
或 tuple
,并且总是会通过第二次检查,因此您可以相应地对其进行处理。还有 numbers 的 ABC 是你想在那种情况下也通用。
我不知道有什么库,但这里有一个基本的实现框架。在我看来,阻止这成为一个实用解决方案的真正问题是,我不知道如何在此处进行专门的解析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]:分辨率的问题是不容易对。这里有一些可供考虑的替代方案,所有这些方案都严重缺乏实施和使用的充分理由。
- apply 的特化谓词可能很复杂,最好交由用户手动定义 ranks/weights。这很笨拙,而且通常 maintenance/boilerplate 令人头疼,不值得这种机制的初始魅力。
- 用户总是可以在程序 运行(Python 被解释)时添加更多重载,这可能会导致令人惊讶的时间行为。在你的代码库中散布它是自满的。如果它没有散布开来,为什么不直接
if/else
就结束呢?
- 您可以以某种方式限制使用,并强制执行它,以便对于给定调用只有一个谓词必须 return 为真。这在很多方面都是奇怪的、低效的和无用的,例如如果你想捕获 A 或其子类的所有实例,但以特殊方式处理子类 C 怎么办?或者,如果您想进一步特化具有额外条件的谓词。你打算如何对这种模型进行分类?
这是根据@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
感谢回复者。在问了这个问题之后,似乎没有现成的模块可以做到这一点。所以我自己写了 :) 它受到@Elazar 的建议的启发。
请随时查看。 It's on PyPI 您可以使用以下方式安装它:
pip install genericfuncs
它也是 hosted on GitHub,我计划在保持 API 简单的同时继续开发和添加功能。欢迎投稿。
我希望能够分派函数的不同实现,不仅基于第一个参数的类型,而且基于任意谓词。目前我必须这样做:
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.')
以下是我希望能够做的事情的精神:
@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
它类似于 Python 3 的 singledispatch
,但是 singledispatch
只支持类型分派,不支持任意谓词。
TL;DR:是否有一个库允许基于任意谓词(不仅是参数的类型)进行基于谓词的函数分派?
您可以使用 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 告诉你参数有什么样的接口,所以如果你不关心它是否是特定类型,你可以根据你的操作使用适当的接口,所以像那个参数一样定义可能是字符串的 set
、list
或 tuple
,并且总是会通过第二次检查,因此您可以相应地对其进行处理。还有 numbers 的 ABC 是你想在那种情况下也通用。
我不知道有什么库,但这里有一个基本的实现框架。在我看来,阻止这成为一个实用解决方案的真正问题是,我不知道如何在此处进行专门的解析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]:分辨率的问题是不容易对。这里有一些可供考虑的替代方案,所有这些方案都严重缺乏实施和使用的充分理由。
- apply 的特化谓词可能很复杂,最好交由用户手动定义 ranks/weights。这很笨拙,而且通常 maintenance/boilerplate 令人头疼,不值得这种机制的初始魅力。
- 用户总是可以在程序 运行(Python 被解释)时添加更多重载,这可能会导致令人惊讶的时间行为。在你的代码库中散布它是自满的。如果它没有散布开来,为什么不直接
if/else
就结束呢? - 您可以以某种方式限制使用,并强制执行它,以便对于给定调用只有一个谓词必须 return 为真。这在很多方面都是奇怪的、低效的和无用的,例如如果你想捕获 A 或其子类的所有实例,但以特殊方式处理子类 C 怎么办?或者,如果您想进一步特化具有额外条件的谓词。你打算如何对这种模型进行分类?
这是根据@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
感谢回复者。在问了这个问题之后,似乎没有现成的模块可以做到这一点。所以我自己写了 :) 它受到@Elazar 的建议的启发。
请随时查看。 It's on PyPI 您可以使用以下方式安装它:
pip install genericfuncs
它也是 hosted on GitHub,我计划在保持 API 简单的同时继续开发和添加功能。欢迎投稿。