Python Ast抽象语法树应该如何使用? |
您所在的位置:网站首页 › pow的中文意思是 › Python Ast抽象语法树应该如何使用? |
引言 Abstract Syntax Trees即抽象语法树。Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构。 此外,我们不仅可以修改和执行语法树,还可以将Source生成的语法树unparse成python源码。因此ast给python源码检查、语法分析、修改代码以及代码调试等留下了足够的发挥空间。 1. AST简介Python官方提供的CPython解释器对python源码的处理过程如下: Parse source code into a parse tree (Parser/pgen.c) Transform parse tree into an Abstract Syntax Tree (Python/ast.c) Transform AST into a Control Flow Graph (Python/compile.c) Emit bytecode based on the Control Flow Graph (Python/compile.c) 即实际python代码的处理过程如下: 源代码解析 --> 语法树 --> 抽象语法树(AST) --> 控制流程图 --> 字节码 上述过程在python2.5之后被应用。python源码首先被解析成语法树,随后又转换成抽象语法树。在抽象语法树中我们可以看到源码文件中的python的语法结构。 大部分时间编程可能都不需要用到抽象语法树,但是在特定的条件和需求的情况下,AST又有其特殊的方便性。 下面是一个抽象语法的简单实例。 Module(body=[ Print( dest=None, values=[BinOp( left=Num(n=1),op=Add(),right=Num(n=2))], nl=True, )])登录后复制2. 创建AST2.1 Compile函数先简单了解一下compile函数。 compile(source, filename, mode[, flags[, dont_inherit]]) source -- 字符串或者AST(Abstract Syntax Trees)对象。一般可将整个py文件内容file.read()传入。 filename -- 代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。 mode -- 指定编译代码的种类。可以指定为 exec, eval, single。 flags -- 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。 flags和dont_inherit是用来控制编译源码时的标志。 func_def = \ """ def add(x, y): return x + y print add(3, 5) """登录后复制使用Compile编译并执行: >>> cm = compile(func_def, '', 'exec') >>> exec cm >>> 8登录后复制上面func_def经过compile编译得到字节码,cm即code对象, True == isinstance(cm, types.CodeType)。 compile(source, filename, mode, ast.PyCF_ONLY_AST) ast.parse(source, filename='', mode='exec') 2.2 生成ast使用上面的func_def生成ast. r_node = ast.parse(func_def) print astunparse.dump(r_node) # print ast.dump(r_node)登录后复制下面是func_def对应的ast结构: Module(body=[ FunctionDef( name='add', args=arguments( args=[Name(id='x',ctx=Param()),Name(id='y',ctx=Param())], vararg=None, kwarg=None, defaults=[]), body=[Return(value=BinOp( left=Name(id='x',ctx=Load()), op=Add(), right=Name(id='y',ctx=Load())))], decorator_list=[]), Print( dest=None, values=[Call( func=Name(id='add',ctx=Load()), args=[Num(n=3),Num(n=5)], keywords=[], starargs=None, kwargs=None)], nl=True) ])登录后复制除了ast.dump,有很多dump ast的第三方库,如astunparse, codegen, unparse等。这些第三方库不仅能够以更好的方式展示出ast结构,还能够将ast反向导出python source代码。 module Python version "$Revision$" { mod = Module(stmt* body)| Expression(expr body) stmt = FunctionDef(identifier name, arguments args, stmt* body, expr* decorator_list) | ClassDef(identifier name, expr* bases, stmt* body, expr* decorator_list) | Return(expr? value) | Print(expr? dest, expr* values, bool nl)| For(expr target, expr iter, stmt* body, stmt* orelse) expr = BoolOp(boolop op, expr* values) | BinOp(expr left, operator op, expr right)| Lambda(arguments args, expr body)| Dict(expr* keys, expr* values)| Num(object n) -- a number as a PyObject. | Str(string s) -- need to specify raw, unicode, etc?| Name(identifier id, expr_context ctx) | List(expr* elts, expr_context ctx) -- col_offset is the byte offset in the utf8 string the parser uses attributes (int lineno, int col_offset) expr_context = Load | Store | Del | AugLoad | AugStore | Param boolop = And | Or operator = Add | Sub | Mult | Div | Mod | Pow | LShift | RShift | BitOr | BitXor | BitAnd | FloorDiv arguments = (expr* args, identifier? vararg, identifier? kwarg, expr* defaults) }登录后复制上面是部分摘自官网的 Abstract Grammar,实际遍历ast Node过程中根据Node的类型访问其属性。 3. 遍历ASTpython提供了两种方式来遍历整个抽象语法树。 3.1 ast.NodeTransfer将func_def中的add函数中的加法运算改为减法,同时为函数实现添加调用日志。 class CodeVisitor(ast.NodeVisitor): def visit_BinOp(self, node): if isinstance(node.op, ast.Add): node.op = ast.Sub() self.generic_visit(node) def visit_FunctionDef(self, node): print 'Function Name:%s'% node.name self.generic_visit(node) func_log_stmt = ast.Print( dest = None, values = [ast.Str(s = 'calling func: %s' % node.name, lineno = 0, col_offset = 0)], nl = True, lineno = 0, col_offset = 0, ) node.body.insert(0, func_log_stmt) r_node = ast.parse(func_def) visitor = CodeVisitor() visitor.visit(r_node) # print astunparse.dump(r_node) print astunparse.unparse(r_node) exec compile(r_node, '', 'exec')登录后复制运行结果: Function Name:add def add(x, y): print 'calling func: add' return (x - y) print add(3, 5) calling func: add -2登录后复制3.2 ast.NodeTransformer使用NodeVisitor主要是通过修改语法树上节点的方式改变AST结构,NodeTransformer主要是替换ast中的节点。 既然func_def中定义的add已经被改成一个减函数了,那么我们就彻底一点,把函数名和参数以及被调用的函数都在ast中改掉,并且将添加的函数调用log写的更加复杂一些,争取改的面目全非:-) class CodeTransformer(ast.NodeTransformer): def visit_BinOp(self, node): if isinstance(node.op, ast.Add): node.op = ast.Sub() self.generic_visit(node) return node def visit_FunctionDef(self, node): self.generic_visit(node) if node.name == 'add': node.name = 'sub' args_num = len(node.args.args) args = tuple([arg.id for arg in node.args.args]) func_log_stmt = ''.join(["print 'calling func: %s', " % node.name, "'args:'", ", %s" * args_num % args]) node.body.insert(0, ast.parse(func_log_stmt)) return node def visit_Name(self, node): replace = {'add': 'sub', 'x': 'a', 'y': 'b'} re_id = replace.get(node.id, None) node.id = re_id or node.id self.generic_visit(node) return node r_node = ast.parse(func_def) transformer = CodeTransformer() r_node = transformer.visit(r_node) # print astunparse.dump(r_node) source = astunparse.unparse(r_node) print source # exec compile(r_node, '', 'exec') # 新加入的node func_log_stmt 缺少lineno和col_offset属性 exec compile(source, '', 'exec') exec compile(ast.parse(source), '', 'exec')登录后复制结果: def sub(a, b): print 'calling func: sub', 'args:', a, b return (a - b) print sub(3, 5) calling func: sub args: 3 5 -2 calling func: sub args: 3 5 -2登录后复制代码中能够清楚的看到两者的区别。这里不再赘述。 4.AST应用AST模块实际编程中很少用到,但是作为一种源代码辅助检查手段是非常有意义的;语法检查,调试错误,特殊字段检测等。 上面通过为函数添加调用日志的信息是一种调试python源代码的一种方式,不过实际中我们是通过parse整个python文件的方式遍历修改源码。 4.1 汉字检测下面是中日韩字符的unicode编码范围 CJK Unified Ideographs Range: 4E00— 9FFF Number of characters: 20992 Languages: chinese, japanese, korean, vietnamese 使用 unicode 范围 \u4e00 - \u9fff 来判别汉字,注意这个范围并不包含中文字符(e.g. u';' == u'\uff1b') . 下面是一个判断字符串中是否包含中文字符的一个类CNCheckHelper: class CNCheckHelper(object): # 待检测文本可能的编码方式列表 VALID_ENCODING = ('utf-8', 'gbk') def _get_unicode_imp(self, value, idx = 0): if idx < len(self.VALID_ENCODING): try: return value.decode(self.VALID_ENCODING[idx]) except: return self._get_unicode_imp(value, idx + 1) def _get_unicode(self, from_str): if isinstance(from_str, unicode): return None return self._get_unicode_imp(from_str) def is_any_chinese(self, check_str, is_strict = True): unicode_str = self._get_unicode(check_str) if unicode_str: c_func = any if is_strict else all return c_func(u'\u4e00' |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |