Pozor, článek je v současné podobě zavádějící.
Jak se píše v oficiální dokumentaci hned několikrát, RPython primárně NENÍ překladač pythonu do nativního kódu! Vlastními slovy autorů:
RPython is a translation and support framework for producing implementations of dynamic languages, emphasizing a clean separation between language specification and implementation aspects.
V čem je rozdíl? Především v tom, že RPython je zaměřený na tvorbu ostatních jazyků, ne na tvorbu binárek a knihoven.
Například přidává velmi dobrou podporu JITování vašeho projektu, kde je možné označit funkce, které je možné JITovat a on je zJITuje. To přitom v normálním kódu nedává moc smysl, neboť je to celé určené směrem k drcení instrukcí bajtkódu (neustále opakovaný lookup do pole, načtení argumentu, zavolání interpretace a tohle pořád dokola).
Další věc je, že RPython je silně, silně omezený subset pythonu, kde je nutné přes assert isinstance() dávat type hinty asi tak každých pět řádek, pole můžou obsahovat jen jeden datový typ a tak podobně.
Proto taky píšou ve FAQ vysloveně:
Can RPython compile normal Python programs to C?
No, RPython is not a Python compiler.
In Python, it is mostly impossible to prove anything about the types that a program will manipulate by doing a static analysis. It should be clear if you are familiar with Python, but if in doubt see [BRETT].
If you want a fast Python program, please use the PyPy JIT instead.
Jazyky v RPythonu nejsou implementovány proto, že by byl tak dobrý překladač do binárního kódu, ale právě proto, že nabízí velkou podporu věcí, které dávají smysl jen v interpretrech programovacích jazyků. Například bindingy na numerické operace nad velkými inty. Možnost definovat si vlastní staticky typované dicty. Volit verzi garbage collectoru. Nechat funkci statisticky poměřit, jestli se vyplatí JITovat a tak podobně.
Pokud chcete překladač do C, či nativního kódu, je k dispozici spousta jiných projektů, například Nuitka, Rusthon, grumpy, cython, shedskin, Theano či Parakeet .
Jako někdo, kdo momentálně píše v RPythonu interpret jazyka a historicky zkoušel různé překladače do C a celkově transkompilery (brython například), můžu potvrdit, že ten rozdíl není malý. Pokud to chcete použít jen k "zrychlení python kódu", tak jsou lepší projekty a s rpythonem budete bojovat na každém kroku.
Přepisoval jsem jen asi 700 řádkový parser z čistého pythonu do RPythonu a dalo mi to zabrat několik dní. Nejenom protože jsem z toho musel udělat staticky typovanou verzi programu, ale hlavně a především proto, že RPython nenabízí kvalitní chybové hlášky. Jeho použitím prostě nikdy nemělo být jako překladač do C, takže místo informace o čísle řádku dostanete něco jako
[translation:info] 2.7.10 (5.1.2+dfsg-1~16.04, Jun 16 2016, 17:37:42)
[PyPy 5.1.2 with GCC 5.3.1 20160413]
[platform:msg] Set platform with 'host' cc=None, using cc='gcc', version='Unknown'
[translation:info] Translating target as defined by src/tinySelf/target
[translation] translate.py configuration:
[translation] [translate]
targetspec = src/tinySelf/target
[translation] translation configuration:
[translation] [translation]
gc = incminimark
gctransformer = framework
list_comprehension_operations = True
withsmallfuncsets = 5
[translation:info] Annotating&simplifying...
[33] {translation-task
starting annotate
[translation:info] with policy: rpython.annotator.policy.AnnotatorPolicy
[f5] translation-task}
[Timer] Timings:
[Timer] annotate --- 6.1 s
[Timer] ========================================
[Timer] Total: --- 6.1 s
[translation:info] Error:
File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/goal/translate.py", line 318, in main
drv.proceed(goals)
File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/driver.py", line 551, in proceed
result = self._execute(goals, task_skip = self._maybe_skip())
File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/tool/taskengine.py", line 114, in _execute
res = self._do(goal, taskcallable, *args, **kwds)
File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/driver.py", line 278, in _do
res = func()
File "/home/bystrousak/Plocha/tests/pypy/rpython/translator/driver.py", line 315, in task_annotate
s = annotator.build_types(self.entry_point, self.inputtypes)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 92, in build_types
return self.build_graph_types(flowgraph, inputs_s, complete_now=complete_now)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 140, in build_graph_types
self.complete()
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 229, in complete
self.complete_pending_blocks()
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 224, in complete_pending_blocks
self.processblock(graph, block)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 398, in processblock
self.flowin(graph, block)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 501, in flowin
self.consider_op(op)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/annrpython.py", line 653, in consider_op
resultcell = op.consider(self)
File "/home/bystrousak/Plocha/tests/pypy/rpython/flowspace/operation.py", line 104, in consider
return spec(annotator, *self.args)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/unaryop.py", line 118, in simple_call_SomeObject
return s_func.call(argspec)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/unaryop.py", line 978, in call
return bookkeeper.pbc_call(self, args)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/bookkeeper.py", line 535, in pbc_call
s_result = unionof(*results)
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/model.py", line 771, in unionof
s1 = pair(s1, s2).union()
File "/home/bystrousak/Plocha/tests/pypy/rpython/annotator/binaryop.py", line 93, in union
raise UnionError(obj1, obj2)
[translation:ERROR] UnionError:
Offending annotations:
SomeInstance(can_be_None=True, classdef=rply.token.BaseBox)
SomeTuple(items=(SomeString(const='$end', no_nul=True), SomeList(listdef=<[SomeString(const='$end', no_nul=True)]>)))
Occurred processing the following simple_call:
function at_the_top_of_the_root_is_just_expression <src/tinySelf/parser.py, line 65> returning
function multiple_expressions_make_code <src/tinySelf/parser.py, line 70> returning
function self_parser <src/tinySelf/parser.py, line 82> returning
function expression_number <src/tinySelf/parser.py, line 88> returning
function expression_string <src/tinySelf/parser.py, line 94> returning
function expression_strings_numbers <src/tinySelf/parser.py, line 106> returning
function unary_message <src/tinySelf/parser.py, line 113> returning
function unary_message_to_expression <src/tinySelf/parser.py, line 118> returning
function binary_message_to_expression <src/tinySelf/parser.py, line 124> returning
function keyword_message <src/tinySelf/parser.py, line 133> returning
function keyword_message_to_obj <src/tinySelf/parser.py, line 138> returning
function keyword <src/tinySelf/parser.py, line 143> returning
function keyword_multiple <src/tinySelf/parser.py, line 148> returning
function keyword_message_with_parameters <src/tinySelf/parser.py, line 158> returning
function keyword_message_to_self_with_parameters <src/tinySelf/parser.py, line 185> returning
function keyword_message_to_obj_with_parameters <src/tinySelf/parser.py, line 212> returning
function all_kinds_of_messages_are_message <src/tinySelf/parser.py, line 239> returning
function expression_is_message <src/tinySelf/parser.py, line 245> returning
function cascade <src/tinySelf/parser.py, line 267> returning
function cascades <src/tinySelf/parser.py, line 281> returning
function expression_cascade <src/tinySelf/parser.py, line 297> returning
function slot_names <src/tinySelf/parser.py, line 316> returning
function nil_slot_definition <src/tinySelf/parser.py, line 332> returning
function slot_definition <src/tinySelf/parser.py, line 339> returning
function slot_definition_rw <src/tinySelf/parser.py, line 362> returning
function nil_argument_definition <src/tinySelf/parser.py, line 370> returning
function slot_name_kwd_one <src/tinySelf/parser.py, line 378> returning
function slot_name_kwd_multiple <src/tinySelf/parser.py, line 383> returning
function slot_name_kwd <src/tinySelf/parser.py, line 393> returning
value_0 = simple_call(v0, targ_0)
In <FunctionGraph of (rply.parser:67)LRParser._reduce_production at 0x6f6e1e0>:
Happened at file /home/bystrousak/.local/lib/pypy2.7/site-packages/rply/parser.py line 80
==> value = p.func(targ)
Known variable annotations:
v0 = SomePBC(can_be_None=True, descriptions={...29...}, knowntype=function, subset_of=None)
targ_0 = SomeList(listdef=<[SomeInstance(can_be_None=False, classdef=rply.token.Token)]mr>)
Processing block:
block@164[targ_0...] is a <class 'rpython.flowspace.flowcontext.SpamBlock'>
in (rply.parser:67)LRParser._reduce_production
containing the following operations:
v0 = getattr(p_0, ('func'))
value_0 = simple_call(v0, targ_0)
--end--
[translation] start debugger...
> /home/bystrousak/Plocha/tests/pypy/rpython/annotator/binaryop.py(93)union()
-> raise UnionError(obj1, obj2)
ze které je relevantní asi tak tohle:
Offending annotations: SomeInstance(can_be_None=True, classdef=rply.token.BaseBox) SomeTuple(items=(SomeString(const='$end', no_nul=True), SomeList(listdef=<[SomeString(const='$end', no_nul=True)]>)))
Nedostanete však číslo řádku, ani jméno funkce kde se to děje (možná to tak vypadá v tom výpisu, ale to jsou irelevantní stack-traces, tu chybu jsem nakonec našel úplně někde jinde zakomentováváním kusů kódu). Vidíte jen že je to nějaký tuple ve kterém jsou stringy a listy. Pěkný článek o tom je tady: The Magic of RPython.
taky jsme jednu věc postupně přepsali a pozor na to, že ten The Magic of Python článek je pro starší verzi RPythonu. Dneska už spoustu umělých omezení odstranili, například to nemusí být staticky typované, to už neplatí. Jen je potřeba v každém control pointu znát typ hodnoty v proměnné
No ty moje hlášky a zkušenosti jsou měsíc staré, vůči vývojové verzi RPythonu (přímo z repa). Takže je možné, že jsou lepší, ale pořád jsou dost příšerné.
například to nemusí být staticky typované, to už neplatí
Počkat, co? Já na to narážel asi tak pořád, což mě nutilo psát "kontejner" třídy poděděné od stejného předka. Jak to myslíš?
No jde o to, že kdysi RPython odvodil typ proměnné z typu první hodnoty, která do ní byla přiřazena. A potom to muselo všude sedět. Ale nedávno (?) se to změnilo a RPython dokáže s typem hýbat, samozřejmě pokud si je pořád jistý (žádné přiřazení různých typů v if-ech atd.). Takže toto bez problémů přeložíš a spustíš, ale v tom dobrém ale starším článku Magic of RPython píše, že to nejde (nešlo):
def entry_point(argv):
x = "one"
print x
x = 2
print x
x = None
print x
x = True
print x
x = range(10)
print x[1]
return 0
def target(driver, args):
return entry_point, None
Btw. umí někdo spojit možnosti [code] a [pre] tady v diskuzi? Ty krátké ukázky kódu jsou celkem na nic.
Nekde v tomto miste bych se rad diskutujicich odborniku zeptal, jestli jsem tedy velke prase, kdyz behem zivota promenne v ni mivam ruzne typy? Uplne v tom nejbeznejsim priklade rad mivam v promenne bud None nebo nejakou hodnotu. (Na to jsem si zvyknul v C#, kde byly typy napr. bool?, int? atd.) A potom jsem se s necim takovym setkal u nejake knihovny pro nacitani z excelu, kde hodnota bunky byla cislo, nebo string - podle toho, co bylo v excelu.
Jsem myslel, ze tohle se v pythonu muze a bezne dela?
To, ze holt nemuzu pouzivat RPython, asi nejak preziju.
Jde to proti filosofii čistého kódu tak, jak jí definuje kniha Čistý kód. Není to nutně špatně, ale nic podstatného tím neušetříš co do systémových prostředků a hlavně pro lidi to může být matoucí, když jedna proměnná s jedním popisným názvem je použitá v několika úplně odlišných kontextech, nedejbože pro úplně rozdílné datové typy.
Neřekl bych, že jsi velké prase, sám to tak taky občas (!) dělám, ale mohl bys to psát čistěji.
Tu knihu mimochodem vážně doporučuji. Bohužel je rozebraná a nedá se sehnat ani v antikvariátech, a na dotaz jestli nechystají dotisk, že bych si klidně koupil 10 dílů, abych je mohl rozdávat juniorům mi odpověděli, že ne. Ale PDF by se mělo dát sehnat na populárním českém webu pro sdílení souborů.
RPython v soucasne verzi zvlada nullable pro prakticky kazdy typ, takze toto problem neni. Umoznuje i zmenit typ hodnoty prirazene do promenne, ale jen ve chvili, kdy si dokaze ten typ jednoznacne odvodit. Prave proto jsem uvadel ten priklad, kde nema sanci to odvodit - vse zavisi na vecech neznamych v case prekladu (pocet args predavanych pres CLI). Ja se snazim hlavne omezovat dobu zivota promenne na co nejkratsi bloky, ale zase v Pythonu bych se typovanim nestresoval :) pokud nepouzivas MyPy a neresis si to skutecne striktne.
Já to používám běžně. Třeba u proměnných, které ukazují na svým způsobem stejná data, ale v jiné formě. Třeba mám pole, do kterého přidávám stringy (a dejme tomu ze se na nej odkazuji promennou err_message) a na závěr ho přes join spojím a udělám z něj text. Na výsledný text se odkazuji stejnou proměnnou jako na to pole.
Na druhou stranu, protože je to framework pro tvorbu interpretů, tak je obecnější než překladače do C. RPython program může být přeložen do C nebo taky do Java bajtkódu. Je možné experimentovat s různými GC atd.
Takže RPython je trochu takový akademický na druhou stranu tu je už docela dlouho a když se v tom dá implementovat interpret Pythonu (PyPy), mělo by to být použitelné i pro normální inženýrské záležitosti.
Jako, já zas nechci tvrdit, že to použitelné není. Konec konců, i socha se dá sochat šroubovákem místo dláta. Jen jsem chtěl podotknout, že to nemusí být vhodné a že existují vhodnější projekty.
Konkrétně třeba ta Nuitka je docela vyspělá (skoro 5k commitů, minimálně 8 let vývoje, poslední commit měsíc zpět) a nemusíš přepisovat věci do RPythonu, který je místy fakt omezený a úplně v něm zapomeň na standardní knihovnu pythonu, ze které tam je asi tak 10%.
Já se snažil informaci o tom, kdy, kde a proč se RPython používá, říct ve druhé kapitole. Původně jsem totiž plánoval článek o "střevech" PyPy, ale tam je toho tolik, že jsem začal s RPythonem, který se mi - musím přiznat - obecně hodně líbí.
Samozřejmě záleží na řešeném problémy, ale zrovna pro úlohy, kde se něco počítá nebo simuluje, tak (pokud to už není v Numpy) není s překladem v RPythonu problém, protože tyto úlohy typicky používají homogenní pole (eh. seznamy), žádné definice uvnitř funkcí a další hodně dynamické věci, které RPython nepřeloží (a nepřeloží je žádný static compiler).
(jinak autoři PyPy se skutečně na několika místech brání tomu, aby lidi RPython používali. Někde tvrdí, že pokud dá RPython výrazně rychlejší kód, než PyPy, je to chyba v PyPy, která by měla být hlášena. A mají částečně pravdu; na druhou stranu mít možnost z pythonu vygenerovat relativně malou binárku nebo so/DLL taky není na škodu).
Je to zatím v hodně rozpracované fázi (hotový lexer, parser a částečně objektový layout, momentálně dělám na kompilátoru a VM tak nějak zároveň), nemám na to zas tak moc času, kolik bych chtěl:
https://github.com/Bystroushaak/tinySelf
Je to Selfem inspirovaný jazyk (superset syntaxe), který by měl být ve výsledku víc prakticky orientovaný. Inspirací mi je RPySOM a RSqueak, i když samozřejmě majorita vnitřností bude fungovat jinak, kvůli na prototypech založenému objektovému modelu.
Mám o tom všem rozepsané články (hotovy 4 díly z prozatimních sedmi, ale nakonec jich bude pravděpodobně tak 10). Píšu většinou pro abclinuxu, tak to bude nejspíš časem tam. Nechci to publikovat v průběhu, aby z toho nebyl další rozepsaný seriál, na který se autor vykašle, když ho zaujme něco jiného.
Tak jsem to začal vydávat. V následujících týdnech se to bude objevovat na mém blogu. První díl je tady: https://www.abclinuxu.cz/blog/bystroushaak/2019/2/jak-se-pise-programovaci-jazyk-1-motivace