1

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

假设我们有一个函数,我们希望在编译之前对源代码进行大量修改:

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

4

2 回答 2

1

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

第二个问题中描述的问题可以通过在装饰器运行时暂时阻塞来解决。这样,装饰器与所有其他装饰器一起保留,但没有操作。

这是更新的来源。

from __future__ import print_function

import sys


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)

_blocked = False

def remove_1(f):
    global _blocked
    if _blocked:
        return f
    import inspect
    source = inspect.getsource(f)    
    new_source = comment_1(source)
    env = sys.modules[f.__module__].__dict__
    _blocked = True
    try:
        exec new_source in env
    finally:
        _blocked = False
    return env[f.__name__]


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


f()

def remove_1(f):    
    import inspect
    source = inspect.getsource(f)    
    new_source = comment_1(source)
    env = sys.modules[f.__module__].__dict__.copy()
    exec new_source in env
    return env[f.__name__]
于 2012-09-02T18:41:44.773 回答
0

我将保留user4815162342答案中给出的解决方案的修改版本。正如对问题的评论所建议的那样,它使用ast模块来删除 的某些部分。为了做到这一点,我主要依赖于本文中的信息。f

此实现删除所有出现的aas 独立表达式。

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
于 2012-09-02T22:18:58.650 回答