Obsah
1. Použití knihovny Jedi pro automatické doplňování kódu a statickou analýzu zdrojových kódů Pythonu
2. Instalace knihovny Jedi a otestování korektnosti instalace
3. Využití možností nabízených knihovnou Jedi v interpretru Pythonu (REPL)
4. Základní funkce Jedi – automatické doplňování
5. Atributy objektů představujících návrhy na doplnění zdrojového textu
6. Dokumentační řetězce u návrhů doplnění zdrojového textu
7. Podpora pro skoky na deklarace funkcí, tříd nebo metod
8. Použití metody Script.goto_definitions()
9. Získání podrobnějších informací o deklarované funkci, třídě nebo metodě
10. Problematika redefinice funkcí v Pythonu
11. Dynamické chování Pythonu a jeho vliv na zjišťování informací o volaných funkcích
12. Složitější příklad ukazující dynamické chování Pythonu
13. Detekce míst ve zdrojovém kódu, v němž je nějaká funkce použita
14. Složitější příklad s redeklarací funkcí
15. Využití knihovny Jedi v pluginu jedi-vim
16. Možnosti nabízené pluginem jedi-vim
18. Repositář s demonstračními příklady
1. Použití knihovny Jedi pro automatické doplňování kódu a statickou analýzu zdrojových kódů Pythonu
Jedním z velmi užitečných nástrojů určených především pro vývojáře používající programovací jazyk Python je knihovna nazvaná Jedi. Tato knihovna slouží ke statické analýze zdrojových kódů napsaných právě v Pythonu (buď verze 2.7, popř. verzí 3.4, 3.5 atd.), přičemž výsledky provedené statické analýzy je možné využít pro implementaci mnoha užitečných operací, které známe z většiny moderních integrovaných vývojových prostředí (IDE). Zejména se jedná o operaci automatického doplňování kódu (autocompletion), například jména funkce, metody, názvu proměnné nebo parametru funkce. Ovšem knihovna Jedi podporuje i některé další zajímavé operace, například dokáže zjistit místo ve zdrojovém kódu, v němž je funkce/metoda/proměnná definována, místa v kódu, kde všude je daný objekt použit, nepřímo dokáže detekovat parametry funkcí a metod, apod.
Obrázek 1: Logo knihovny Jedi.
Vzhledem k potenciální užitečnosti knihovny Jedi nebude příliš velkým překvapením zjištění, že tuto knihovnu nalezneme jako součást pluginů určených pro programátorské textové editory. To se týká zejména Vimu, pro který existují pluginy jedi-vim, YouCompleteMe apod. postavené právě na Jedi (první z nich si ostatně dnes popíšeme v závěrečné části článku). Dále nesmíme zapomenout na textový editor Emacs s pluginy Jedi.el, elpy (Emacs Python Development Environment), populární Atom s pluginem pojmenovaným autocomplete-python-jedi, prostředí Visual Studio Code s Python Extension, Eric IDE apod. Knihovna Jedi je taktéž použita v populárním IPythonu, blíže viz New completion API and Interface a dokonce můžeme její možnost automatického doplňování kódu použít i v běžném interpretu Pythonu, což si popíšeme ve třetí kapitole.
2. Instalace knihovny Jedi a otestování korektnosti instalace
V první polovině článku si na několika demonstračních příkladech ukážeme veřejné API knihovny Jedi. Ještě předtím je však samozřejmě nutné provést instalaci této knihovny, což ve skutečnosti není nic těžkého, protože můžeme (podobně jako u většiny knihoven a nástrojů popisovaných v předchozích článcích věnovaných Pythonu) použít nástroj pip2 nebo pip3 (Python Package Installer). Instalaci provedeme s využitím parametru –user lokálně pro přihlášeného uživatele, takže nainstalovanou knihovnu nalezneme v adresáři ~/.local/lib/pythonX.Y/ (kde X.Y je verze Pythonu 2 či Pythonu 3).
Příkaz pro instalaci Jedi ve variantě pro Python 3 a vlastní průběh instalace bude vypadat následovně:
$ pip3 install --user jedi Collecting jedi Downloading https://files.pythonhosted.org/packages/3d/68/8bbf0ef969095a13ba0d4c77c1945bd86e9811960d052510551d29a2f23b/jedi-0.12.1-py2.py3-none-any.whl (174kB) 100% |████████████████████████████████| 184kB 1.6MB/s Collecting parso>=0.3.0 (from jedi) Downloading https://files.pythonhosted.org/packages/09/51/9c48a46334be50c13d25a3afe55fa05c445699304c5ad32619de953a2305/parso-0.3.1-py2.py3-none-any.whl (88kB) 100% |████████████████████████████████| 92kB 2.2MB/s Installing collected packages: parso, jedi Successfully installed jedi-0.12.1 parso-0.3.1
Korektnost instalace si můžeme velmi snadno ověřit. Nejprve spustíme interpret Pythonu. Pokud jste pro instalaci použili příkaz pip2, spustíte samozřejmě interpret Pythonu 2.x, v případě použití příkazu pip3 pak interpret Pythonu 3.x:
$ python3 Python 3.6.3 (default, Oct 9 2017, 12:11:29) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>>
Následně se po zobrazení výzvy (prompt) pokusíme naimportovat hlavní modul knihovny Jedi:
>>> import jedi
V případě, že předchozí příkaz skončil bez chyby, můžeme se například pokusit vypsat si nápovědu (dokumentační řetězec) k této knihovně:
>>> help("jedi") Help on package jedi: NAME jedi DESCRIPTION Jedi is a static analysis tool for Python that can be used in IDEs/editors. Its historic focus is autocompletion, but does static analysis for now as well. Jedi is fast and is very well tested. It understands Python on a deeper level than all other static analysis frameworks for Python. Jedi has support for two different goto functions. It's possible to search for related names and to list all names in a Python file and infer them. Jedi understands docstrings and you can use Jedi autocompletion in your REPL as well. Jedi uses a very simple API to connect with IDE's. There's a reference implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_, which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs. It's really easy.
Pokud nastane situace, že knihovna nebyla nainstalována, nebo došlo při instalaci k nějakým dalším problémům (špatně zadané cesty atd.), vypíše se po pokusu o import tato chyba:
Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named 'jedi'
3. Využití možností nabízených knihovnou Jedi v interpretru Pythonu (REPL)
Již v úvodní kapitole jsme si řekli, že knihovnu Jedi, přesněji řečeno její část umožňující automatické doplňování kódu (jména metod a funkcí), je možné použít i ve standardním interpretru Pythonu. Ukažme si nyní, jak lze tuto užitečnou vlastnost do interpretru vlastně přidat. Nejdříve je nutné nastavit proměnnou prostředí nazvanou PYTHONSTARTUP, do které je možné zapsat jméno souboru s inicializačním skriptem (blíže viz oficiální dokumentace k Pythonu). Nastavení by pro knihovnu Jedi mělo vypadat následovně:
$ export PYTHONSTARTUP="$(python3 -m jedi repl)"
Dále již můžeme běžným způsobem spustit interpret Pythonu. Povšimněte si ovšem posledního řádku zobrazeného před výzvou (prompt). Tento řádek nám oznamuje, že se knihovna Jedi inicializovala a bude použita při zadávání příkazů:
$ python3 Python 3.6.3 (default, Oct 9 2017, 12:11:29) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)] on linux Type "help", "copyright", "credits" or "license" for more information. REPL completion using Jedi 0.12.1 >>>
Pokusme se nyní využít automatické doplňování kódu nabízené touto knihovnou. Nejdříve (čistě jen pro příklad) naimportujeme standardní knihovnu time:
>>> import time
Následně zadáme část příkazu time.asctime().is a namísto doplnění celého jména funkce stlačíme klávesu [Tab]. V tomto okamžiku je knihovně Jedi předán zapsaný skript (více viz navazující kapitoly) a knihovna Jedi vrátí seznam metod objektu typu str, které začínají na prefix „is“:
>>> time.asctime().is ...alnum ...digit ...numeric ...title ...alpha ...identifier ...printable ...upper ...decimal ...lower ...space
4. Základní funkce Jedi – automatické doplňování
Nyní si popíšeme základní koncepty, na nichž je postaveno veřejné API knihovny Jedi. Ukažme si nejprve, jakým způsobem je možné funkci pro automatické doplňování kódu (jmen funkcí atd.) ukázanou prakticky v předchozí kapitole využít programově, tj. v běžném skriptu naprogramovaném přímo v Pythonu. Přitom si vysvětlíme základní třídu Script, na níž je postavena základní část API knihovny Jedi.
Nejprve provede import knihovny:
import jedi
Následně deklarujeme řetězec src reprezentující zdrojový kód, nad nímž budeme automatické doplňování spouštět. Pro jednoduchost bude tento kód reprezentován přímo v těle příkladu formou víceřádkového řetězce, tedy následovně (v praxi by tento řetězec posílal přímo textový editor nebo IDE):
src = ''' anybody=True answer="42" an'''
Následuje nejdůležitější část celého skriptu – předání zdrojového kódu knihovně Jedi a určení, na kterém místě se nachází pomyslný kurzor, protože právě toto místo bude knihovna Jedi při analýze považovat za klíčové pro funkci automatického doplňování. Pozice kurzoru je vyjádřena dvěma celými čísly – číslem řádku a číslem sloupce, přičemž čísla řádků začínají od jedničky (tak je tomu ostatně i v textových editorech). Číslo sloupce nemusíme složitě počítat, ale použijeme funkci len():
1 2 anybody=True 3 answer="42" 4 an[pozice kurzoru vypočtená funkcí len()]
Posledním parametrem předávaným konstruktoru třídy Script je jméno modulu, ale to můžeme ponechat prázdné (prozatím ho nevyužijeme):
Zavolání konstruktoru třídy Script() tedy bude vypadat následovně:
script = jedi.Script(src, 4, len('an'), '')
Metoda pro zjištění všech možností, které nám prefix „an“ nabízí, se zavolá následujícím způsobem:
print(script.completions())
Metoda Script.completions() by měla vrátit seznam obsahující tři objekty typu Completion. Tyto objekty představují texty „answer“, „any“ a „anybody“, tj. všechny možné identifikátory, které mají v kontextu zdrojového kódu smysl („answer“ a „anybody“ je deklarován přímo v analyzovaném zdrojovém kódu, „any“ je naproti tomu standardní vestavěná funkce Pythonu):
[<Completion: answer>, <Completion: any>, <Completion: anybody>]
Následuje výpis úplného zdrojového kódu dnešního prvního demonstračního příkladu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' anybody=True answer="42" an''' script = jedi.Script(src, 4, len('an'), '') print(script.completions())
5. Atributy objektů představujících návrhy na doplnění zdrojového textu
V předchozí kapitole jsme si řekli, že metoda Script.completions() nevrací pouze seznam textů (řetězců), které v daném místě kódu dávají smysl pro doplnění. To by sice mohlo dostačovat, ale pouze pro ty nejjednodušší IDE. V moderních IDE se většinou očekává, že technologie pro automatické doplňování kódu zobrazí i další relevantní údaje, například kde je definován identifikátor, který je nabízen pro automatické doplnění atd. A právě tyto údaje je možné získat z objektů typu Completion, které jsou metodou Script.completions() vráceny.
Tyto objekty obsahují atribut nazvaný complete, který obsahuje pouze text, který se má doplnit za již zapsaný text. V našem ukázkovém skriptu se po zadání prefixu „an“ vrátí tři objekty s těmito atributy complete:
swer y ybody
Naproti tomu atribut name obsahuje plné jméno pro doplnění, tedy včetně zapsaného prefixu:
answer any anybody
Díky existenci těchto dvou atributů je implementace technologie pro automatické doplňování do IDE či textových editorů jednodušší.
Opět se podívejme na demonstrační příklad, který vypíše obsah obou výše popsaných atributů pro všechny získané objekty typu Completion:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' anybody=True answer="42" an''' script = jedi.Script(src, 4, len('an'), '') completions = script.completions() for completion in completions: print(completion.complete) print() for completion in completions: print(completion.name)
6. Dokumentační řetězce u návrhů doplnění zdrojového textu
Další informací obsaženou v objektech typu Completion je dokumentační řetězec, který je ovšem pochopitelně vyplněn pouze ve chvíli, kdy je v analyzovaných zdrojových kódech nalezen. Dokumentační řetězec lze získat zavoláním metody docstring() (nikoli atributu docstring), což je ukázáno v dnešním třetím demonstračním příkladu, v němž se bude funkce pro automatické doplnění kódu spouštět nad skriptem obsahujícím i funkci s dokumentačním řetězcem:
1 2 def anagrams(word): 3 """Very primitive anagram generator.""" 4 if len(word) < 2: 5 return word 6 else: 7 tmp = [] 8 for i, letter in enumerate(word): 9 for j in anagrams(word[:i]+word[i+1:]): 10 tmp.append(j+letter) 11 return tmp 12 13 anybody=True 14 answer="42" 15 an[pozice kurzoru pro automatické doplňování]
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def anagrams(word): """Very primitive anagram generator.""" if len(word) < 2: return word else: tmp = [] for i, letter in enumerate(word): for j in anagrams(word[:i]+word[i+1:]): tmp.append(j+letter) return tmp anybody=True answer="42" an''' lines = src.count('\n') script = jedi.Script(src, lines+1, len('an'), '') completions = script.completions() for completion in completions: print(completion.name) print("-"*40) print(completion.docstring()) print("\n"*3)
Výsledek bude vypadat následovně. Povšimněte si, že dokumentační řetězec není vypsán u proměnných, u nichž není definován:
anagrams ---------------------------------------- anagrams(word) Very primitive anagram generator. answer ---------------------------------------- any ---------------------------------------- Return True if bool(x) is True for any x in the iterable. If the iterable is empty, return False. anybody ----------------------------------------
7. Podpora pro skoky na deklarace funkcí, tříd nebo metod
V knihovně Jedi nalezneme i další užitečnou funkcionalitu. Jedná se zejména o zjištění podrobnějších informací o volané funkci nebo metodě. Jméno funkce/metody je opět získáno na místě, kde se nachází pomyslný kurzor definovaný číslem řádku a sloupce. Pro tento účel se používá metoda Script.goto_definitions(), která vrací seznam objektů typu Definition (skutečně se opět jedná o seznam, důvod bude vysvětlen v navazujících kapitolách). Samotná třída Definition je popsaná na stránce https://jedi.readthedocs.io/en/latest/docs/api-classes.html#jedi.api.classes.Definition; její použití si ukážeme na několika demonstračních příkladech zmíněných níže.
8. Použití metody Script.goto_definitions()
V dalším demonstračním příkladu si ukážeme použití metody Script.goto_definitions() pro získání podrobnějších informací o funkcích x a y volaných na řádcích 5 a 6 (v obou případech se jedná o sloupec 7, kde začíná volání funkce):
1 2 def x(): 3 return 42 4 5 print(x()) 6 print(y())
Informace o těchto funkcích lze přečíst následujícím způsobem (povšimněte si použití jména skriptu „example.py“, které může být vymyšleno a typicky ho knihovně Jedi dodá textový editor):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def x(): return 42 print(x()) print(y()) ''' script = jedi.Script(src, 5, 7, 'example.py') goto_definitions = script.goto_definitions() print(goto_definitions) print("-"*40) script = jedi.Script(src, 6, 7, 'example.py') goto_definitions = script.goto_definitions() print(goto_definitions)
Výsledkem bude v prvním případě jednoprvkový seznam obsahující (jediný) objekt typu Definition. V případě druhém se vrátí prázdný seznam, protože knihovna Jedi žádné informace o funkci y nedokáže zjistit:
[<Definition def x>] ---------------------------------------- []
9. Získání podrobnějších informací o deklarované funkci, třídě nebo metodě
Ukažme si ještě poněkud složitější příklad, v němž budeme zjišťovat informace o volaných funkcích i anonymních funkcích v následujícím skriptu (viz zvýrazněné pasáže):
1 2 def x(): 3 return 42 4 5 def y(): 6 return 42 7 8 print(x()) 9 print(y()) 10 print(z()) 11 12 w = lambda: 42 13 14 print(w())
Každý objekt typu Definition obsahuje několik atributů s typem objektu (zda se jedná o funkci atd.), plným jménem objektu, jménem modulu a taktéž řádkem, na němž byl objekt definován. O výpis se postará tato pomocná funkce:
def print_definitions(source, line, column, module): print("-"*40) script = jedi.Script(source, line, column, module) goto_definitions = script.goto_definitions() if not goto_definitions: print("not found") return for definition in goto_definitions: print("{type} {name} in {module}.py:{line}".format(type=definition.type, name=definition.full_name, module=definition.module_name, line=definition.line))
Zjištění a zobrazení informací pro výše zvýrazněné pasáže zajistí tyto čtyři řádky:
script = print_definitions(src, 8, 7, 'example.py') script = print_definitions(src, 9, 7, 'example.py') script = print_definitions(src, 10, 7, 'example.py') script = print_definitions(src, 14, 7, 'example.py')
S výsledky ukazujícími přesné místo deklarace příslušné funkce nebo lambda funkce:
---------------------------------------- function __main__.x in example.py:2 ---------------------------------------- function __main__.y in example.py:5 ---------------------------------------- not found ---------------------------------------- function __main__.<lambda> in example.py:12
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def x(): return 42 def y(): return 42 print(x()) # line 8 print(y()) # line 9 print(z()) # line 10 w = lambda: 42 print(w()) # line 14 ''' def print_definitions(source, line, column, module): print("-"*40) script = jedi.Script(source, line, column, module) goto_definitions = script.goto_definitions() if not goto_definitions: print("not found") return for definition in goto_definitions: print("{type} {name} in {module}.py:{line}".format(type=definition.type, name=definition.full_name, module=definition.module_name, line=definition.line)) script = print_definitions(src, 8, 7, 'example.py') script = print_definitions(src, 9, 7, 'example.py') script = print_definitions(src, 10, 7, 'example.py') script = print_definitions(src, 14, 7, 'example.py')
10. Problematika redefinice funkcí v Pythonu
Co se stane ve chvíli, kdy se pokusíme funkci deklarovat vícekrát? To je v Pythonu samozřejmě dovoleno:
1 2 def x(): 3 return 1 4 5 def x(): 6 return 2 7 8 def x(): 9 return 3 10 11 print(x())
Pro volání funkce x na jedenáctém řádku se nyní vypíše, že se volá funkce deklarovaná na osmém řádku (což je v souladu s chováním samotného Pythonu):
---------------------------------------- function __main__.x in example.py:8
Předchozí kód demonstračního příkladu byl nepatrně upraven a nyní vypadá následovně:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def x(): return 1 def x(): return 2 def x(): return 3 print(x()) ''' def print_definitions(source, line, column, module): print("-"*40) script = jedi.Script(source, line, column, module) goto_definitions = script.goto_definitions() if not goto_definitions: print("not found") return for definition in goto_definitions: print("{type} {name} in {module}.py:{line}".format(type=definition.type, name=definition.full_name, module=definition.module_name, line=definition.line)) script = print_definitions(src, 11, 7, 'example.py')
11. Dynamické chování Pythonu a jeho vliv na zjišťování informací o volaných funkcích
Víme již, že návratovou hodnotou metody Script.goto_definitions() je seznam objektů typu Definition. Proč tomu tak je si ukážeme na dalším testovacím skriptu:
1 2 if random.random() < 0.5: 3 def x(): 4 return 1 5 else: 6 def x(): 7 return 2 8 9 print(x())
Při získání informací o funkci volané na devátém řádku získáme seznam se dvěma objekty Definition, protože knihovna Jedi nemůže ze statické analýzy kódu zjistit, která funkce se skutečně zavolá (resp. přesněji řečeno, která funkce je vůbec deklarována):
---------------------------------------- function __main__.x in example.py:3 function __main__.x in example.py:6
Opět si pro úplnost ukažme zdrojový kód tohoto příkladu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' if random.random() < 0.5: def x(): return 1 else: def x(): return 2 print(x()) ''' def print_definitions(source, line, column, module): print("-"*40) script = jedi.Script(source, line, column, module) goto_definitions = script.goto_definitions() if not goto_definitions: print("not found") return for definition in goto_definitions: print("{type} {name} in {module}.py:{line}".format(type=definition.type, name=definition.full_name, module=definition.module_name, line=definition.line)) script = print_definitions(src, 9, 7, 'example.py')
12. Složitější příklad ukazující dynamické chování Pythonu
Předchozí příklad je možné upravit do ještě složitější podoby, tentokrát dokonce s lambda funkcí:
1 2 def x(): 3 return 1 4 5 def y(): 6 return 2 7 8 z = lambda: 3 9 10 if random.random() < 0.3: 11 f = x 12 else: 13 if random.random() < 0.3: 14 f = y 15 else: 16 f = z 17 18 print(f())
Pokud se pokusíme získat informaci o deklaraci funkce x volané na osmnáctém řádku, dostaneme tři objekty typu Definition; u posledního objektu dokonce s korektní informací o tom, že se jedná o lambda funkci:
---------------------------------------- function __main__.x in example.py:2 function __main__.y in example.py:5 function __main__.<lambda>; in example.py:8
Opět si pro úplnost ukažme zdrojový kód tohoto příkladu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def x(): return 1 def y(): return 2 z = lambda: 3 if random.random() < 0.3: f = x else: if random.random() < 0.3: f = y else: f = z print(f()) ''' def print_definitions(source, line, column, module): print("-"*40) script = jedi.Script(source, line, column, module) goto_definitions = script.goto_definitions() if not goto_definitions: print("not found") return for definition in goto_definitions: print("{type} {name} in {module}.py:{line}".format(type=definition.type, name=definition.full_name, module=definition.module_name, line=definition.line)) lines = src.count('\n') script = print_definitions(src, lines, 7, 'example.py')
13. Detekce míst ve zdrojovém kódu, v němž je nějaká funkce použita
V dalším příkladu si ukážeme jeden ze způsobů, jakým je možné získat seznam míst v analyzovaném zdrojovém kódu, v němž je nějaká funkce použita (tedy volána) nebo deklarována. Testovací skript, který budeme analyzovat, vypadá následovně:
1 2 def x(): 3 return 1 4 5 a = x() 6 b = x() + x() 7 8 print(x())
Povšimněte si, že na čtyřech místech voláme funkci x, která je deklarována na řádku 2. Seznam deklarací a použití této funkce získáme metodou Script.usages(), tedy přibližně následujícím způsobem:
script = jedi.Script(source, line, column, module) usages = script.usages()
Záleží jen na nás, jaký výskyt funkce x zvolíme při volání konstruktoru Script(). Může se jednat například o řádek 8, sloupec 7, tj. o poslední volání funkce x:
1 2 def x(): 3 return 1 4 5 a = x() 6 b = x() + x() 7 8 print(x())
Výsledek:
function __main__.x in example.py:2 statement __main__.x in example.py:5 statement __main__.x in example.py:6 statement __main__.x in example.py:6 statement __main__.x in example.py:8
Ovšem naprosto stejnou informaci získáme ve chvíli, kdy budeme analyzovat řádek 2, sloupec 5, tj. deklaraci funkce:
1 2 def x(): 3 return 1 4 5 a = x() 6 b = x() + x() 7 8 print(x())
Výsledek je v tomto případě shodný s výsledkem předchozím:
function __main__.x in example.py:2 statement __main__.x in example.py:5 statement __main__.x in example.py:6 statement __main__.x in example.py:6 statement __main__.x in example.py:8
Celý zdrojový kód, který tyto výsledky vypisuje, vypadá následovně:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def x(): return 1 a = x() b = x() + x() print(x()) ''' def print_usages(source, line, column, module): script = jedi.Script(source, line, column, module) usages = script.usages() if not usages: print("not found") return for usage in usages: print("{type} {name} in {module}.py:{line}".format(type=usage.type, name=usage.full_name, module=usage.module_name, line=usage.line)) script = print_usages(src, 8, 7, 'example.py') print() script = print_usages(src, 2, 5, 'example.py')
14. Složitější příklad s redeklarací funkcí
Z předchozího příkladu by se mohlo zdát, že je úplně jedno, jaký výskyt funkce ve zdrojovém kódu vybereme – vždy by se měl (zdánlivě) vrátit stejný seznam použití a deklarací této funkce. Ve skutečnosti to však pochopitelně není zcela pravdivé, protože díky dynamické povaze Pythonu je možné funkci redeklarovat a zcela tak pozměnit její chování. Ostatně podívejme se na následující skript, v němž je funkce x deklarována dvakrát:
1 2 def x(): 3 return 1 4 5 print(x()) 6 7 a = x() 8 b = x() + x() 9 10 def x(): 11 return 2 12 13 print(x())
Pokud se zeptáme na použití funkce na řádku 5, sloupci 7:
1 2 def x(): 3 return 1 4 5 print(x()) 6 7 a = x() 8 b = x() + x() 9 10 def x(): 11 return 2 12 13 print(x())
Dostaneme výsledek odpovídající zhruba prvním dvou třetinám skriptu (až do druhé deklarace funkce x):
function __main__.x in example.py:2 statement __main__.x in example.py:5 statement __main__.x in example.py:7 statement __main__.x in example.py:8 statement __main__.x in example.py:8
Naproti tomu při analýze řádku 13, sloupce 7:
1 2 def x(): 3 return 1 4 5 print(x()) 6 7 a = x() 8 b = x() + x() 9 10 def x(): 11 return 2 12 13 print(x())
Bude výsledek zcela odlišný a bude plně odpovídat chování Pythonu:
function __main__.x in example.py:10 statement __main__.x in example.py:13
Tato užitečná vlastnost knihovny Jedi je ukázána v dnešním posledním příkladu, jehož úplný zdrojový kód je následující:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 import jedi src = ''' def x(): return 1 print(x()) a = x() b = x() + x() def x(): return 2 print(x()) ''' def print_usages(source, line, column, module): script = jedi.Script(source, line, column, module) usages = script.usages() if not usages: print("not found") return for usage in usages: print("{type} {name} in {module}.py:{line}".format(type=usage.type, name=usage.full_name, module=usage.module_name, line=usage.line)) script = print_usages(src, 5, 7, 'example.py') print() script = print_usages(src, 13, 7, 'example.py') print() script = print_usages(src, 2, 5, 'example.py')
15. Využití knihovny Jedi v pluginu jedi-vim
Podívejme se nyní na jeden ze způsobů praktického využití knihovny Jedi. Tato knihovna je totiž intenzivně používaná například přídavným modulem (pluginem) nazvaným jedi-vim, který je pochopitelně určený, jak již jeho název napovídá, pro textový editor Vim. Díky propojení Vimu s knihovnou Jedi je možné přímo z prostředí textového editoru používat prakticky všechny základní operace, na které jsme zvyklí z „plnohodnotných“ integrovaných vývojových prostředí (IDE). Zejména se jedná o automatické doplňování jmen funkcí, tříd, metod a proměnných, zobrazení dynamické nápovědy s parametry funkce či metody (u Pythonu bez uvedení typu), skok na deklaraci funkce, třídy, metody, zobrazení dokumentace k prakticky libovolnému objektu, nad nímž se nachází kurzor a konečně o operaci přejmenování (samozřejmě v míře použitelné pro tak dynamický jazyk, jakým Python bezesporu je – nečekejte zde stoprocentní funkčnost v míře obvyklé například pro Javu).
Obrázek 2: Pro bezproblémovou funkčnost pluginu jedi-vim je nutné, aby byl textový editor Vim přeložen s povolenou volbou +conceal. Existenci této volby zjistíme například příkazem :ver či :version.
Obrázek 3: Na tomto screenshotu je patrné, že volba +conceal byla na mém systému skutečně při překladu Vimu povolena.
Před popisem základních vlastností pluginu jedi-vim si samozřejmě musíme tento plugin nainstalovat. Ve Fedoře (ale i v dalších linuxových distribucích) je to jednoduché, neboť jedi-vim je většinou dostupný ve standardním repositáři distribuce a tím pádem i ve správci balíčků. Pro větší zmatek se však tento balíček někdy nejmenuje jedi-vim, ale naopak vim-jedi :-) Instalaci tedy provedeme buď z účtu superuživatele:
# dnf install vim-jedi
Nebo alternativně z účtu toho uživatele, který má příslušná práva nastavená v sudoers:
$ sudo dnf install vim-jedi
Samotná instalace je provedena prakticky okamžitě:
Last metadata expiration check: 1:19:11 ago on Fri 17 Aug 2018 05:30:26 AM EDT. Dependencies resolved. ================================================================================ Package Arch Version Repository Size ================================================================================ Installing: vim-jedi noarch 0.8.0-4.fc28 Fedora-Everything 28 k Installing dependencies: python2-jedi noarch 0.12.0-1.fc28 updates 291 k python2-parso noarch 0.2.1-1.fc28 updates 142 k Transaction Summary ================================================================================ Install 3 Packages Total download size: 461 k Installed size: 1.9 M Is this ok [y/N]: y
Vidíme, že se v případě potřeby doinstaluje i samotná knihovna Jedi:
Downloading Packages: (1/3): vim-jedi-0.8.0-4.fc28.noarch.rpm 508 kB/s | 28 kB 00:00 (2/3): python2-parso-0.2.1-1.fc28.noarch.rpm 375 kB/s | 142 kB 00:00 (3/3): python2-jedi-0.12.0-1.fc28.noarch.rpm 674 kB/s | 291 kB 00:00 -------------------------------------------------------------------------------- Total 524 kB/s | 461 kB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : python2-parso-0.2.1-1.fc28.noarch 1/3 Installing : python2-jedi-0.12.0-1.fc28.noarch 2/3 Installing : vim-jedi-0.8.0-4.fc28.noarch 3/3 Running scriptlet: vim-jedi-0.8.0-4.fc28.noarch 3/3 Verifying : vim-jedi-0.8.0-4.fc28.noarch 1/3 Verifying : python2-jedi-0.12.0-1.fc28.noarch 2/3 Verifying : python2-parso-0.2.1-1.fc28.noarch 3/3 Installed: vim-jedi.noarch 0.8.0-4.fc28 python2-jedi.noarch 0.12.0-1.fc28 python2-parso.noarch 0.2.1-1.fc28 Complete!
16. Možnosti nabízené pluginem jedi-vim
Použití pluginu jedi-vim při editaci zdrojových kódů napsaných v Pythonu si nejlépe ukážeme na několika screenshotech:
Obrázek 4: Po instalaci pluginu jedi-vim je k dispozici i nápověda vyvolaná příkazem :help jedi-vim.
Obrázek 5: Základní funkcí pluginu jedi-vim je automatické doplňování kódu vyvolané klávesovou zkratkou Ctrl+Space ve vkládacím režimu.
Obrázek 6: Zobrazení dokumentačního řetězce v režimu automatického doplňování.
Obrázek 7: Nastavení zvýraznění skupiny jedifat v případě, že nápověda k parametrům funkcí není čitelná (což skutečně není ve chvíli, kdy je použit terminálu se světlým pozadím). Bližší informace o této vlastnosti Vimu lze najít v článku Textový editor Vim jako IDE (4.část) .
Obrázek 8: Zobrazení parametrů funkce se zvýrazněním právě zadávaného parametru.
Obrázek 9: Zobrazení parametrů funkce se zvýrazněním právě zadávaného parametru.
Obrázek 10: Zobrazení parametrů funkce se zvýrazněním právě zadávaného parametru.
Obrázek 11: Zobrazení dokumentačního řetězce pro identifikátor, nad nímž se nachází kurzor. Tato funkce je dostupná po stisku klávesové zkratky K.
17. Obsah druhé části článku
Ve druhé části tohoto článku popis možností nabízených knihovnou Jedi dokončíme. Ukážeme si další funkcionalitu dostupnou při statické analýze a taktéž si popíšeme možnosti nastavení a konfigurace virtuálního prostředí Pythonu, v jehož rámci je vlastně statická analýza prováděna.
18. Repositář s demonstračními příklady
Všechny dnes popisované demonstrační příklady ukazující některé možnosti knihovny Jedi byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/presentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
19. Odkazy na Internetu
- Jedi – an awesome autocompletion/static analysis library for Python
https://jedi.readthedocs.io/en/latest/index.html - Jedi API Overview
https://jedi.readthedocs.io/en/latest/docs/api.html - jedi-vim
https://github.com/davidhalter/jedi-vim - YouCompleteMe: A code-completion engine for Vim
https://valloric.github.io/YouCompleteMe/ - Elpy: Emacs Python Development Environment
https://github.com/jorgenschaefer/elpy - Emacs-Jedi
https://github.com/tkf/emacs-jedi - Python Autocomplete Jedi Package
https://atom.io/packages/autocomplete-python-jedi - Autocomplete (Wikipedia)
https://en.wikipedia.org/wiki/Autocomplete - Seriál Textový editor Vim jako IDE (zde na Root.cz)
https://www.root.cz/serialy/textovy-editor-vim-jako-ide/ - Jedi.el – Python auto-completion for Emacs
https://tkf.github.io/emacs-jedi/latest/