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

python?ast模塊詳析與用法

 更新時(shí)間:2023年07月25日 10:28:43   作者:愛幻想-hjyp  
這篇文章主要給大家介紹了關(guān)于python?ast模塊詳析與用法的相關(guān)資料, Python的ast(Abstract Syntax Trees,抽象語(yǔ)法樹)模塊是一個(gè)內(nèi)置模塊,用于解析Python代碼并生成語(yǔ)法樹,需要的朋友可以參考下

基本概念

在 python 中,我們可以通過自帶的 ast 模塊來(lái)對(duì)解析遍歷語(yǔ)法樹,通過ast.parse()可以將字符串代碼解析為抽象語(yǔ)法樹,然后通過ast.dump()可以打印這棵語(yǔ)法樹。

除了ast模塊外,還有 astor 模塊,其中的 astor.to_sourse() 函數(shù)可以將語(yǔ)法樹Node轉(zhuǎn)換為代碼, astor.dump_tree() 可以很好地格式化整棵樹。

除了這些基礎(chǔ)操作外,我們還可以遍歷和修改整棵語(yǔ)法樹。

比如,對(duì)于a = 10 來(lái)說,我們可以先解析成抽象語(yǔ)法樹,然后打印所有的結(jié)點(diǎn),如下所示。根據(jù)輸出,我們可以看到根節(jié)點(diǎn)是Module類型的,然后其bodyAssign類型的。對(duì)于Assign類型的結(jié)點(diǎn),可以繼續(xù)劃分為Name結(jié)點(diǎn)(表示變量名)和Constant結(jié)點(diǎn)(表示變量?jī)?nèi)容)。

node = ast.parse('a = 10')
print(astor.dump_tree(node))
# Module(body=[Assign(targets=[Name(id='a')], value=Constant(value=10, kind=None), type_comment=None)], type_ignores=[])

節(jié)點(diǎn)類型

上面的簡(jiǎn)單示例向我們展示了幾種基本結(jié)點(diǎn)類型(Assign、Name、Constant),接下來(lái)我們將會(huì)展示其他幾種常見的結(jié)點(diǎn)類型和示例,完整的節(jié)點(diǎn)類型可以查閱節(jié)點(diǎn)類型。大體上,我們可以把結(jié)點(diǎn)類型分為葉子結(jié)點(diǎn)類型和非葉子結(jié)點(diǎn)類型,比如Assign就是非葉子結(jié)點(diǎn)類型,NameConstant是葉子結(jié)點(diǎn)類型,因?yàn)樗麄儾粫?huì)有子結(jié)點(diǎn)了。

ast.Assign

Assign 類型用來(lái)表示賦值語(yǔ)句,比如a = 10、 b = a 這樣的賦值語(yǔ)句都是Assign結(jié)點(diǎn)類型,他并不是一個(gè)葉子結(jié)點(diǎn),因?yàn)樗南旅嬉话氵€有 Name 結(jié)點(diǎn)。

ast.Name

Name類型用來(lái)表示一個(gè)變量的名稱,是一個(gè)葉子結(jié)點(diǎn)。比如對(duì)于b = a 這樣的賦值語(yǔ)句,子結(jié)點(diǎn)就是兩個(gè)Name。

node = ast.parse('a = b')
print(astor.dump_tree(node.body[0]))
# Assign(targets=[Name(id='a')], value=Name(id='b'), type_comment=None)

ast.Constant

表示一個(gè)不可變內(nèi)容,它可以是Number 、string,只要其內(nèi)容是不可變的,都是ast.Constant類型的結(jié)點(diǎn),它是一個(gè)葉子結(jié)點(diǎn)

node = ast.parse('a = 100')
print(astor.dump_tree(node.body[0]))
# Assign(targets=[Name(id='a')], value=Constant(value=100, kind=None), type_comment=None)
node = ast.parse('a = "paddle"')
print(astor.dump_tree(node.body[0]))
# Assign(targets=[Name(id='a')], value=Constant(value='paddle', kind=None), type_comment=None)

ast.Call

表示函數(shù)的調(diào)用,比如paddle.to_tensor()。非葉子節(jié)點(diǎn)類型,一般包含三個(gè)屬性:func、args、 keywords。

  • func:代表調(diào)用函數(shù)的名稱,一般是一個(gè)ast.Nameast.Constant類型的結(jié)點(diǎn),如果是連續(xù)調(diào)用,會(huì)是一個(gè)ast.Call結(jié)點(diǎn)。
  • args:代表函數(shù)傳入的位置參數(shù)和可變參數(shù)。
  • keywords:代表函數(shù)傳入的關(guān)鍵字參數(shù)。
node = ast.parse('paddle.to_tensor(1, a = 10)')
print(astor.dump_tree(node.body[0]))

# Expr(
    value=Call(func=Attribute(value=Name(id='paddle'), attr='to_tensor'),
        args=[Constant(value=1, kind=None)],
        keywords=[keyword(arg='a', value=Constant(value=10, kind=None))]))

對(duì)于上面的例子,我們通過可視化可以看到,頂層是一個(gè)ast.Expr類型的結(jié)點(diǎn),表示一個(gè)表達(dá)式。下面是ast.Call 結(jié)點(diǎn),Call 結(jié)點(diǎn)包含 一個(gè)ast.Attribute結(jié)點(diǎn),表示調(diào)用者和調(diào)用的方法名,paddle是調(diào)用者,to_tensor是方法名;一個(gè)ast.Constant類型的args,表示函數(shù)的位置參數(shù);一個(gè)ast.keyword,表示函數(shù)的關(guān)鍵字參數(shù)。

下面我們看一個(gè)比較復(fù)雜的示例,多個(gè)函數(shù)的連續(xù)調(diào)用。根據(jù)輸出結(jié)果可以看到,最后的調(diào)用reshape在最外層,然后一直向內(nèi)遞歸,子結(jié)點(diǎn)還是ast.Call類型的結(jié)點(diǎn)。

node = ast.parse('a.to_tensor(1, a = 10).reshape(1)')
print(astor.dump_tree(node.body[0]))

Expr(
    value=Call(
        func=Attribute(
            value=Call(func=Attribute(value=Name(id='a'), attr='to_tensor'),      
                args=[Constant(value=1, kind=None)],
                keywords=[keyword(arg='a', value=Constant(value=10, kind=None))]),
            attr='reshape'),
        args=[Constant(value=1, kind=None)],
        keywords=[]))

ast.Attribute

上面的例子中出現(xiàn)了ast.Attribute結(jié)點(diǎn),Attribute結(jié)點(diǎn)可以理解為屬性,是一個(gè)非葉子結(jié)點(diǎn)。它包含兩個(gè)字段,value字段和attr字段。對(duì)于a.shape來(lái)說value指明調(diào)用者,即a;attr指明調(diào)用的方法名,即shape。

node = ast.parse('a.shape')
print(astor.dump_tree(node.body[0]))

Expr(value=Attribute(value=Name(id='a'), attr='shape'))

結(jié)點(diǎn)的遍歷

ast模塊中,可以借助繼承ast.NodeVisitor類來(lái)完成結(jié)點(diǎn)的遍歷,該類具有兩種訪問結(jié)點(diǎn)的方法,一種是針對(duì)所有結(jié)點(diǎn)類型通用的訪問方法generic_visit(),另一種是針對(duì)某個(gè)類型結(jié)點(diǎn)的訪問方法 visit_xxx,其中xxx代表具體的結(jié)點(diǎn)類型。generic_visit()函數(shù)是遍歷每個(gè)結(jié)點(diǎn)的入口函數(shù),隨后會(huì)調(diào)用visitor()函數(shù),獲取該結(jié)點(diǎn)的類型,然后判斷是否有遍歷該類型結(jié)點(diǎn)的函數(shù),如果有則調(diào)用 visit_xxx類型的方法,如果沒有則調(diào)用通用generic_visit()方法。

ast源碼

class NodeVisitor(object):
    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
        return visitor(node)
    def generic_visit(self, node):
    	# 可以看到 generic_visit函數(shù)會(huì)調(diào)用visit函數(shù),然后尋找并調(diào)用特定類型的visit函數(shù)。 
        """Called if no explicit visitor function exists for a node."""
        for field, value in iter_fields(node):
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, AST):
                        self.visit(item)
            elif isinstance(value, AST):
                self.visit(value)
    def visit_Constant(self, node):
        value = node.value
        type_name = _const_node_type_names.get(type(value))
        if type_name is None:
            for cls, name in _const_node_type_names.items():
                if isinstance(value, cls):
                    type_name = name
                    break
        if type_name is not None:
            method = 'visit_' + type_name
            try:
                visitor = getattr(self, method)
            except AttributeError:
                pass
            else:
                import warnings
                warnings.warn(f"{method} is deprecated; add visit_Constant",
                              PendingDeprecationWarning, 2)
                return visitor(node)
        return self.generic_visit(node)

示例

下面是一個(gè)例子,我們定義了一個(gè)繼承ast.NodeVisitor的類,并且重寫了visit_attribute方法,這樣在遍歷到ast.Attribute結(jié)點(diǎn)時(shí),會(huì)輸出當(dāng)前調(diào)用的屬性名或方法名,對(duì)于其他類型的結(jié)點(diǎn)則會(huì)輸出結(jié)點(diǎn)類型。

class CustomVisitor(ast.NodeVisitor):
    def visit_Attribute(self, node):
        print('----' + node.attr)
        ast.NodeVisitor.generic_visit(self, node)
    def generic_visit(self, node):
        print(node.__class__.__name__)
        ast.NodeVisitor.generic_visit(self, node)
code = textwrap.dedent(
    '''
    import paddle
    x = paddle.to_tensor([1, 2, 3])
    axis = 0
    y = paddle.max(x, axis=axis)
    '''
)
node = ast.parse(code)
visitor = CustomVisitor()
visitor.generic_visit(node)

需要注意的是,當(dāng)我們重寫visit_xxx函數(shù)后,一定要記得再次調(diào)用ast.NodeVisitor.generic_visit(self, node),這樣才會(huì)繼續(xù)遍歷整棵語(yǔ)法樹。

結(jié)點(diǎn)的修改

對(duì)于結(jié)點(diǎn)的修改可以借助ast.NodeTransformer 類來(lái)完成,ast.NodeTransformer繼承自ast.NodeVisitor類,重寫了generic_visit方法,該方法可以傳入一個(gè)結(jié)點(diǎn),并且返回修改后的結(jié)點(diǎn),從而完成語(yǔ)法樹的修改。

示例

在該示例中,我們定義了CustomVisitor類來(lái)修改ast.Call 結(jié)點(diǎn)。具體來(lái)說,當(dāng)遍歷到Call類型的結(jié)點(diǎn)后,流程如下:

  • 首先會(huì)調(diào)用get_full_attr方法獲取整個(gè)api名稱,如果是普通方法調(diào)用,則會(huì)返回完整的調(diào)用名稱,比如torch.tensor()會(huì)返回torch.tensor;如果是連續(xù)的方法調(diào)用,比如x.exp().floor(),則會(huì)返回ClassMethod.floor。
  • 然后調(diào)用 ast.NodeVisitor.generic_visit(self, node) ,進(jìn)行深度優(yōu)先的修改,這樣就可以一層層遞歸,先修改內(nèi)層,再修改外層。
  • 如果是普通的方法調(diào)用,則修改結(jié)點(diǎn)后返回;
  • 如果是連續(xù)的方法調(diào)用,需要先通過astor.to_source(node)獲取前綴方法,即調(diào)用者,保留前綴方法名稱的同時(shí),修改目前的方法名后返回。具體是通過'{}.{}()'實(shí)現(xiàn)的。
def get_full_attr(node):
        # torch.nn.fucntional.relu
        if isinstance(node, ast.Attribute):
            return get_full_attr(node.value) + '.' + node.attr
        # x.abs() -> 'x'
        elif isinstance(node, ast.Name):
            return node.id
        # for example ast.Call
        else:
            return 'ClassMethod'
            
class CustomVisitor(ast.NodeTransformer):
    
    def visit_Call(self, node):
        # 獲取api的全稱
        full_func = get_full_attr(node.func)

        # post order
        ast.NodeVisitor.generic_visit(self, node)
        
        # 如果是普通方法調(diào)用,直接改寫整個(gè)結(jié)點(diǎn)即可
        if full_func == 'torch.tensor':
            # 將 torch.tensor() 改寫為 paddle.to_tensor()
            code = 'paddle.to_tensor()'
            new_node = ast.parse(code).body[0]
            return new_node.value
        
        # 如果是類方法調(diào)用,需要取前面改寫后的方法作為 func.value 
        if full_func == 'ClassMethod.floor':
            # 獲取前綴方法作為 func.value
            new_func = astor.to_source(node).strip('\n')
            new_func = new_func[0: new_func.rfind('.')]
            # 將 floor() 改寫為 floor2()
            code = '{}.{}()'.format(new_func, 'floor2')
            new_node = ast.parse(code).body[0]
            return new_node.value

        # 其余結(jié)點(diǎn)不修改
        return node

code = textwrap.dedent(
    '''
    import torch
    x = torch.tensor([1, 2, 3])
    x = x.exp().floor()
    '''
)
node = ast.parse(code)
visitor = CustomVisitor()
node = visitor.generic_visit(node)
result_code = astor.to_source(node)
print(result_code)

參考鏈接

  • https://blog.csdn.net/ThinkTimes/article/details/110831176?ydreferer=aHR0cHM6Ly9jbi5iaW5nLmNvbS8%3D
  • https://greentreesnakes.readthedocs.io/en/latest/
  • https://github.com/PaddlePaddle/PaConvert

總結(jié)

到此這篇關(guān)于python ast模塊詳析與用法的文章就介紹到這了,更多相關(guān)python ast模塊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

婺源县| 云和县| 阿合奇县| 调兵山市| 昌都县| 曲水县| 漯河市| 孟连| 疏勒县| 阳西县| 天台县| 辽源市| 贵南县| 年辖:市辖区| 类乌齐县| 贵州省| 泸水县| 宝丰县| 调兵山市| 安乡县| 五河县| 合作市| 邹城市| 固安县| 双辽市| 望江县| 西充县| 安新县| 那坡县| 景谷| 屏东市| 繁昌县| 蚌埠市| 临江市| 阿尔山市| 津市市| 云林县| 盐源县| 东丽区| 黄浦区| 垦利县|