我正在尝试延迟评估,因此我更愿意尽可能长时间地使用函数。
我有class Function
定义函数的组合和逐点算术:
from functools import reduce
def compose(*funcs):
'''
Compose a group of functions f1, f2, f3, ... into (f1(f2(f3(...))))
'''
result = reduce(lambda f, g: lambda *args, **kaargs: f(g(*args, **kaargs)), funcs))
return Function(result)
class Function:
'''
>>> f = Function(lambda x : x**2)
>>> g = Function(lambda x : x + 4)
>>> h = f/g
>>> h(6)
3.6
>>> (f + 1)(5)
26
>>> (2 * f)(3)
18
# >> means composition, but in the order opposite to the mathematical composition
>>> (f >> g)(6) # g(f(6))
40
# | means apply function: x | f is the same as f(x)
>>> 6 | f | g # g(f(6))
40
'''
# implicit type conversion from a non-callable arg to a function that returns arg
def __init__(self, arg):
if isinstance(arg, Function):
# would work without this special case, but I thought long chains
# of nested functions are best avoided for performance reasons (??)
self._func = arg._func
elif callable(arg):
self._func = arg
else:
self._func = lambda *args, **kwargs : arg
def __call__(self, *args, **kwargs):
return self._func(*args, **kwargs)
def __add__(lhs, rhs):
# implicit type conversions, to allow expressions like f + 1
lhs = Function(lhs)
rhs = Function(rhs)
new_f = lambda *args, **kwargs: lhs(*args, **kwargs) + rhs(*args, **kwargs)
return Function(new_f)
# same for __sub__, __mul__, __truediv__, and their reflected versions
# ...
# function composition
# similar to Haskell's ., but with reversed order
def __rshift__(lhs, rhs):
return compose(rhs, lhs)
def __rrshift__(rhs, lhs):
return compose(rhs, lhs)
# function application
# similar to Haskell's $, but with reversed order and left-associative
def __or__(lhs, rhs):
return rhs(lhs)
def __ror__(rhs, lhs):
return rhs(lhs)
最初,我的所有函数都有相同的签名:它们将class Data
的实例作为单个参数,并返回float
。也就是说,我对Function
的实施并不依赖于此签名。
然后我开始添加各种高阶函数。例如,我经常需要创建一个现有函数的有界版本,所以我写了一个函数cap_if
:
from operator import le
def cap_if(func, rhs):
'''
Input arguments:
func: function that determines if constraint is violated
rhs: Function(rhs) is the function to use if constraint is violated
Output:
function that
takes as an argument function f, and
returns a function with the same signature as f
>>> f = Function(lambda x : x * 2)
>>> 5 | (f | cap_if(le, 15))
15
>>> 10 | (f | cap_if(le, 15))
20
>>> 5 | (f | cap_if(le, lambda x : x ** 2))
25
>>> 1.5 | (f | cap_if(le, lambda x : x ** 2))
3.0
'''
def transformation(original_f):
def transformed_f(*args, **kwargs):
lhs_value = original_f(*args, **kwargs)
rhs_value = rhs(*args, **kwargs) if callable(rhs) else rhs
if func(lhs_value, rhs_value):
return rhs_value
else:
return lhs_value
return Function(transformed_f)
return Function(transformation)
这是问题所在。我现在想要引入带有Data
个实例的“向量”的函数,并返回数字的“向量”。乍一看,我可以保持现有框架不变。毕竟,如果我实现一个向量,比如numpy.array
,向量将支持逐点算术,因此函数上的逐点算术将按预期工作,而不会对上面的代码进行任何更改。
但是上面的代码打破了高阶函数,例如cap_if
(它应该约束向量中的每个单独元素)。我看到三个选项:
为矢量函数创建cap_if
的新版本,例如vector_cap_if
。但是,我需要为所有其他高阶函数执行此操作,这感觉不合适。不过,这种方法的优点是,我可以在将来用numpy
函数替换这些函数的实现,以获得巨大的性能提升。
实现将函数类型从“数字 - >数字”“提升”到“<函数从数据到数字>到<函数从数据到数字>”和“数字”的函数 - >数字“to”<从数据向量到数字的函数>到<从向量数据到数字的函数>“。我们称这些函数为raise_to_data_function
和raise_to_vector_function
。然后我可以将basic_cap_if
定义为单个数字的函数(而不是更高阶函数);我也为我需要的其他类似帮助函数做同样的事情。然后我使用raise_to_data_function(basic_cap_if)
代替cap_if
和raise_to_vector_function(basic_cap_if)
代替cap_if_vector
。这种方法似乎更优雅,因为我只需要实现一次基本功能。但是它失去了我上面描述的可能的性能提升,并且它还导致代码中有很多函数调用。
我可以按照2中的方法进行操作,但会根据需要在需要时自动应用raise_to_data_function
,raise_to_vector_function
个函数。据推测,我可以在__or__
方法(函数应用程序)中实现它:如果它检测到传递给simple_cap_if
的函数,它将检查正在传递的函数的签名,并应用适当的{{1功能到右侧。 (例如,可以通过使用raise_to
的不同子类的不同签名成员的函数来公开签名;或者通过在Function
中使用指定的方法来公开签名。这似乎是非常hacky,因为可能会发生许多隐式类型转换;但它确实减少了代码的混乱。
我是否错过了更好的方法,和/或针对这些方法的一些论据?