最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python?Ast抽象語法樹的介紹及應用詳解

 更新時間:2022年07月28日 16:38:13   作者:alpha_panda  
這篇文章主要為大家介紹了Python?Ast抽象語法樹的介紹及應用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

Abstract Syntax Trees即抽象語法樹。Ast是python源碼到字節(jié)碼的一種中間產(chǎn)物,借助ast模塊可以從語法樹的角度分析源碼結(jié)構(gòu)。

此外,我們不僅可以修改和執(zhí)行語法樹,還可以將Source生成的語法樹unparse成python源碼。因此ast給python源碼檢查、語法分析、修改代碼以及代碼調(diào)試等留下了足夠的發(fā)揮空間。

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) --> 控制流程圖 --> 字節(jié)碼

上述過程在python2.5之后被應用。python源碼首先被解析成語法樹,隨后又轉(zhuǎn)換成抽象語法樹。在抽象語法樹中我們可以看到源碼文件中的python的語法結(jié)構(gòu)。

大部分時間編程可能都不需要用到抽象語法樹,但是在特定的條件和需求的情況下,AST又有其特殊的方便性。

下面是一個抽象語法的簡單實例。

Module(body=[
    Print(
          dest=None,
          values=[BinOp( left=Num(n=1),op=Add(),right=Num(n=2))],
          nl=True,
 )])                                

2. 創(chuàng)建AST

2.1 Compile函數(shù)

先簡單了解一下compile函數(shù)。

compile(source, filename, mode[, flags[, dont_inherit]]) 

  • source -- 字符串或者AST(Abstract Syntax Trees)對象。一般可將整個py文件內(nèi)容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編譯并執(zhí)行:

>>> cm = compile(func_def, '<string>', 'exec')
>>> exec cm
>>> 8

上面func_def經(jīng)過compile編譯得到字節(jié)碼,cm即code對象,

True == isinstance(cm, types.CodeType)。

compile(source, filename, mode, ast.PyCF_ONLY_AST)  <==> ast.parse(source, filename='<unknown>', 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結(jié)構(gòu):

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結(jié)構(gòu),還能夠?qū)st反向?qū)С鰌ython 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)
}

上面是部分摘自官網(wǎng)的 Abstract Grammar,實際遍歷ast Node過程中根據(jù)Node的類型訪問其屬性。

 3. 遍歷AST

python提供了兩種方式來遍歷整個抽象語法樹。

3.1 ast.NodeTransfer

將func_def中的add函數(shù)中的加法運算改為減法,同時為函數(shù)實現(xiàn)添加調(diào)用日志。

  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, '<string>', 'exec')

 運行結(jié)果:

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主要是通過修改語法樹上節(jié)點的方式改變AST結(jié)構(gòu),NodeTransformer主要是替換ast中的節(jié)點。

既然func_def中定義的add已經(jīng)被改成一個減函數(shù)了,那么我們就徹底一點,把函數(shù)名和參數(shù)以及被調(diào)用的函數(shù)都在ast中改掉,并且將添加的函數(shù)調(diào)用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, '<string>', 'exec')        # 新加入的node func_log_stmt 缺少lineno和col_offset屬性
  exec compile(source, '<string>', 'exec')
  exec compile(ast.parse(source), '<string>', 'exec')

結(jié)果:

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

 代碼中能夠清楚的看到兩者的區(qū)別。這里不再贅述。

 4.AST應用

AST模塊實際編程中很少用到,但是作為一種源代碼輔助檢查手段是非常有意義的;語法檢查,調(diào)試錯誤,特殊字段檢測等。

上面通過為函數(shù)添加調(diào)用日志的信息是一種調(diào)試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' <= char <= u'\u9fff' for char in unicode_str)
          return False

 接口is_any_chinese有兩種判斷模式,嚴格檢測只要包含中文字符串就可以檢查出,非嚴格必須全部包含中文。

下面我們利用ast來遍歷源文件的抽象語法樹,并檢測其中字符串是否包含中文字符。

  class CodeCheck(ast.NodeVisitor):
      def __init__(self):
          self.cn_checker = CNCheckHelper()
      def visit_Str(self, node):
          self.generic_visit(node)
          # if node.s and any(u'\u4e00' <= char <= u'\u9fff' for char in node.s.decode('utf-8')):
          if self.cn_checker.is_any_chinese(node.s, True):
              print 'line no: %d, column offset: %d, CN_Str: %s' % (node.lineno, node.col_offset, node.s)
  project_dir = './your_project/script'
  for root, dirs, files in os.walk(project_dir):
      print root, dirs, files
      py_files = filter(lambda file: file.endswith('.py'), files)
      checker = CodeCheck()
      for file in py_files:
          file_path = os.path.join(root, file)
          print 'Checking: %s' % file_path
          with open(file_path, 'r') as f:
              root_node = ast.parse(f.read())
              checker.visit(root_node)

 上面這個例子比較的簡單,但大概就是這個意思。

關于CPython解釋器執(zhí)行源碼的過程可以參考官網(wǎng)描述:PEP 339

4.2 Closure 檢查

一個函數(shù)中定義的函數(shù)或者lambda中引用了父函數(shù)中的local variable,并且當做返回值返回。特定場景下閉包是非常有用的,但是也很容易被誤用。

關于python閉包的概念可以參考我的另一篇文章:理解Python閉包概念

這里簡單介紹一下如何借助ast來檢測lambda中閉包的引用。代碼如下:

  class LambdaCheck(ast.NodeVisitor):
      def __init__(self):
          self.illegal_args_list = []
          self._cur_file = None
          self._cur_lambda_args = []
      def set_cur_file(self, cur_file):
          assert os.path.isfile(cur_file), cur_file
          self._cur_file = os.path.realpath(cur_file)
      def visit_Lambda(self, node):
          """
          lambda 閉包檢查原則:
          只需檢測lambda expr body中args是否引用了lambda args list之外的參數(shù)
          """
          self._cur_lambda_args =[a.id for a in node.args.args]
          print astunparse.unparse(node)
          # print astunparse.dump(node)
          self.get_lambda_body_args(node.body)
          self.generic_visit(node)
      def record_args(self, name_node):
          if isinstance(name_node, ast.Name) and name_node.id not in self._cur_lambda_args:
              self.illegal_args_list.append((self._cur_file, 'line no:%s' % name_node.lineno, 'var:%s' % name_node.id))
      def _is_args(self, node):
          if isinstance(node, ast.Name):
              self.record_args(node)
              return True
          if isinstance(node, ast.Call):
              map(self.record_args, node.args)
              return True
          return False
      def get_lambda_body_args(self, node):
          if self._is_args(node): return
          # for cnode in ast.walk(node):
          for cnode in ast.iter_child_nodes(node):
              if not self._is_args(cnode):
                  self.get_lambda_body_args(cnode)

 遍歷工程文件:

  project_dir = './your project/script'
  for root, dirs, files in os.walk(project_dir):
      py_files = filter(lambda file: file.endswith('.py'), files)
      checker = LambdaCheck()
      for file in py_files:
          file_path = os.path.join(root, file)
          checker.set_cur_file(file_path)
          with open(file_path, 'r') as f:
              root_node = ast.parse(f.read())
              checker.visit(root_node)
      res = '\n'.join([' ## '.join(info) for info in checker.illegal_args_list])
      print res

 由于Lambda(arguments args, expr body)中的body expression可能非常復雜,上面的例子中僅僅處理了比較簡單的body expr??筛鶕?jù)自己工程特點修改和擴展檢查規(guī)則。為了更加一般化可以單獨寫一個visitor類來遍歷lambda節(jié)點。

Ast的應用不僅限于上面的例子,限于篇幅,先介紹到這里。期待ast能幫助你解決一些比較棘手的問題。

以上就是Python Ast抽象語法樹的介紹及應用詳解的詳細內(nèi)容,更多關于Python Ast抽象語法樹的資料請關注腳本之家其它相關文章!

相關文章

  • pycharm和anaconda的一些問題及解決

    pycharm和anaconda的一些問題及解決

    這篇文章主要介紹了pycharm和anaconda的一些問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • Python實現(xiàn)的一個簡單LRU cache

    Python實現(xiàn)的一個簡單LRU cache

    這篇文章主要介紹了Python實現(xiàn)的一個簡單LRU cache,本文根據(jù)實際需求總結(jié)而來,需要的朋友可以參考下
    2014-09-09
  • 如何使用python計算復雜三角函數(shù)

    如何使用python計算復雜三角函數(shù)

    當涉及到計算復雜的三角函數(shù)時,Python 提供了強大的數(shù)學庫和函數(shù)來幫助我們進行計算,在本篇博客中,我將介紹如何使用 Python 來計算復雜的三角函數(shù),需要的朋友可以參考下
    2023-08-08
  • pyqt4教程之實現(xiàn)半透明的天氣預報界面示例

    pyqt4教程之實現(xiàn)半透明的天氣預報界面示例

    這篇文章主要介紹了pyqt4實現(xiàn)半透明的天氣預報界面示例,需要的朋友可以參考下
    2014-03-03
  • PyTorch dropout設置訓練和測試模式的實現(xiàn)

    PyTorch dropout設置訓練和測試模式的實現(xiàn)

    這篇文章主要介紹了PyTorch dropout設置訓練和測試模式的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。
    2021-05-05
  • python3實現(xiàn)常見的排序算法(示例代碼)

    python3實現(xiàn)常見的排序算法(示例代碼)

    排序是非常常見的排序算法,今天給大家分享幾種比較常見的排序算法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-07-07
  • Python NumPy教程之遍歷數(shù)組詳解

    Python NumPy教程之遍歷數(shù)組詳解

    這篇文章主要為大家詳細介紹了Python?NumPy中遍歷數(shù)組的方法,文中的示例代碼講解詳細,對我們學習Python有一定幫助,需要的可以參考一下
    2022-08-08
  • Python 字典詳解

    Python 字典詳解

    這篇文章主要介紹了Python的字典,結(jié)合實例形式詳細分析了Python字典的概念、創(chuàng)建、格式化及常用操作方法與相關注意事項,需要的朋友可以參考下
    2021-10-10
  • Python 多模式字符串搜索 Aho-Corasick詳解

    Python 多模式字符串搜索 Aho-Corasick詳解

    Aho-Corasick 算法是一種用于精確或近似多模式字符串搜索的高效算法,本文給大家介紹Python 多模式字符串搜索 Aho-Corasick的相關知識,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • Python中uuid模塊生成唯一標識符的方法詳解

    Python中uuid模塊生成唯一標識符的方法詳解

    這篇文章主要給大家介紹了關于Python中uuid模塊生成唯一標識符的相關資料,uuid庫是Python標準庫中的一個功能強大的庫,可以用于生成全局唯一標識符(UUID),文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-08-08

最新評論

秦安县| 休宁县| 临沧市| 金阳县| 刚察县| 葫芦岛市| 衡东县| 施甸县| 肇东市| 南岸区| 三门峡市| 闽清县| 百色市| 五大连池市| 武宣县| 锡林浩特市| 荆门市| 尚志市| 昌平区| 镇远县| 台江县| 高雄县| 谢通门县| 天镇县| 龙川县| 双桥区| 武邑县| 栾川县| 钟山县| 交口县| 南郑县| 佛坪县| 九江市| 五河县| 托克逊县| 平陆县| 西乌珠穆沁旗| 清镇市| 广宁县| 来凤县| 云南省|