成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Python靜態(tài)類型解析工具簡介和實踐

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

 [[412368]]

一、背景

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

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

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

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

  • 重構(gòu)更放心。明確了接口的出入?yún)ⅲ勾a重構(gòu)更明確更穩(wěn)定。

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

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

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

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

  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ǔ)概念。

三、類型檢查工具簡介

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

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

1.mypy

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

2.pytype

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

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

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

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

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

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

3.pyre

臉書的pyre-check有兩個特別的功能:

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

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

4.pyright

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

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

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

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

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

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

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

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

四、Pytype使用介紹

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

1.基本概念

pyi 文件

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

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

Typeshed Stubs

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

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

2.實戰(zhàn)

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

總體效果

  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工具,解析項目空間的依賴關(guān)系,以及對應(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配置的不對,會返回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.     # 打印整個依賴樹 
  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對應(yīng),通過alias_map的key這個變量名去找真正的實現(xiàn)文件 
  22.     for file_name in inputs: 
  23.         # 如果有pyi文件匹配,則會放入resolved 
  24.         # 如果依賴了Build_in依賴,會被跳過,不返回 
  25.         # 如果依賴了自定義依賴,會放入unresolved,需要自己進(jìn)一步解析,定位到項目工程文件 
  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.     # 對于代碼搜索場景,只需要alias_map,既可以通過正在使用的對象關(guān)聯(lián)到引入的模塊 
  42.     print('\n\n#################################\n\n'
  43.     print('對于代碼搜索場景,只需要alias_map,既可以通過正在使用的對象關(guān)聯(lián)到引入的模塊'
  44.     print('alias_map: ', alias_map) 
  45.  
  46.  
  47.     # 對于代碼補(bǔ)全場景,需要進(jìn)一步解析當(dāng)前文件以及引用的pyi文件,如果當(dāng)前文件是__init__文件,則要進(jìn)一步去該目錄下的所有文件方法中全局搜索 
  48.     print('\n\n#################################\n\n'
  49.     print('對于代碼補(bǔ)全場景,需要進(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另一個開源項目:ImportLab。

用于分析文件間的依賴關(guān)系,此時可以把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文件匹配,則會放入resolved 
  4. # 如果依賴了Build_in依賴,會被跳過,不返回 
  5. # 如果依賴了自定義依賴,會放入unresolved,需要自己進(jìn)一步解析,定位到項目工程文件 
  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'

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

  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'

接下來,就是去具體解析對應(yīng)的文件了。我的需求是獲取一些方法的返回值類型,對于 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文件中,我們載入了兩個方法都是re.compile,只是入?yún)⒉煌祷刂刀际荘attern類型。

于是我們就知道了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ā)者不知道如何使用某個 API 時(如調(diào)用方式或方法入?yún)⒌龋梢詫⑹髽?biāo)移動到指定 API 上,即可展示智能編碼插件提供的 API 概要信息。開發(fā)者點擊“ API 文檔詳情”,能在右側(cè)欄看到 API 的官方文檔、代碼示例等詳細(xì)信息,也可以直接搜索所需的 API 代碼文檔。目前支持 JavaScript、Python 語言的代碼文檔搜索推薦。

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

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

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

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

六、總結(jié)

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

 

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

2011-06-09 11:11:35

QT 靜態(tài)庫 動態(tài)庫

2025-02-10 07:40:00

Java集合工具類編程

2012-04-26 13:44:18

ibmdw

2010-01-07 17:36:38

Linux靜態(tài)庫

2011-04-14 17:32:21

2010-03-12 17:29:16

Python模塊

2010-02-24 14:53:33

Python開發(fā)工具

2024-10-29 20:58:38

2009-08-20 14:28:00

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

2023-02-24 07:48:20

PHPGo服務(wù)

2013-01-16 14:45:47

HadoopApache Hado

2010-11-22 10:57:22

MySQL字段類型

2010-02-02 16:22:37

Python動態(tài)類型語

2025-03-14 10:34:22

2009-07-01 19:21:00

2009-09-24 14:30:04

DotProject

2022-07-26 08:00:00

測試工具回歸測試軟件功能

2022-06-28 13:25:19

K8sPrometheusGrafana

2020-10-17 09:48:55

Spinnaker實踐

2009-12-09 10:31:32

ibmdwJava
點贊
收藏

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

主站蜘蛛池模板: 综合色久| 亚洲v日韩v综合v精品v | gav成人免费播放视频 | 欧美一区二区 | 久久蜜桃资源一区二区老牛 | 天天操天天射天天舔 | 精品无码久久久久久久动漫 | 日日夜夜影院 | 一级毛片播放 | 天天操夜夜操 | 黄网站在线观看 | 国产日韩精品视频 | 黄色大片免费看 | 亚洲欧洲小视频 | 亚洲手机视频在线 | 精品自拍视频在线观看 | 日本精品一区二区 | 黄色大片视频 | 欧美精品一区二区三区在线四季 | 欧美日韩国产一区二区三区 | 国产视频1区| 精品欧美一区二区三区免费观看 | 中文字幕一区二区三 | 福利视频二区 | 午夜av一区二区 | a毛片| 亚洲综合成人网 | 国产日韩欧美激情 | 看毛片网站 | 免费看黄视频网站 | 欧美色综合一区二区三区 | 成人在线视频网 | 色悠悠久 | 国产一级视屏 | 欧美精品久久一区 | 亚洲深夜福利 | 久久久久久国产精品 | 精品国产乱码久久久久久闺蜜 | 999久久久| 蜜桃精品噜噜噜成人av | 亚洲网站在线观看 |