偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Python靜態(tài)類型解析工具簡(jiǎn)介和實(shí)踐

開發(fā) 開發(fā)工具 后端
Python是一門強(qiáng)類型的動(dòng)態(tài)類型語言,開發(fā)者可以給對(duì)象動(dòng)態(tài)指定類型(動(dòng)態(tài)),但類型不匹配的操作是不被允許的(強(qiáng)類型,如str和int兩個(gè)變量無法相加)。

 [[412368]]

一、背景

Python是一門強(qiáng)類型的動(dòng)態(tài)類型語言,開發(fā)者可以給對(duì)象動(dòng)態(tài)指定類型(動(dòng)態(tài)),但類型不匹配的操作是不被允許的(強(qiáng)類型,如str和int兩個(gè)變量無法相加)。

動(dòng)態(tài)類型幫助開發(fā)者寫代碼輕松愉快,然而,俗話說:動(dòng)態(tài)一時(shí)爽,重構(gòu)火葬場(chǎng)。動(dòng)態(tài)類型也帶來了許多麻煩,如果動(dòng)態(tài)語言能加入靜態(tài)類型標(biāo)記的話,主要有以下幾點(diǎn)好處:

  • 編寫更便捷。配合各種IDE工具,可以實(shí)現(xiàn)定義跳轉(zhuǎn),類型提示等。

  • 編碼更可靠。既然有了類型定義的加持,許多工具能夠在靜態(tài)編碼階段就能提前發(fā)現(xiàn)語義錯(cuò)誤。

  • 重構(gòu)更放心。明確了接口的出入?yún)?,使代碼重構(gòu)更明確更穩(wěn)定。

目前主流語言大多數(shù)是支持靜態(tài)類型的,如Java,Go,Rust。而動(dòng)態(tài)語言(Python,JS)也在擁抱靜態(tài)類型,如TypeScript。

本文主要介紹一下Python對(duì)靜態(tài)類型的支持、社區(qū)發(fā)展的現(xiàn)狀、類型檢查工具介紹與對(duì)比,以及類型解析的實(shí)戰(zhàn)。

二、Python的靜態(tài)類型支持

早在06年的Python3.0就引入了類型annotation的語法,并列出了許多改進(jìn)項(xiàng)。

  1. # 加類型前 
  2. def add(a, b): 
  3.     return a + b 
  4.      
  5. # 加類型后 
  6. def add(a:int, b:int) -> int
  7.     return a + b 

隨著持續(xù)的演進(jìn),到Python3.5,能夠做到Type Hints,配合類型標(biāo)注,IDE可以做Type Checking。

進(jìn)而到Python3.7,靜態(tài)類型支持基本完善。

下面我來具體介紹下類型檢查工具和一些基礎(chǔ)概念。

三、類型檢查工具簡(jiǎn)介

Python作者和主流大廠都陸續(xù)推出了Python類型檢查工具:

這些類型解析工具的功能大同小異,下面簡(jiǎn)單介紹下:

1.mypy

最早的官方推出的mypy是由Python之父Guido van Rossum親自開發(fā),被各種主流編輯器所集成(如PyCharm, Emacs, Sublime Text, VS Code等),用戶基礎(chǔ)和文檔經(jīng)驗(yàn)都很豐富。

2.pytype

谷歌的pytype可以做類型檢查,并且提供了一些實(shí)用小工具,下文會(huì)簡(jiǎn)單介紹下其應(yīng)用:

  • annotate-ast,過程中的AST樹標(biāo)記工具。

  • merge-pyi,把生成的 pyi 文件合并回原文件中,甚至還能做到隱藏類型,在類型檢查時(shí)再加載。

  • pytd-tool,解析 pyi 文件的工具,解析成pytype自定義的PYTD文件。

  • pytype-single,再給定所有依賴的 pyi 文件的前提下,可以解析單個(gè)Python文件。

  • pyxref,交叉引用的生成器。

3.pyre

臉書的pyre-check有兩個(gè)特別的功能:

  • Watchman功能, 可以監(jiān)聽代碼文件,追蹤改動(dòng)。

  • Query功能,可以對(duì)源碼做局部區(qū)域性的檢查,例如查詢某行中一個(gè)表達(dá)式的類型、查詢一個(gè)類的全部方法并返回成列表等,避免了全局檢查。

4.pyright

微軟的pyright是最晚開源推出的,宣稱有以下優(yōu)點(diǎn):

  • 速度快。相較于 mypy 及其它用 Python 寫的檢查工具,它的速度是 5 倍甚至更多。

  • 不依賴 Python 環(huán)境。它用 TypeScript 寫成,運(yùn)行于 node 上,不依賴 Python 環(huán)境或第三方包。

  • 可配置性強(qiáng)。支持自由地配置,支持指定不同的運(yùn)行環(huán)境(PYTHONPATH 設(shè)置、Python 版本、平臺(tái)目標(biāo))。

  • 檢查項(xiàng)齊全。支持類型檢查及其它語法項(xiàng)的檢查(如 PEP-484、PEP-526、PEP-544),以及函數(shù)返回值、類變量、全局變量的檢查,甚至可以檢查條件循環(huán)語句。

  • 命令行工具。它包含兩個(gè) VS Code 插件:一個(gè)命令行工具和一個(gè)語言服務(wù)器協(xié)議(Language Server Protocol)。

  • 內(nèi)置 Stubs 。使用的是 Typeshed 的副本(注:使用靜態(tài)的 pyi 文件,檢查內(nèi)置模塊、標(biāo)準(zhǔn)庫和三方件 ) 。

  • 語言服務(wù)特性。懸停提示信息、符號(hào)定義的跳轉(zhuǎn)、實(shí)時(shí)的編輯反饋。

四、Pytype使用介紹

接下來重點(diǎn)介紹一下pytype。為什么選取pytype呢,首先mypy比較古老,很多功能沒有新出的工具新穎和實(shí)用。計(jì)劃使用Python LSP來處理Python文件提供一些語法服務(wù)的功能,pyre-check用的是Ocamel,所以我們就拿Python語言的pytype來實(shí)現(xiàn)想要的功能,而且pytype提供了一些實(shí)用工具,比如解析一個(gè)pyi文件,基于Python文件生成pyi文件等。

1.基本概念

pyi 文件

pyi 的“ i ”指的是interfiace,將 Python 文件的類型定義用接口的形式存儲(chǔ)到pyi文件里,來輔助類型檢查。

大家常用的Pycharm,可以關(guān)注下項(xiàng)目空間的External Libraries > Python 3.6 > Typeshed Stubs里面就有許多內(nèi)置的 pyi 文件,來輔助編碼過程的類型提示和定位。

Typeshed Stubs

上面提到了typeshed stubs,這相當(dāng)于是提前集成的pyi集合,pycharm似乎自己維護(hù)了一份數(shù)據(jù)。 許多比較大的開源項(xiàng)目也在陸續(xù)提供stubs, 比如pyTorch。 Tensorflow也正在考慮。

很多Python大庫去制作pyi工程量比較大,而且還有很多C的API調(diào)用,大家還需要耐心等待。

2.實(shí)戰(zhàn)

我翻閱了pytype的源碼,把比較實(shí)用的代碼和需求做了結(jié)合,下面介紹幾個(gè)示例:

總體效果

  1. import logging 
  2. import sys 
  3. import os 
  4. import importlab.environment 
  5. import importlab.fs 
  6. import importlab.graph 
  7. import importlab.output 
  8. from importlab import parsepy 
  9.  
  10.  
  11. from sempy import util 
  12. from sempy import environment_util 
  13.  
  14.  
  15. from pytype.pyi import parser 

示例Demo,通過Importlab工具,解析項(xiàng)目空間的依賴關(guān)系,以及對(duì)應(yīng)的pyi文件:

  1. def main(): 
  2.     # 指定要解析的目錄 
  3.     ROOT = '/path/to/demo_project' 
  4.     # 指定TYPESHED目錄,可以從這里下載:https://github.com/python/typeshed 
  5.     TYPESHED_HOME = '/path/to/typeshed_home' 
  6.     util.setup_logging() 
  7.     # 載入typeshed,如果TYPESHED_HOME配置的不對(duì),會(huì)返回None 
  8.     typeshed = environment_util.initialize_typeshed_or_return_none(TYPESHED_HOME) 
  9.     # 載入目標(biāo)目錄有效文件 
  10.     inputs = util.load_all_py_files(ROOT) 
  11.     # 生成用于生成import_graph的環(huán)境 
  12.     env = environment_util.create_importlab_environment(inputs, typeshed) 
  13.     # 基于pyi和工程文件生成import graph 
  14.     import_graph = importlab.graph.ImportGraph.create(env, inputs, trim=True) 
  15.     # 打印整個(gè)依賴樹 
  16.     logging.info('Source tree:\n%s', importlab.output.formatted_deps_list(import_graph)) 
  17.     # import模塊的別名 e.g. import numpy as np -> {'np''numpy'
  18.     alias_map = {} 
  19.     # 引入模塊的名稱和具體pyi文件的映射 e.g. import os -> {'os''/path/to/os/__init__.pyi'
  20.     import_path_map = {} 
  21.     # alias_map的value,可以和import_path_map的key對(duì)應(yīng),通過alias_map的key這個(gè)變量名去找真正的實(shí)現(xiàn)文件 
  22.     for file_name in inputs: 
  23.         # 如果有pyi文件匹配,則會(huì)放入resolved 
  24.         # 如果依賴了Build_in依賴,會(huì)被跳過,不返回 
  25.         # 如果依賴了自定義依賴,會(huì)放入unresolved,需要自己進(jìn)一步解析,定位到項(xiàng)目工程文件 
  26.         (resolved, unresolved) = import_graph.get_file_deps(file_name) 
  27.         for item in resolved: 
  28.             item_name = item.replace('.pyi''') \ 
  29.                 .replace('.py''') \ 
  30.                 .replace('/__init__''').split('/')[-1
  31.             import_path_map[item_name] = item 
  32.         for item in unresolved: 
  33.             file_path = os.path.join(ROOT, item.new_name + '.py'
  34.             import_path_map[item.name] = file_path 
  35.         import_stmts = parsepy.get_imports(file_name, env.python_version) 
  36.         for import_stmt in import_stmts: 
  37.             alias_map[import_stmt.new_name] = import_stmt.name 
  38.     print('以下為通過importlab解析方式獲取的import關(guān)系\n\n'
  39.  
  40.  
  41.     # 對(duì)于代碼搜索場(chǎng)景,只需要alias_map,既可以通過正在使用的對(duì)象關(guān)聯(lián)到引入的模塊 
  42.     print('\n\n#################################\n\n'
  43.     print('對(duì)于代碼搜索場(chǎng)景,只需要alias_map,既可以通過正在使用的對(duì)象關(guān)聯(lián)到引入的模塊'
  44.     print('alias_map: ', alias_map) 
  45.  
  46.  
  47.     # 對(duì)于代碼補(bǔ)全場(chǎng)景,需要進(jìn)一步解析當(dāng)前文件以及引用的pyi文件,如果當(dāng)前文件是__init__文件,則要進(jìn)一步去該目錄下的所有文件方法中全局搜索 
  48.     print('\n\n#################################\n\n'
  49.     print('對(duì)于代碼補(bǔ)全場(chǎng)景,需要進(jìn)一步解析當(dāng)前文件以及引用的pyi文件,如果當(dāng)前文件是__init__文件,則要進(jìn)一步去該目錄下的所有文件方法中全局搜索'
  50.     print('import_path_map: ', import_path_map) 
  51.  
  52.  
  53.     print('\n\n\n以下為通過pytype工具,解析pyi文件AST來分析三方依賴返回類型,從而解析出當(dāng)前變量的類型\n\n'
  54.     # 通過pytype的解析,去解析依賴的pyi文件,獲得調(diào)用方法的返回值 
  55.     fname = '/path/to/parsed_file' 
  56.     with open(fname, 'r') as reader: 
  57.         lines = reader.readlines() 
  58.     sourcecode = '\n'.join(lines) 
  59.     ret = parser.parse_string(sourcecode, filename=fname, python_version=3
  60.  
  61.  
  62.     constant_map = dict() 
  63.     function_map = dict() 
  64.     for key in import_path_map.keys(): 
  65.         v = import_path_map[key] 
  66.         with open(v, 'r') as reader: 
  67.             lines = reader.readlines() 
  68.         src = '\n'.join(lines) 
  69.         try
  70.             res = parser.parse_pyi(src, v, key, 3
  71.         except: 
  72.             continue 
  73.         # Alias 
  74.         # Classes 
  75.         for constant in res.constants: 
  76.             constant_map[constant.name] = constant.type.name 
  77.         for function in res.functions: 
  78.             signatures = function.signatures 
  79.             sig_list = [] 
  80.             for signature in signatures: 
  81.                 sig_list.append((signature.params, signature.return_type)) 
  82.             function_map[function.name] = sig_list 
  83.  
  84.  
  85.     var_type_from_pyi_list = [] 
  86.     for alias in ret.aliases: 
  87.         variable_name = alias.name 
  88.         if alias.type is not None: 
  89.             typename_in_source = alias.type.name 
  90.             typename = typename_in_source 
  91.             # 引入別名的case,把它轉(zhuǎn)化回來 
  92.             if '.' not in typename: 
  93.                 # 只是普通的別名,不是函數(shù)調(diào)用的返回值,忽略 
  94.                 continue 
  95.             if typename.split('.')[0] in alias_map: 
  96.                 real_module_name = alias_map[typename.split('.')[0]] 
  97.                 typename = real_module_name + typename[typename.index('.'):] 
  98.             if typename in function_map: 
  99.                 possible_return_types = [item[1].name for item in function_map[typename]] 
  100.                 var_type_from_pyi_list.append((variable_name, possible_return_types)) 
  101.             if typename in constant_map: 
  102.                 possible_return_type = constant_map[typename] 
  103.                 var_type_from_pyi_list.append((variable_name, possible_return_type)) 
  104.                 pass 
  105.     print('\n\n#################################\n\n'
  106.     print('這些都是從PYI文件中分析出來的返回值類型'
  107.     for item in var_type_from_pyi_list: 
  108.         print('變量名:', item[0], '返回類型:', item[1]) 
  109.  
  110.  
  111. if __name__ == '__main__'
  112.     sys.exit(main()) 

被 解析的示例代碼:

  1. # demo.py 
  2. import os as abcdefg 
  3. import re 
  4. from demo import utils 
  5. from demo import refs 
  6.  
  7.  
  8.  
  9.  
  10. cwd = abcdefg.getcwd() 
  11. support_version = abcdefg.supports_bytes_environ 
  12. pattern = re.compile(r'.*'
  13.  
  14.  
  15.  
  16.  
  17. add_res = utils.add(13
  18. mul_res = refs.multi(35
  19.  
  20.  
  21.  
  22.  
  23. c = abs(1

具體步驟

首先pytype利用了Google另一個(gè)開源項(xiàng)目:ImportLab。

用于分析文件間的依賴關(guān)系,此時(shí)可以把typeshed目錄下的文件也放入環(huán)境中,importlab能夠生成依賴圖。

  1. env = environment_util.create_importlab_environment(inputs, typeshed) 
  2. import_graph = importlab.graph.ImportGraph.create(env, inputs, trim=True) 
  3. # 如果有pyi文件匹配,則會(huì)放入resolved 
  4. # 如果依賴了Build_in依賴,會(huì)被跳過,不返回 
  5. # 如果依賴了自定義依賴,會(huì)放入unresolved,需要自己進(jìn)一步解析,定位到項(xiàng)目工程文件 
  6. (resolved, unresolved) = import_graph.get_file_deps(file_name) 

通過import graph我們拿到了變量的來源(包括引用別名,方法調(diào)用返回值):

  1. {'ast''ast''astpretty''astpretty''abcdefg''os''re''re''utils''demo.utils''refs''demo.refs''JsonRpcStreamReader''pyls_jsonrpc.streams.JsonRpcStreamReader'

通過依賴圖,還能直接引用的依賴在具體哪個(gè)位置:

  1. import_path_map:  {'ast''/Users/zhangxindong/Desktop/search/code/sempy/sempy/typeshed/stdlib/ast.pyi''astpretty''/Users/zhangxindong/Desktop/search/code/sempy/venv/lib/python3.9/site-packages/astpretty.py''os''/Users/zhangxindong/Desktop/search/code/sempy/sempy/typeshed/stdlib/os/__init__.pyi''re''/Users/zhangxindong/Desktop/search/code/sempy/sempy/typeshed/stdlib/re.pyi''utils''/Users/zhangxindong/Desktop/search/code/sempy/sempy/demo/utils.py''refs''/Users/zhangxindong/Desktop/search/code/sempy/sempy/demo/refs/__init__.py''streams''/Users/zhangxindong/Desktop/search/code/sempy/venv/lib/python3.9/site-packages/pyls_jsonrpc/streams.py'

接下來,就是去具體解析對(duì)應(yīng)的文件了。我的需求是獲取一些方法的返回值類型,對(duì)于 pyi 文件,pytype能夠幫助我們解析,然后我們通過調(diào)用關(guān)系去匹配。

  1. print('\n\n\n以下為通過pytype工具,解析pyi文件AST來分析三方依賴返回類型,從而解析出當(dāng)前變量的類型\n\n'
  2. # 通過pytype的解析,去解析依賴的pyi文件,獲得調(diào)用方法的返回值 
  3. fname = '/path/to/parsed_file' 
  4. with open(fname, 'r') as reader: 
  5.     lines = reader.readlines() 
  6. sourcecode = '\n'.join(lines) 
  7. ret = parser.parse_string(sourcecode, filename=fname, python_version=3
  8.  
  9.  
  10. constant_map = dict() 
  11. function_map = dict() 
  12. for key in import_path_map.keys(): 
  13.     v = import_path_map[key] 
  14.     with open(v, 'r') as reader: 
  15.         lines = reader.readlines() 
  16.     src = '\n'.join(lines) 
  17.     try
  18.         res = parser.parse_pyi(src, v, key, 3
  19.     except: 
  20.         continue 
  21.     # Alias 
  22.     # Classes 
  23.     for constant in res.constants: 
  24.         constant_map[constant.name] = constant.type.name 
  25.     for function in res.functions: 
  26.         signatures = function.signatures 
  27.         sig_list = [] 
  28.         for signature in signatures: 
  29.             sig_list.append((signature.params, signature.return_type)) 
  30.         function_map[function.name] = sig_list 
  31.  
  32.  
  33. var_type_from_pyi_list = [] 
  34. for alias in ret.aliases: 
  35.     variable_name = alias.name 
  36.     if alias.type is not None: 
  37.         typename_in_source = alias.type.name 
  38.         typename = typename_in_source 
  39.         # 引入別名的case,把它轉(zhuǎn)化回來 
  40.         if '.' not in typename: 
  41.             # 只是普通的別名,不是函數(shù)調(diào)用的返回值,忽略 
  42.             continue 
  43.         if typename.split('.')[0] in alias_map: 
  44.             real_module_name = alias_map[typename.split('.')[0]] 
  45.             typename = real_module_name + typename[typename.index('.'):] 
  46.         if typename in function_map: 
  47.             possible_return_types = [item[1].name for item in function_map[typename]] 
  48.             # print('The possible return type of', typename_in_source, 'is', possible_return_types) 
  49.             var_type_from_pyi_list.append((variable_name, possible_return_types)) 
  50.         if typename in constant_map: 
  51.             possible_return_type = constant_map[typename] 
  52.             var_type_from_pyi_list.append((variable_name, possible_return_type)) 
  53.             pass 

比如:

  1. pattern = re.compile(r'.*'

從/Users/zhangxindong/Desktop/search/code/sempy/sempy/typeshed/stdlib/re.pyi文件中,我們載入了兩個(gè)方法都是re.compile,只是入?yún)⒉煌?,返回值都是Pattern類型。

于是我們就知道了pattern變量的類型是re.Pattern。

  • 這些都是從 pyi 文件中分析出來的返回值類型。

  • 變量名 cwd 返回類型:['str']

  • 變量名 support_version 返回類型:bool

  • 變量名 pattern 返回類型:['typing.Pattern', 'typing.Pattern']

五、應(yīng)用

Python語法分析的功能有一部分已經(jīng)應(yīng)用在了阿里云Dev Studio的代碼文檔搜索推薦和代碼智能補(bǔ)全中。

1.代碼文檔搜索推薦

當(dāng)開發(fā)者不知道如何使用某個(gè) API 時(shí)(如調(diào)用方式或方法入?yún)⒌龋?,可以將鼠?biāo)移動(dòng)到指定 API 上,即可展示智能編碼插件提供的 API 概要信息。開發(fā)者點(diǎn)擊“ API 文檔詳情”,能在右側(cè)欄看到 API 的官方文檔、代碼示例等詳細(xì)信息,也可以直接搜索所需的 API 代碼文檔。目前支持 JavaScript、Python 語言的代碼文檔搜索推薦。

文檔采集過程中,我們能夠拿到API名稱和API所對(duì)應(yīng)的class,在實(shí)際代碼中,我們通過語法分析就能基于調(diào)用的方法對(duì)應(yīng)到調(diào)用的類信息,從而用于文檔搜索。

2.代碼智能補(bǔ)全

開發(fā)者在編寫代碼時(shí),智能編碼插件會(huì)自動(dòng)感知代碼上下文,為開發(fā)者提供精準(zhǔn)的代碼補(bǔ)全候選項(xiàng),代碼補(bǔ)全候選項(xiàng)中標(biāo)記有 :sparkles: 符號(hào)的為代碼智能補(bǔ)全結(jié)果。目前支持 Java、JavaScript、Python 語言的代碼智能補(bǔ)全。

代碼補(bǔ)全過程中,通過語法分析,能夠更加精準(zhǔn)地獲悉用戶使用變量的類信息,幫助過濾掉深度學(xué)習(xí)模型推薦的不合理選項(xiàng),也能夠基于類的內(nèi)部方法集合,召回一些合理的補(bǔ)全項(xiàng)。

六、總結(jié)

Python靜態(tài)類型支持的理念和工具均以完善,但由于歷史包袱太重,社區(qū)推動(dòng)力不足,實(shí)際能達(dá)到的效果比較有限。另外官方、各大廠以及本地IDE都有自己的實(shí)現(xiàn)和分析方式,還沒有達(dá)到統(tǒng)一的標(biāo)準(zhǔn)和格式。大家可以根據(jù)上述的優(yōu)劣勢(shì)以及配合的工具集與數(shù)據(jù)集,選擇適合自己的方式做解析。期待Python社區(qū)對(duì)靜態(tài)類型的支持能越來越完善。

 

責(zé)任編輯:張燕妮 來源: 阿里技術(shù)
相關(guān)推薦

2025-02-10 07:40:00

Java集合工具類編程

2011-06-09 11:11:35

QT 靜態(tài)庫 動(dòng)態(tài)庫

2011-04-14 17:32:21

2012-04-26 13:44:18

ibmdw

2010-01-07 17:36:38

Linux靜態(tài)庫

2010-03-12 17:29:16

Python模塊

2010-02-24 14:53:33

Python開發(fā)工具

2024-10-29 20:58:38

2010-11-22 10:57:22

MySQL字段類型

2023-02-24 07:48:20

PHPGo服務(wù)

2009-08-20 14:28:00

C#靜態(tài)構(gòu)造函數(shù)

2013-01-16 14:45:47

HadoopApache Hado

2022-06-28 13:25:19

K8sPrometheusGrafana

2009-07-01 19:21:00

2009-09-24 14:30:04

DotProject

2010-02-02 16:22:37

Python動(dòng)態(tài)類型語

2025-03-14 10:34:22

2020-10-17 09:48:55

Spinnaker實(shí)踐

2022-07-26 08:00:00

測(cè)試工具回歸測(cè)試軟件功能

2009-12-09 10:31:32

ibmdwJava
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)