在创建函数时使用填充的参数生成函数?

时间:2017-01-26 20:39:05

标签: python

我的目标是动态生成函数,然后将它们保存在文件中。例如,在我当前的尝试中,在呼叫create_file

import io


def create_file(a_value):
    a_func = make_concrete_func(a_value)
    write_to_file([a_func], '/tmp/code.py')


def make_concrete_func(a_value):
    def concrete_func(b, k):
        return b + k + a_value

    return concrete_func


def write_to_file(code_list, path):
    import inspect
    code_str_list = [inspect.getsource(c) for c in code_list]
    with open(path, 'w') as ofh:
        for c in code_str_list:
            fh = io.StringIO(c)
            ofh.writelines(fh.readlines())
            ofh.write('\n')


create_file('my_value')

我想要的输出是(文件/tmp/code.py):

def concrete_func(b, k):
    return b + k + 'my_value'

我得到的输出是(文件'/tmp/code.py'):

def concrete_func(b, k):
    return b + k + a_value

更新:我的解决方案使用inspect.getsource返回一个字符串。我想知道我是否限制了你的选择,因为大多数解决方案建议更换字符串。解决方案无需使用inspect.getsource。无论如何你都可以写它来获得所需的输出。

更新2:我这样做的原因是因为我想为Amazon Lambda生成一个文件。 Amazon Lambda将获取一个python文件及其虚拟环境,并将为您执行它(使您免于担心可伸缩性和容错性)。你必须告诉Lambda要调用哪个文件和哪个函数,Lambda将为你执行它。

6 个答案:

答案 0 :(得分:3)

函数定义在定义时不会查找其自由变量(函数本身未定义的变量)。即concrete_func此处:

def make_concrete_func(a_value):
    def concrete_func(b, k):
        return b + k + a_value

    return concrete_func

在定义时不查找a_value,而是包含在运行时从其闭包(简化封闭函数)加载a_value的代码。

您可以通过反汇编返回的函数来看到这一点:

f = make_concrete_func(42)

import dis
print dis.dis(f)

  3           0 LOAD_FAST                0 (b)
              3 LOAD_FAST                1 (k)
              6 BINARY_ADD          
              7 LOAD_DEREF               0 (a_value)
             10 BINARY_ADD          
             11 RETURN_VALUE        
None

你可以通过编辑字节代码来做你想做的事情..它是在http://bytecodehacks.sourceforge.net/bch-docs/bch/module-bytecodehacks.macro.html .. shudder)之前完成的。

答案 1 :(得分:3)

使用getsource将函数转换为字符串,并使用简单的字符串操作替换变量名称。

from inspect import getsource

def write_func(fn, path, **kwargs):
    fn_as_string = getsource(fn)
    for var in kwargs:
        fn_as_string = fn_as_string.replace(var, kwargs[var])
    with open(path, 'a') as fp:  # append to file
        fp.write('\n' + fn_as_string)

def base_func(b, k):
    return b + k + VALUE

# add quotes to string literals   
write_func(base_func, '/tmp/code.py', VALUE="'my value'")

# you should replace the function name if you write multiple functions to the file
write_func(base_func, '/tmp/code.py', base_func='another_func', VALUE='5')

/tmp/code.py中的输出正如预期的那样:

def base_func(b, k):
    return b + k + 'my value'

def another_func(b, k):
    return b + k + 5

答案 2 :(得分:1)

试试这个。请注意,我已向write_to_file添加了另一个参数

def write_to_file(code_list, path,a_value):
    print "lc",code_list
    code_str_list = [inspect.getsource(c) for c in code_list]
    with open(path, 'w') as ofh:
        for c in code_str_list:
            c= c.replace('a_value','\''+a_value+'\'')
            fh = io.StringIO(c)
            ofh.writelines(fh.readlines())
            ofh.write('\n')

答案 3 :(得分:1)

如果文件不必是人类可读的并且您相信它不会被攻击者操纵,那么组合functools.partialpickle可能是最pythonic的方法。然而,它有一些我不完全理解的缺点:一方面,它似乎不适用于本地定义的函数(或者通常可能是本地定义的变量?)。

我可能只是问我自己的问题。

import functools
import pickle


def write_file_not_working():

    def concrete_func_not_working(b, k, a_value):
        return b + k + a_value

    with open('data.pickle', 'wb') as f:
        data = functools.partial(concrete_func_not_working, a_value='my_value')
        pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)


def use_file_not_working():
    with open('data.pickle', 'rb') as f:
        resurrected_data = pickle.load(f)
        print(resurrected_data('hi', 'there'))


def top_level_concrete_func(b, k, a_value):
    return a_value + b + k


def write_file_working():
    with open('working.pickle', 'wb') as f:
        data = functools.partial(top_level_concrete_func, a_value='my_working_value')
        pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)


def use_file_working():
    with open('working.pickle', 'rb') as f:
        resurrected_data = pickle.load(f)
        print(resurrected_data('hi', 'there'))


if __name__ == "__main__":
    write_file_working()
    use_file_working()

    write_file_not_working()
    use_file_not_working()

答案 4 :(得分:1)

@Ben让我意识到我不需要使用基于字符串的方法来生成代码,而且我可以使用序列化。我使用pickle代替了有限的dill库,它克服了Ben提到的限制

所以,我终于做了类似的事情。

import dill


def create_file(a_value, path):
    a_func = make_concrete_func(a_value)
    dill.dump(a_func, open(path, "wb"))
    return path


def make_concrete_func(a_value):
    def concrete_func(b, k):
        return b + k + a_value

    return concrete_func


if __name__ == '__main__':
    path = '/tmp/code.dill'
    create_file('Ben', path)
    a_func = dill.load(open(path, "rb"))
    print(a_func('Thank ', 'You '))

答案 5 :(得分:0)

如果你想要创建的函数都有一个确定的模式,我会为它创建一个模板并用它来批量生成函数

>>> def test(*values):
        template="""
def {name}(b,k):
    return b + k + {value}

"""
        for i,v in enumerate(values):
            print( template.format(name="func{}".format(i),value=repr(v)) )


>>> test("my_value",42,[1])

def func0(b,k):
    return b + k + 'my_value'



def func1(b,k):
    return b + k + 42



def func2(b,k):
    return b + k + [1]


>>>