Preprocessing function text in runtime bofore compilation

前端 未结 2 1006
孤城傲影
孤城傲影 2021-01-23 15:04

I decided to try to preprocess function text before it\'s compilation into byte-code and following execution. This is merely for training. I hardly imagine situations where it\'

相关标签:
2条回答
  • 2021-01-23 15:48

    One can avoid creating a temporary file by invoking the exec statement on the source. (You can also explicitly call compile prior to exec if you want additional control over compilation, but exec will do the compilation for you, so it's not necessary.) Correctly calling exec has the additional benefit that the function will work correctly if it accesses global variables from the namespace of its module.

    The problem described in the second question can be resolved by temporarily blocking the decorator while it is running. That way the decorator remains, along all the other ones, but is a no-op.

    Here is the updated source.

    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__]
    
    0 讨论(0)
  • 2021-01-23 15:59

    I'll leave a modified version of the solution given in the answer by user4815162342. It uses ast module to delete some parts of f, as was suggested in the comment to the question. To make it I majorly relied on the information in this article.

    This implementation deletes all occurrences of a as standalone expression.

    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()
    

    The output is:

    Some statements 1
    Some statements 2
    
    0 讨论(0)
提交回复
热议问题