在运行时bofore编译中预处理函数文本

时间:2012-09-02 18:15:20

标签: python

我决定尝试在将函数文本编译成字节代码并执行之后对其进行预处理。这仅用于培训。我很难想象它会成为一个令人满意的解决方案。我遇到过一个我想以这种方式解决的问题,但最终找到了一个更好的方法。所以这只是为了培训和学习新东西,而不是真正的用途。

假设我们有一个函数,在编译之前我们想要修改哪些源代码:

def f():
    1;a()
    print('Some statements 1')
    1;a()
    print('Some statements 2')

例如,让我们用1;标记它的某些行,因为它们有时会被评论,有时则不会。我只是以它为例,功能的修改可能会有所不同。

为了评论这些线条,我做了一个装饰师。它的全部代码如下:

from __future__ import print_function


def a():
    print('a()')


def comment_1(s):
    lines = s.split('\n')
    return '\n'.join(line.replace(';','#;',1) if line.strip().startswith('1;') else line for line in lines)


def remove_1(f):    
    import inspect
    source = inspect.getsource(f)    
    new_source = comment_1(source)
    with open('temp.py','w') as file:
        file.write(new_source)
    from temp import f as f_new
    return f_new


def f():
    1;a()
    print('Some statements 1')
    1;a()
    print('Some statements 2')


f = remove_1(f) #If decorator @remove is used above f(), inspect.getsource includes @remove inside the code.

f()

我使用inspect.getsourcelines来检索函数f代码。然后我做了一些文本处理(在这种情况下注释以1;开头的行)。之后,我将其保存到temp.py模块,然后导入。然后在主模块中修饰函数f

应用装饰器时的输出是:

Some statements 1
Some statements 2

未申请时是:

a()
Some statements 1
a()
Some statements 2

我不喜欢的是我必须使用硬盘来加载编译功能。可以在不将其写入临时模块temp.py并从中导入的情况下完成吗?

第二个问题是将装饰器放在f上方@replace。当我这样做时,inspect.getsourcelines会使用此装饰器返回f文本。我可以手动从f的文本中删除。但这样做会非常危险,因为可能会应用多个装饰器。所以我采用了旧式的装饰语法f = remove_1(f)来完成这项工作。但是,是否可以使用@replace

来允许正常的装饰技术

2 个答案:

答案 0 :(得分:1)

可以通过在源上调用exec语句来避免创建临时文件。 (如果您想要对编译进行额外控制,也可以在compile之前显式调用exec,但是exec将为您进行编译,因此没有必要。)正确调用{{1}如果它从模块的命名空间访问全局变量,那么函数将正常工作的额外好处。

第二个问题中描述的问题可以通过暂时阻止装饰器运行来解决。就这样,装饰者和其他所有装饰者一样,但是没有操作。

这是更新的来源。

exec

答案 1 :(得分:0)

我将留下 user4815162342 在答案中给出的解决方案的修改版本。它使用ast模块删除f的某些部分,如comment to the question中所述。为了实现这一点,我主要依赖this article中的信息。

此实现删除所有出现的a作为独立表达式。

from __future__ import print_function
import sys
import ast
import inspect


def a():
    print('a() is called')


_blocked = False

def remove_1(f):
    global _blocked
    if _blocked:
        return f
    import inspect
    source = inspect.getsource(f)

    a = ast.parse(source) #get ast tree of f

    class Transformer(ast.NodeTransformer):
        '''Will delete all expressions containing 'a' functions at the top level'''
        def visit_Expr(self, node): #visit all expressions
            try:
                if node.value.func.id == 'a': #if expression consists of function with name a
                    return None #delete it
            except(ValueError):
                pass
            return node #return node unchanged
    transformer = Transformer()
    a_new = transformer.visit(a)
    f_new_compiled = compile(a_new,'<string>','exec')

    env = sys.modules[f.__module__].__dict__
    _blocked = True
    try:
        exec(f_new_compiled,env)
    finally:
        _blocked = False
    return env[f.__name__]


@remove_1
def f():
    a();a()
    print('Some statements 1')
    a()
    print('Some statements 2')


f()

输出结果为:

Some statements 1
Some statements 2