zoukankan      html  css  js  c++  java
  • 使用ast(抽象语法树)在代码中植入埋点

    功能说明

    在代码执行过程中收集一些日志,但是这个操作是与业务无关的,需要根据运行环境来决定
    是否要执行这些操作,
    一个解决办法:

    在代码中加点儿收集日志的标记,比如“注释”,既不会对改变原有代码的结构又能实现功能,
    入侵性比较低。

    那就需要解释器能识别“注释”并翻译成代码执行了,比如对于以下代码:

    # -*- encoding: utf-8 -*-
    
    metrics = {
        "f1": 0.9,
        "acc": 0.8
    }
    '''@myclient.send_metrics(metrics)'''
    
    

    希望解释器执行时能把

    '''@myclient.send_metrics(metrics)'''
    

    当成代码:

    myclient.send_metrics(metrics)
    

    也就是整体代码要翻译成:

    # -*- encoding: utf-8 -*-
    import myclient
    
    metrics = {
        "f1": 0.9,
        "acc": 0.8
    }
    
    myclient.send_metrics(metrics)
    

    简单粗暴的方法替换字符串就可以了,还可以用ast(Abstract Syntax Trees)模块处理这个问题。

    代码块有各种类型:

    • 方法定义
    • 变量申明、赋值
    • 计算表达式
    • ...

    在python中,语句:

    '''@myclient.send_metrics(metrics)'''
    

    表示定义了一个字符串对象,有时候这种形式用于注释,但不是真的注释,解释器不会把它忽略,
    只要从语法树中找到这样的代码节点,把它的内容从字符串申明变成方法调用就可以了。

    实现

    1. 找到埋点在语法树中的代码节点

    # -*- encoding: utf-8 -*-
    import ast, astunparse
    
    code = 
    """
    # -*- encoding: utf-8 -*-
    
    metrics = {
        "f1": 0.9,
        "acc": 0.8
    }
    '''@myclient.send_metrics(metrics)'''
    """
    
    ast_tree = ast.parse(code)  # 解析成语法树
    
    for node in ast_tree.body:
        if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str):  # 字符串定义表达式
            anno_content = node.value.s
            if anno_content.startswith('@myclient.send_metrics'):  # 发现标记
                anno_code = anno_content[1:]  # 去掉@
                print("这个node=%s中带有埋点,埋点代码:
    %s" % (str(node), anno_code))
    
    
    

    执行结果:

    这个node=<_ast.Expr object at 0x120336250>中带有埋点,埋点代码:
    myclient.send_metrics(metrics)
    

    2. 把埋点的字符串变成方法调用并生成新代码

    # -*- encoding: utf-8 -*-
    import ast, astunparse, astor
    
    code = 
    """
    # -*- encoding: utf-8 -*-
    
    metrics = {
        "f1": 0.9,
        "acc": 0.8
    }
    '''@myclient.send_metrics(metrics)'''
    """
    
    ast_tree = ast.parse(code)  # 解析成语法树
    
    for node in ast_tree.body:
        if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str):   # 字符串定义表达式
            anno_content = node.value.s
            if anno_content.startswith('@myclient.send_metrics'):  # 发现标记
                anno_code = anno_content[1:]  # 去掉@
                print("这个node=%s中带有埋点,埋点代码:
    %s" % (str(node), anno_code))
                anno_ast_tree = ast.parse(anno_code)  # 用"注释"中的代码生成一个新的语法树
                anno_expr = anno_ast_tree.body[0]  # 获取语法树中的表达式,也就是 myclient.send_metrics(metrics)
                anno_call = anno_expr.value  # 获取表达式中的方法调用
                node.value = anno_call  # 把赋值语句变成方法调用语句
    
    # 在第1行导入一下  myclient
    import_nni = ast.Import(names=[ast.alias(name='myclient', asname=None)])
    nodes = ast_tree.body
    nodes.insert(0, import_nni)
    
    print("最终代码: ")
    print(astor.to_source(ast_tree))
    
    

    输出是:

    这个node=<_ast.Expr object at 0x1214156d0>中带有埋点,埋点代码:
    myclient.send_metrics(metrics)
    最终代码: 
    import myclient
    metrics = {'f1': 0.9, 'acc': 0.8}
    myclient.send_metrics(metrics)
    

    3. 执行生成的代码

    python 提供的compile、exec、eval 等函数都可以执行。

    4. 上述代码的问题:

    1. 只遍历了根节点下的那一层节点,也就是不支持在方法中使用这种特殊注释,这个需要递归遍历所有语法节点
    2. import 是直接加入第一行的,可以遍历代码查看是否有导入,决定添加import语句以及其位置
  • 相关阅读:
    perl -p -i -w -e
    s///|s()()i|/i|/g|U|u|L|l|Ul|split|join|匹配到hash|匹配到变量|`date`|$^I
    /^/m|/$/m||B|$&|$`|$'|变量捕获|()?|(?:pattern)|(?<LABEL>PATTERN)|$+{LABEL}|(|)|g{LABEL}
    ALG 4-3: Optimal Caching
    ALG 4-2: Scheduling to Minimize Lateness
    ALG 4-1: Interval Scheduling
    ALG 3-n: Practice
    ALG 3-6: Directed Acyclic Graphs and Topological Ordering (有向无环图与拓扑排序)
    ALG 3-5: Connectivity in Directed Graphs (有向图的连接性)
    ALG 3-4: Testing Bipartiteness
  • 原文地址:https://www.cnblogs.com/oaks/p/13338000.html
Copyright © 2011-2022 走看看