如何在Python中使用lambda操作函数?

6
我想操作一个 lambda 函数列表。这个 lambda 函数列表中都包含同一个函数,我想要操作这个函数,给它添加一个额外的参数。
以下是我的示例:
def base_function(*args):
    for arg in args:
        print arg

list_of_functions = [
    lambda new_arg: base_function(new_arg,'arg_1'),
    lambda new_arg: base_function(new_arg,'arg_2'),
    lambda new_arg: base_function(new_arg,'arg_3')
]

有没有可能将这个函数列表更改为下面的形式,而不必重新编写整个函数列表?
list_of_functions = [
    lambda new_arg: base_function(new_arg,'arg_1','another_arg'),
    lambda new_arg: base_function(new_arg,'arg_2','another_arg'),
    lambda new_arg: base_function(new_arg,'arg_3','another_arg')
]

请看下面的另一个例子,这是我试图做的事情。我有以下内容:
def simple_function(arg,capitalize=False):
    if capitalize:
        print arg.upper()
    else:
        print arg 


list_of_functions = [lambda key_word: simple_function(key_word),lambda key_word: simple_function(key_word)]

但我希望将list_of_functions更改为以下内容,而不必重新编写上面的list_of_functions或simple_function:

desired_list_of_functions = [lambda key_word: simple_function(key_word,capitalize=True),lambda key_word: simple_function(key_word,capitalize=True)]

2
调用总是指向同一个函数吗?您可以维护一个列表的列表(参数),然后在最后一刻将它们映射到函数调用。或者,您可以使用 functools.partial ,并且可能操作生成的对象的 partial.args (相同文档)? - aghast
在我的情况下,是的,调用将是相同的函数。但即使列表中的每个项目都指向不同的函数,如果可能的话,我仍然希望能够向列表中的每个函数添加一个附加函数。 - Chris
如果你要支持不同的函数,你可能会想采用“partial”方法。 - aghast
从快速查看文档来看,我同意它非常相关。现在我只是试图弄清楚如何将其应用于我的示例。 - Chris
3个回答

0

以下是我使用部分应用的方法,正如@austin-hastings所建议的:

# first example from the OP

def base_function(*args):
    for arg in args:
        print arg

list_of_functions = [
    lambda new_arg: base_function(new_arg,'arg_1'),
    lambda new_arg: base_function(new_arg,'arg_2'),
    lambda new_arg: base_function(new_arg,'arg_3')
]

# solution:

from functools import partial

new_list_of_functions = [
  partial(fn, 'another_arg') for fn in list_of_functions
]

如果想把参数放在最后,可以参考@austin-hastings的解决方案


当然,您可以使用部分应用程序来影响函数列表。
然后,对于您的特定用例:
desired_list_of_functions = [
  lambda key_word: simple_function(key_word,capitalize=True),
  lambda key_word: simple_function(key_word,capitalize=True)
]

你可以这样做应用程序:

desired_list_of_functions = [
    partial(fn, capitalize=True) for fn in list_of_functions
]

这是因为partial函数将(fn, *args, **kwargs)作为参数,然后应用于该函数。

好的,可以通过使用引用影子技术来解决您的问题:

def simple_function(arg,capitalize=False):
    if capitalize:
        print (arg.upper())
    else:
        print (arg)


list_of_functions = [lambda key_word: simple_function(key_word),lambda key_word: simple_function(key_word)]

# will print 'foo' twice
for fn in list_of_functions:
    fn('foo')

simple_function = partial(simple_function, capitalize=True)

# will print 'FOO' twice
for fn in list_of_functions:
    fn('foo')

上述hack利用了list_of_functions中的lambda定义尚未绑定到simple_function引用的事实,因为在运行时lambda函数的内容尚未被执行。

只有在调用lambda时才会对引用进行赋值,因此lambda内部的simple_function引用将指向新的partial实例而不是原始函数实例。


但是如果你使用 for fn in list_of_functions,那么 fn 已经是一个不接受关键字参数的 lambda 函数了? - ewcz
啊,好的注意事项,我的错,我错过了那个细节。让我考虑一下并完成我的回答。 - zmo

0
一个有点不太优雅的解决方案可能是这样的:
def simple_function(arg, capitalize=False):
    if capitalize:
        print arg.upper()
    else:
        print arg

#this function takes as its argument a lambda function which
#is assumed to merely call another global function defined within
#the same module  
def transf(l):
    #find out which function is being called from within the lambda
    f = globals()[l.__code__.co_names[0]]

    #return a lambda calling the same function with customized keyword argument
    return lambda arg: f(arg, capitalize=True)

list_of_functions = [lambda key_word: simple_function(key_word),lambda key_word: simple_function(key_word)]

new_list_of_functions = list(map(transf, list_of_functions))

for f in list_of_functions:
    f('x')

for f in new_list_of_functions:
    f('x')

0

这里有一个版本,它继承了partial,以便将预先指定的参数放在后面。它从您提供的代码开始,然后进行替换(主要是为了确认行为):

def base_function(*args):
    for arg in args:
        print arg

list_of_functions = [
    lambda new_arg: base_function(new_arg,'arg_1'),
    lambda new_arg: base_function(new_arg,'arg_2'),
    lambda new_arg: base_function(new_arg,'arg_3')
]

for f in list_of_functions:
    f('x')

import functools

class partial_atend(functools.partial):
    def __call__(self, *args, **kwargs):
        f=self.func
        args += self.args
        kw = self.keywords.copy()
        kw.update(kwargs)
        return f(*args, **kw)

list_of_functions = [
    partial_atend(base_function, 'arg_1'),
    partial_atend(base_function, 'arg_2'),
    partial_atend(base_function, 'arg_3'),
]

# Modify args:
list_of_functions = [ partial_atend(f.func, *(f.args+('another_arg',)), **f.keywords) for f in list_of_functions]

# Make the call:
print "Make the call(s)..."
for f in list_of_functions:
    f('x')

值得注意的一件事:我不想去搞可能会调用partial_atends的局部调用...等等。所以我选择在列表推导式中展开参数,然后用新的partial对象替换一个partial对象。这样会更快(如果有必要的话),而且我认为它更不容易被任何实现细节弄糊涂。
编辑:
为了解决capitalize=True更新的问题,您可以使用keywords属性做同样的事情:
首先,修改base_function以打印kwargs:
def base_function(*args, **kwargs):
    for arg in args:
        print arg
    for k,v in kwargs.items():
        print("{}={}".format(k,v))

然后,添加这段代码:

# Modify kwargs, add `capitalize=True`:
list_of_functions = [ partial_atend(f.func, *f.args, capitalize=True, **f.keywords) for f in list_of_functions]

并打印新的lambda表达式:

print "Make the call(s)...capitalized"
for f in list_of_functions:
    f('x')

不,看一下注释“# 修改args:” - 这里是我修改每个部分的参数以添加指定的'another_arg'调用,正如OP所指定的那样。 - aghast
是的,但你没有回答 capitalize=True 的问题 ☺ - zmo
你说得对,因为那是作为原问题的编辑添加的。:-) 但是同样的技术可以使用partial.keywords字段来实现。 - aghast
不对,你误解了重点:楼主想要保持list_of_functions代码不变,但是对该列表中的每个函数进行猴子补丁,以便在lambda内部的函数调用中获得一个新的参数。一开始我也没理解,感谢@ewcz指出给我。 - zmo
你还是没有理解重点!你正在重新编写list_of_functions而不是猴子补丁它。引用OP的话:“我想将list_of_functions更改为以下内容,而不必重写上面的任何list_of_functions或simple_function。”在这里,你正在重新编写list_of_functions,因此你没有回答OP的问题! - zmo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接