Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Lupa: propojení programovacích jazyků Lua a Python

Дата публикации: 10-08-2026 22:00:10

Dnes si popíšeme projekt nazvaný Lupa, což je zkratka získaná z „lua-python“. Tento projekt umožňuje propojení programů psaných v Pythonu se skripty zapsanými v jazyce Lua. Tyto skripty jsou spouštěny v sandboxu: v izolovaných interpretrech, což mj. zajišťuje větší bezpečnost.

Основное содержимое страницы с новостью.

S programovacím jazykem Lua jsme se již na stránkách tohoto serveru setkali, a to dokonce mnohokrát. Většina článků o tomto jazyku je shrnuta do seriálu Programovací jazyk Lua a taktéž jsme se Luou zabývali ve druhém seriálu nazvaném Torch: framework pro strojové učení (Torch se stal předlohou dnes známějšího balíčku PyTorch pro Python). Víme již, že se tento programovací jazyk stal oblíbený například mezi tvůrci her, v nichž je použit pro skriptování.

To ovšem není zdaleka vše, protože Lua je použita například v systému LuaTeX, v databázi Redis, ve webovém serveru Nginx a v neposlední řadě i v textovém editoru Neovim. Jednoduše použitelná syntaxe a současně i poměrně velká vyjadřovací schopnost jazyka Lua by však pravděpodobně nedostačovala pro jeho masovější rozšíření, a to zejména v situaci, kdy tuto niku programovacích jazyků do značné míry okupuje Python, kterému se již úspěšně podařilo „odstavit“ některé konkurenty.

Hlavním důvodem, proč jsou některé hry, například Escape from Monkey Island, Grim Fandango, Fish Fillets, Neverwinter Nights či MDK2 z menší či větší části naprogramované právě v jazyku Lua, spočívá v tom, že kombinace nízkoúrovňového a skriptovacího jazyka umožňuje soustředit se při vývoji na podstatné věci – herní engine vytvořit co nejefektivnější s využitím všech možností nízkoúrovňového jazyka a naopak herní scénář a logiku hry naskriptovat s co největším urychlením cyklu oprava–překlad–spuštění.

V mnoha případech se také využívá další užitečné vlastnosti jazyka Lua – celý překladač i interpret vygenerovaného bajtkódu (popř. pouze interpret) je možné velmi snadno zabudovat do jiné aplikace, přičemž se výsledná velikost spustitelného souboru této aplikace zvětší o cca 70 kB (popř. lze volat dynamickou knihovnu o řádově stejné velikosti), což není mnoho, když si uvědomíme, že dostáváme k dispozici plnohodnotný vysokoúrovňový programovací jazyk (ostatně Lua se díky své malé velikosti používá i pro pouhé zpracování konfiguračních souborů, které díky tomu mohou obsahovat různé konstanty, výrazy atd.).

Možná nejdůležitější je však možnost izolace jednotlivých skriptů psaných v jazyce Lua. Pro každý z těchto skriptů totiž může aplikace, která Luu využívá jako skriptovací jazyk, spustit samostatný interpret (dále nazývaný runtime), který je odizolován od ostatních interpretrů. To tedy znamená, že takový skript nedokáže měnit globální proměnné, nemůže volat funkce, které v něm nejsou definovány atd. Právě z tohoto důvodu můžeme vidět zajímavé a na první pohled možná nelogické spojení jazyka Python (tvorba ucelené aplikace) a jazyka Lua (pro skriptování). Mohlo by se zdát, že je vhodnější i pro skriptování použít Python a skripty spouštět (například) přes eval. To je sice funkční, ale o to nebezpečnější varianta. Alternativu nabízí balíček lua-python popsaný v dnešním článku.

Instalace balíčku lua-python

Projekt Lupa je dostupný ve formě běžného pythonovského balíčku, který se jmenuje lupa. Jeho instalace je tedy stejně jednoduchá, jako instalace jakéhokoli jiného balíčku určeného pro ekosystém Pythonu. Pro instalaci lze použít pip atd., ale (alespoň podle mého názoru) je vhodnější nainstalované balíčky co nejvíce odizolovat od operačního systému. Z tohoto důvodu nejdříve s využitím správců projektů PDM nebo uv (viz článek PDM: moderní správce balíčků a virtuálních prostředí Pythonu) vytvoříme nový projekt, který bude „své“ balíčky ukládat do svého podadresáře:

$ uv init lua-python
 
Initialized project `lua-python` at `/tmp/ramdisk/lua-python/lua-python`

Následně do nově vzniklého projektu nainstalujeme balíček lupa:

$ uv add lupa
 
Using CPython 3.13.9 interpreter at: /usr/bin/python3.13
Creating virtual environment at: .venv
Resolved 2 packages in 363ms
Prepared 1 package in 661ms
Installed 1 package in 7ms
 + lupa==2.8

Nově nainstalovaný balíček bude přidán do projektového souboru pyproject.toml:

[project]
name = "lua-python"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
    "lupa>=2.8",
]

Jen pro zajímavost si ověříme, jak vlastně vypadá strom závislostí:

$ uv tree
 
Resolved 2 packages in 1ms
lua-python v0.1.0
└── lupa v2.8

Poznámka: z předchozího výpisu je patrné, že nejsou vyžadovány žádné další tranzitivní závislosti.

Zajímavé bude zjistit, jaké soubory vlastně byly společně s balíčkem lupa nainstalovány. Všechny tyto soubory si můžeme zobrazit:

$ ls -l .venv/lib64/python3.13/site-packages/lupa
 
total 5116
-rw-r--r--. 1 ptisnovs ptisnovs    2961 Aug  5 18:42 __init__.py
-rwxr-xr-x. 1 ptisnovs ptisnovs  557872 Aug  5 18:42 lua51.cpython-313-x86_64-linux-gnu.so
-rwxr-xr-x. 1 ptisnovs ptisnovs  582544 Aug  5 18:42 lua52.cpython-313-x86_64-linux-gnu.so
-rwxr-xr-x. 1 ptisnovs ptisnovs  615280 Aug  5 18:42 lua53.cpython-313-x86_64-linux-gnu.so
-rwxr-xr-x. 1 ptisnovs ptisnovs  672656 Aug  5 18:42 lua54.cpython-313-x86_64-linux-gnu.so
-rwxr-xr-x. 1 ptisnovs ptisnovs  689072 Aug  5 18:42 lua55.cpython-313-x86_64-linux-gnu.so
-rwxr-xr-x. 1 ptisnovs ptisnovs  987872 Aug  5 18:42 luajit20.cpython-313-x86_64-linux-gnu.so
-rwxr-xr-x. 1 ptisnovs ptisnovs 1102816 Aug  5 18:42 luajit21.cpython-313-x86_64-linux-gnu.so
drwxr-xr-x. 2 ptisnovs ptisnovs      80 Aug  5 18:42 __pycache__
-rw-r--r--. 1 ptisnovs ptisnovs      20 Aug  5 18:42 version.py

Poznámka: v rámci instalace balíčku lupa jsme tedy vlastně získali i implementaci několika interpretrů jazyka Lua (liší se svou verzí) a taktéž i JIT překladač jazyka Lua (LuaJIT). To je důležité vědět v případě, že nějaké skripty psané v jazyce Lua vyžadují určitou verzi interpretru (což je však dosti málo pravděpodobné).

V posledním kroku si ověříme, že přímo v interpretru Pythonu můžeme provést import balíčku lupa. Interpret Pythonu se spustí následujícím způsobem (přes PDM nebo uv):

$ uv run python
 
Python 3.13.9 (main, Oct 14 2025, 00:00:00) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.

Otestujeme import:

>>> import lupa
>>> help(lupa)
 
 
Help on package lupa:
 
NAME
    lupa
 
PACKAGE CONTENTS
    lua51
    lua52
    lua53
    lua54
    lua55
    luajit20
    luajit21
    version
 
SUBMODULES
    _newest_lib
 
FUNCTIONS
    __getattr__(name)
        Get a name from the latest available Lua (or LuaJIT) module.
        Imports the module as needed.
 
DATA
    __all__ = ['LUA_VERSION', 'LUA_MAXINTEGER', 'LUA_MININTEGER', 'LuaRunt...
 
VERSION
    2.8

Poznámka: vše se tedy zdá být v naprostém pořádku.

Konstrukce a inicializace interpretru jazyka Lua

Balíček lua-python umožňuje, aby aplikace psané v Pythonu vytvářely nové interpretry pro jazyk Lua, vyhodnocovaly výrazy či celé funkce psané v jazyku Lua a taktéž (pochopitelně) podporuje předávání hodnot mezi oběma programovacími jazyky. To ovšem není tak jednoduché, jak by se mohlo zdát, protože podporovaná sada datových typů je v obou jazycích odlišná a taktéž do hry vstupují správci paměti (garbage collectors) na obou stranách, tedy jak v Pythonu, tak i v interpretru jazyka Lua.

Nový interpret jazyka Lua je vytvořen konstrukcí instance třídy LuaRuntime. Vytvořit lze libovolné množství interpretrů; ty jsou od sebe izolovány, takže se vlastně jedná o formu sandboxingu (což je dnes ještě důležitější, než kdy jindy). Konstruktoru lze předat různé nepovinné parametry, z nichž v praxi nejdůležitější je parametr max_memory. To znamená, že nejenom že dokážeme vytvořit interpretry odizolované od svého okolí, ale navíc je možné omezit jejich spotřebu paměti (ovšem ne již strojového času – nekonečná smyčka psaná v Lue nebude automaticky zastavena):

class LuaRuntime(builtins.object)
 |  The main entry point to the Lua runtime.
 |
 |  Available options:
 |
 |  * ``encoding``: the string encoding, defaulting to UTF-8.  If set
 |    to ``None``, all string values will be returned as byte strings.
 |    Otherwise, they will be decoded to unicode strings on the way
 |    from Lua to Python and unicode strings will be encoded on the
 |    way to Lua.  Note that ``str()`` calls on Lua objects will
 |    always return a unicode object.
 |
 |  * ``source_encoding``: the encoding used for Lua code, defaulting to
 |    the string encoding or UTF-8 if the string encoding is ``None``.
 |
 |  * ``attribute_filter``: filter function for attribute access
 |    (get/set).  Must have the signature ``func(obj, attr_name,
 |    is_setting)``, where ``is_setting`` is True when the attribute
 |    is being set.  If provided, the function will be called for all
 |    Python object attributes that are being accessed from Lua code.
 |    Normally, it should return an attribute name that will then be
 |    used for the lookup.  If it wants to prevent access, it should
 |    raise an ``AttributeError``.  Note that Lua does not guarantee
 |    that the names will be strings.  (New in Lupa 0.20)
 |
 |  * ``attribute_handlers``: like ``attribute_filter`` above, but
 |    handles the getting/setting itself rather than giving hints
 |    to the LuaRuntime.  This must be a 2-tuple, ``(getter, setter)``
 |    where ``getter`` has the signature ``func(obj, attr_name)``
 |    and either returns the value for ``obj.attr_name`` or raises an
 |    ``AttributeError``  The function ``setter`` has the signature
 |    ``func(obj, attr_name, value)`` and may raise an ``AttributeError``.
 |    The return value of the setter is unused.  (New in Lupa 1.0)
 |
 |  * ``register_eval``: should Python's ``eval()`` function be available
 |    to Lua code as ``python.eval()``?  Note that this does not remove it
 |    from the builtins.  Use an ``attribute_filter`` function for that.
 |    (default: True)
 |
 |  * ``register_builtins``: should Python's builtins be available to Lua
 |    code as ``python.builtins.*``?  Note that this does not prevent access
 |    to the globals available as special Python function attributes, for
 |    example.  Use an ``attribute_filter`` function for that.
 |    (default: True, new in Lupa 1.2)
 |
 |  * ``unpack_returned_tuples``: should Python tuples be unpacked in Lua?
 |    If ``py_fun()`` returns ``(1, 2, 3)``, then does ``a, b, c = py_fun()``
 |    give ``a == 1 and b == 2 and c == 3`` or does it give
 |    ``a == (1,2,3), b == nil, c == nil``?  ``unpack_returned_tuples=True``
 |    gives the former.
 |    (default: False, new in Lupa 0.21)
 |
 |  * ``overflow_handler``: function for handling Python integers overflowing
 |    Lua integers. Must have the signature ``func(obj)``. If provided, the
 |    function will be called when a Python integer (possibly of arbitrary
 |    precision type) is too large to fit in a fixed-precision Lua integer.
 |    Normally, it should return the now well-behaved object that can be
 |    converted/wrapped to a Lua type. If the object cannot be precisely
 |    represented in Lua, it should raise an ``OverflowError``.
 |
 |  * ``max_memory``: max memory usage this LuaRuntime can use in bytes.
 |    If max_memory is None, the default lua allocator is used and calls to
 |    ``set_max_memory(limit)`` will fail with a ``LuaMemoryError``.
 |    Note: Not supported on 64bit LuaJIT.
 |    (default: None, i.e. no limitation. New in Lupa 2.0)
 |
 |  * ``string_hash_seed``: hash seed for Lua strings.
 |    (default: randomly initialised. Ignored in Lua < 5.5.)

Inicializace nového intepretru jazyka Lua je vlastně triviální:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
print(lua)
Vyhodnocení Lua výrazu popř. spuštění Lua skriptu z Pythonu

V okamžiku, kdy je inicializován nový interpret jazyka Lua, začnou být k dispozici dvě základní operace, které dokáže interpret na žádost provést. Jednodušší z těchto operací se jmenuje eval a slouží k vyhodnocení výrazu zapsaného v jazyce Lua s předáním návratové hodnoty zpět do Pythonu. Výraz předávaný interpretru je uložen v řetězci. Povšimněte si, že je možné předat jak výraz ve zdrojové podobě, tak i bajtkód (přeložený pomocí luac):

eval(lua_code, *args, name=None, mode=None) method of lupa.lua55.LuaRuntime instance
    LuaRuntime.eval(self, lua_code, *args, name=None, mode=None)
 
    Evaluate a Lua expression passed in a string.
 
    The 'name' argument can be used to override the name printed in error messages.
 
    The 'mode' argument specifies the input type.  By default, both source code and
    pre-compiled byte code is allowed (mode='bt').  It can be restricted to source
    code with mode='t' and to byte code with mode='b'.  This has no effect on Lua 5.1.

Složitější skripty psané v jazyce Lua (tedy vlastně definice globálních proměnných a funkcí, sekvence příkazů atd.) se spouští metodou execute a nikoli eval. Parametry této metody jsou prakticky stejné, jako v případě eval:

execute(lua_code, *args, name=None, mode=None) method of lupa.lua55.LuaRuntime instance
    LuaRuntime.execute(self, lua_code, *args, name=None, mode=None)
 
    Execute a Lua program passed in a string.
 
    The 'name' argument can be used to override the name printed in error messages.
 
    The 'mode' argument specifies the input type.  By default, both source code and
    pre-compiled byte code is allowed (mode='bt').  It can be restricted to source
    code with mode='t' and to byte code with mode='b'.  This has no effect on Lua 5.1.

Poznámka: proč tedy vlastně existuje eval, když je execute „mocnější“? Je tomu právě z tohoto důvodu – díky tomu, že eval vyhodnocuje jen výrazy, nelze omylem vyhodnotit nekonečné smyčky, složité rekurzivní algoritmy atd. (zdaleka ne vše, co lze v jazyku Lua zapsat, je totiž výrazem).

Vyhodnocení výrazu zapsaného v jazyce Lua s předáním výsledku do Pythonu

Praktickou část dnešního článku začneme velmi jednoduchým demonstračním příkladem. Ten je zapsán v Pythonu a obsahuje v řetězci uložený výraz 6 * 7. Tento výraz necháme vyhodnotit (vypočítat) metodou LuaRuntime.eval s tím, že výsledek bude předán zpět Pythonu a bude vypsán na standardní výstup:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
LUA_SCRIPT = "6 * 7"
 
print(lua.eval(LUA_SCRIPT))

Otestování tohoto příkladu je snadné:

$ uv run simple_eval.py
 
42

Můžeme si otestovat i výraz, který spojí dva řetězce. V jazyce Lua se operátor spojení řetězců zapisuje dvěma tečkami:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
LUA_SCRIPT = """
'foo' .. 'bar'
"""
 
print(LUA_SCRIPT)
print(lua.eval(LUA_SCRIPT))

Po spuštění se nejdříve vypíše zdrojový kód výrazu a posléze i výsledek jeho vyhodnocení:

'foo' .. 'bar'
 
foobar
Spuštění celého Lua skriptu, zavolání Lua funkce z Pythonu

Vyhodnocení jednoduchého výrazu zapsaného v programovacím jazyce Lua je sice snadné, ovšem v praxi budeme vyžadovat poněkud sofistikovanější formu komunikace mezi Pythonem na straně jedné a jazykem Lua na straně druhé. Typicky je požadováno, aby bylo možné z Pythonu volat nějakou funkci definovanou v jazyce Lua. To se provádí následovně:

  1. Celý skript zapsaný v jazyce Lua je načten (z řetězce nebo ze souboru, socketu atd.)
  2. Následně je metodou LuaRuntime.execute() načtený skript spuštěn
  3. I kdyby skript obsahoval pouze definici funkcí, jsou tyto funkce uloženy do tabulky globals
  4. Na straně Pythonu můžeme k této tabulce přistupovat metodou LuaRuntime.globals() a přečíst z ní reference na funkci/funkce
  5. Tyto funkce lze přímo z Pythonu zavolat a popřípadě jim předat parametry

Přístup k funkcím definovaným v jazyce Lua (nebo i ke globálním proměnným) je realizován přes metodu, která vrací tabulku (viz další text):

globals() method of lupa.lua55.LuaRuntime instance
    LuaRuntime.globals(self)
 
    Return the globals defined in this Lua runtime as a Lua
    table.

Otestování je snadné. V následujícím skriptu napsaném v Pythonu je nejdříve vyhodnocen (spuštěn) řetězec obsahující definici funkce greet. Následně se reference na tuto funkci přečte z tabulky globals a funkce je zavolána:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
LUA_SCRIPT = """
function greet()
    return "Hello from Lua!"
end
"""
 
lua.execute(LUA_SCRIPT)
greet = lua.globals().greet
 
print(greet())

Chování programu po jeho spuštění ukazuje, že vše pracuje podle předpokladů:

$ uv run lua_function_1.py
 
Hello from Lua!
Zavolání Lua funkce z Pythonu s předáním parametru či parametrů

Ukažme si ještě, jakým způsobem se funkci definované v programovacím jazyce Lua předává parametr. Funkce greet bude očekávat parametr typu řetězec a řetězec skutečně této funkci předáme (z Pythonu). Veškeré konverze jsou provedeny interně:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
LUA_SCRIPT = """
function greet(name)
    return "Hello from " .. name .. "!"
end
"""
 
lua.execute(LUA_SCRIPT)
greet = lua.globals().greet
 
print(greet("Lua"))

Otestování tohoto jednoduchého prográmku je snadné a ukazuje, že řetězec byl skutečně předán z Pythonu do interpretru jazyka Lua:

$ uv run lua_function_2.py
 
Hello from Lua!
Dvě varianty načtení Lua skriptu ze samostatného souboru

Zápis skriptů s definicemi funkcí v jazyce Lua formou řetězců ukládaných do programů v Pythonu je pravděpodobně ten nejhorší možný způsob, jak propojit dva programovací jazyky (snad ještě horší je generování přes šablony). V praxi je výhodnější skripty zapsané v jazyce Lua načítat ze samostatných souborů. Ten nejjednodušší způsob realizace je ukázán v dalším příkladu.

Definice funkce uložená do souboru greet.lua:

function greet()
    return "Hello from Lua!"
end

Načtení definice funkce v Pythonu s jejím zavoláním:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("greet.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
greet = lua.globals().greet
 
print(greet())

Otestování funkcionality:

$ uv run lua_from_file_1.py
 
Hello from Lua!

Podobný příklad, ovšem s Lua funkcí akceptující parametry:

function greet(name)
    return "Hello from " .. name .. "!"
end

Zavolání této funkce z Pythonu s předáním hodnoty:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("greet_name.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
greet = lua.globals().greet
 
print(greet("Lua"))

Výsledek:

$ uv run lua_from_file_2.py
 
Hello from Lua!

Ovšem i samotný skript napsaný v jazyce Lua dokáže načíst (a vlastně i vyhodnotit/spustit) jiný skript. Pro tento účel se používá Lua funkce dofile (což se vzdáleně podobá kombinaci funkce eval a příkazu import v Pythonu):

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
LUA_SCRIPT = """
dofile("greet_name.lua")
"""
 
lua.execute(LUA_SCRIPT)
greet = lua.globals().greet
 
print(greet("Lua"))

Opět si otestujeme funkcionalitu tohoto řešení:

$ uv run lua_from_file_3.py
 
Hello from Lua!
Typový systém programovacího jazyka Lua

V programovacím jazyku Lua je možné využívat osm základních datových typů vypsaných v tabulce níže. V úvodním odstavci jsme si řekli, že Lua patří mezi dynamicky typované jazyky, tj. typy nejsou přiřazené proměnným, ale jejich hodnotám. To mj. také znamená, že se při vytváření proměnných neuvádí jejich typ, protože překladač použitý datový typ rozpozná přímo z toho, jakým způsobem je hodnota proměnné zapsána (pokud je místo konstantní hodnoty použit výraz či volání funkce, vyhodnocuje se typ výsledku výrazu či výsledku volání funkce).

Samotný název datového typu (první sloupec tabulky) tedy není rezervovaným ani klíčovým slovem, naopak některé hodnoty určitých datových typů (především nil, true, false) klíčovými hodnotami jsou. Určitou výjimkou je první datový typ, který je pojmenovaný stejně jako (jediná a v celém programu jedinečná) hodnota tohoto typu: nil.

Datový typ Význam
nil datový typ hodnoty nil, která je v celém programu jedinečná; tato hodnota se definitoricky odlišuje od všech ostatních hodnot (nutná podmínka pro správné porovnávání); některými vlastnostmi se podobá céčkovému NULL či Javovskému null
boolean datový typ pravdivostních hodnot true a false
number reálné číslo podle IEEE 754 s dvojnásobnou přesností (v céčku či Javě se jedná o typ double); při překladu jazyka je však možné tento typ změnit, například na celá čísla
string řetězec
function funkce, buď vytvořená přímo ve skriptu či zaregistrovaná céčková funkce; s funkcemi lze manipulovat podobně jako s dalšími datovými typy, včetně jejich přiřazení do proměnných, uvedení funkce jako parametru jiné funkce atd.
userdata data vytvořená v hostitelském (céčkovém) programu; v samotném skriptu je jejich použití omezeno (interpretr nemá dostatek informací o struktuře skutečně uložených dat)
thread vlákno použité pro implementaci koprogramů (coroutine)
table asociativní pole – velmi flexibilní datový typ, na kterém je založena většina funkcionality jazyka Lua, včetně objektového systému (v asociativním poli mohou být umístěny mj. i funkce)
Předávání hodnot nil, true a false

V této kapitole i v kapitolách navazujících si ukážeme, jakým způsobem se předávají hodnoty různých typů mezi Pythonem na jedné straně a skriptem naprogramovaných v jazyce Lua na straně druhé. Začneme hodnotami nil, true a false (strana jazyka Lua), které se nějakým způsobem musí převádět na hodnoty použitelné v Pythonu. V následujícím skriptu (Lua) je definována trojice funkcí get_nil, get_true a get_false, které tyto tři hodnoty vrací:

function get_nil()
    return nil
end
 
function get_true()
    return true
end
 
function get_false()
    return false
end

V Pythonu po inicializaci interpretru Lua a načtení skriptu získáme reference na všechny tři výše definované funkce, které jsou ihned poté zavolány. Návratové hodnoty jsou vypsány, a to včetně svých typů:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("types.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
 
get_nil = lua.globals().get_nil
get_true = lua.globals().get_true
get_false = lua.globals().get_false
 
n = get_nil()
print(type(n), n)
 
t = get_true()
print(type(t), t)
 
f = get_false()
print(type(f), f)

Výsledky, které po spuštění tohoto programu získáme, ukazují, jakým způsobem jsou hodnoty nil, true a false převedeny na Pythonovské hodnoty None, True a False (což přesně odpovídá očekávání):

$ uv run lua_types.py
 
<class 'NoneType'> None
<class 'bool'> True
<class 'bool'> False
Předávání numerických hodnot

V programovacím jazyce Lua se pro reprezentaci numerických hodnot používá formát čísel s plovoucí řádovou čárkou double (což lze ovšem změnit při překladu interpretru; to je ale téma na jiný článek). To znamená, že přenos těchto hodnot mezi oběma jazyky je možný, i když není podporován Pythonovský typ long. To by v praxi však nemuselo vadit. Opět si nejprve ukažme skript naprogramovaný v jazyce Lua, který obsahuje definici dvou funkcí gen_int a gen_double vracejících celočíselnou hodnotu resp. číslo s plovoucí řádovou čárkou:

counter = 0
 
function reset_counter()
    counter = 0
end
 
function gen_int()
    counter = counter + 1
    return counter
end
 
function gen_double()
    counter = counter + 1
    return 1.0/counter
end

Tyto funkce budeme opět volat z Pythonu a vypíšeme jak typ vrácené hodnoty, tak i samotnou hodnotu:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("numbers.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
 
reset_counter = lua.globals().reset_counter
gen_int = lua.globals().gen_int
gen_double = lua.globals().gen_double
 
reset_counter()
for _ in range(10):
    i = gen_int()
    print(type(i), i)
 
print()
 
reset_counter()
for _ in range(10):
    f = gen_double()
    print(type(f), f)

Povšimněte si, že v tomto případě byly provedeny automatické konverze na typ int nebo float (to jsou názvy typů Pythonu):

$ uv run lua_number.py
 
<class 'int'> 1
<class 'int'> 2
<class 'int'> 3
<class 'int'> 4
<class 'int'> 5
<class 'int'> 6
<class 'int'> 7
<class 'int'> 8
<class 'int'> 9
<class 'int'> 10
 
<class 'float'> 1.0
<class 'float'> 0.5
<class 'float'> 0.3333333333333333
<class 'float'> 0.25
<class 'float'> 0.2
<class 'float'> 0.16666666666666666
<class 'float'> 0.14285714285714285
<class 'float'> 0.125
<class 'float'> 0.1111111111111111
<class 'float'> 0.1

Poznámka: v praxi jsme v případě celočíselných hodnot omezeni na rozsah 52 bitů (ostatně podobně jako v JavaScriptu atd.).

Předávání řetězců

Relativně přímočaré je i předávání řetězců mezi oběma programovacími jazyky. Ukažme si skript napsaný v jazyce Lua, který umožňuje oboustranné předávání řetězců (jedna funkce řetězec akceptuje jako parametr, druhá ho vrací):

string = ""
filler = "."
 
function set_filler(s)
    filler = s
end
 
function gen_string()
    string = string .. filler
    return string
end

Obě výše uvedené funkce voláme z Pythonu. Pro zajímavost navíc předávám i řetězce obsahující znaky z Unicode, protože i tuto vlastnost je dobré ověřit:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("strings.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
 
set_filler = lua.globals().set_filler("*")
gen_string = lua.globals().gen_string
 
for _ in range(10):
    s = gen_string()
    print(type(s), s)
 
set_filler = lua.globals().set_filler("ΔěščΔ")
 
for _ in range(10):
    s = gen_string()
    print(type(s), s)

Z výsledků je patrné, že se pracuje korektně (na obou stranách) i s Unicode:

$ uv run lua_string.py
 
<class 'str'> *
<class 'str'> **
<class 'str'> ***
<class 'str'> ****
<class 'str'> *****
<class 'str'> ******
<class 'str'> *******
<class 'str'> ********
<class 'str'> *********
<class 'str'> **********
<class 'str'> **********ΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔ
<class 'str'> **********ΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔΔěščΔ
Předání seznamu z Pythonu do skriptu psaného v jazyce Lua

V deváté kapitole byly uvedeny všechny datové typy, se kterými se pracuje v programovacím jazyku Lua. Mezi tyto typy patří i tabulka (table), která se používá buď ve funkci rozšiřitelného pole nebo slovníku (popř. se může jednat i o kombinaci obou datových struktur). Při komunikaci mezi Pythonem a jazykem Lua tedy mohou nastat potřeby těchto konverzí:

Python Lua Směr konverze
seznam tabulka Python → Lua
slovník tabulka Python → Lua
seznam tabulka Python ← Lua
slovník tabulka Python ← Lua

Ukažme si nejprve první typ konverze, tedy situaci, v níž je nutné do funkce naprogramované v jazyku Lua předat obsah pythonovského seznamu. Příkladem může být funkce, která vypočte součet všech prvků seznamu (předpokládáme, že seznam obsahuje numerické hodnoty):

function sum(t)
    local s = 0
    for i = 1, #t do
        s = s + t[i]
    end
    return s
end

Seznam je v Pythonu realizován běžným způsobem:

l = [1, 2, 3, 4]

Pro převod seznamu na tabulku lze využít metodu nazvanou table_from, která je definována pro inicializovaný interpret jazyka Lua:

table_from(*args, recursive=False) method of lupa.lua55.LuaRuntime instance
    LuaRuntime.table_from(self, *args, bool recursive=False)
 
    Create a new table from Python mapping or iterable.
 
    table_from() accepts either a dict/mapping or an iterable with items.
    Items from dicts are set as key-value pairs; items from iterables
    are placed in the table in order.
 
    Nested mappings / iterables are passed to Lua as userdata
    (wrapped Python objects) by default.  If `recursive` is True,
    they are converted to Lua tables recursively, handling loops
    and duplicates via identity de-duplication.

V praxi je to snadné:

t = lua.table_from(l)

Následně můžeme tabulku předat do funkce definované v Lua skriptu:

sum = lua.globals().sum
print(sum(t))

Celý skript, který tuto konverzi i volání Lua funkce provede, vypadá následovně:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("table1.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
sum = lua.globals().sum
 
l = [1, 2, 3, 4]
t = lua.table_from(l)
 
print(sum(t))

Z výsledku je patrné, že se seznam vytvořil, následně byl zkonvertován do formy tabulky a posléze se předal funkci definovanou na straně Lua interpretru:

$ uv run lua_table_1.py
 
10
Tabulky obsahující „seznamovou“ část

V dalším demonstračním příkladu si ukážeme opačný způsob převodu, tedy konkrétně převodu tabulky obsahující „seznamovou část“ do podoby klasického seznamu na straně programovacího jazyka Python. Skript napsaný v jazyce Lua obsahuje jedinou funkci nazvanou gen_table, která skutečně vrací tabulku se třemi prvky (ty mají indexy 1, 2 a 3 – viz poznámku na konci kapitoly):

function gen_table()
    return {"foo", "bar", "baz"}
end

Pythonovská část příkladu se snaží o explicitní převod jednotlivých prvků tabulky. Nejedná se o příliš idiomatický zápis, ovšem ukazuje to, že na straně Pythonu jsou prvky indexovány od nuly a nikoli od jedničky:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("table2.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
gen_table = lua.globals().gen_table
 
t = gen_table()
l = [t[i] for i in range(1, len(t) + 1)]
 
print(type(l))
print(l)

Ověřme si, že výsledkem bude skutečně seznam:

$ uv run lua_table_2.py 
 
<class 'list'>
['foo', 'bar', 'baz']

Ovšem můžeme použít i kratší a vlastně i idiomatičtější zápis, který získá hodnoty z tabulky metodou values a následně provede explicitní převod na seznam:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("table3.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
gen_table = lua.globals().gen_table
 
t = gen_table()
l = list(t.values())
 
print(type(l))
print(l)

Výsledný seznam je naprosto totožný s výsledkem předchozím:

$ uv run lua_table_3.py
<class 'list'>
['foo', 'bar', 'baz']

Poznámka: v jazyku Lua se prvky polí implicitně indexují od jedničky a nikoli od nuly. V dnešním IT světě, který měl to štěstí (a současně i hodně velkou smůlu), že všechny mainstreamové jazyky jsou odvozeny od céčka, může indexování od nuly vypadat přinejmenším podivně, ovšem i mnoho dalších programovacích jazyků ve skutečnosti používá podobný koncept: Algol-60, Algol-68, COBOL, Fortran (lze specifikovat i jinak), R, Julia, Mathematica, MATLAB či APL (tedy většinou jazyky, které vznikly před céčkem nebo jazyky orientované na matematiku). Viz například Zero-based indexing considered harmful.

Tabulky obsahující „slovníkovou“ část

V předchozím textu jsme si řekli, že se mezi podporovanými datovými typy programovacího jazyka Lua nachází (jako nepovinná součást tabulky) i asociativní pole, někdy také nazývané hashmapa nebo slovník. Jedná se o datovou strukturu, v níž jsou uloženy dvojice klíč–hodnota, přičemž klíčem může být hodnota libovolného typu kromě typu nil. Hodnota může být skutečně libovolná, což ovšem znamená, že se může jednat i o další pole, funkci atd. V případě, že za klíče jsou zvolena přirozená čísla, jsou asociativní pole ze sémantického hlediska rovnocenná klasicky chápaným indexovaným polím – tímto způsobem je možné do jisté míry zajistit indexování prvků polí od nuly (ovšem nebude možné použít operátor pro zjištění délky pole). Tento datový typ tak představuje základ jak pro tvorbu indexovaných polí, tak i seznamů, zásobníků, front, stromů, struktur typu záznam (jako klíče jsou v tomto případě použity řetězce) a dokonce i objektů (jako hodnoty uložené v asociativním poli lze totiž použít i funkce, které tak vystupují v roli metod).

Ukažme si nejdříve způsob čtení prvků z asociativního pole. Zápis selektoru je stejný, jako v mnoha jiných programovacích jazycích (t je předaná tabulka):

function full_name(t)
    local name = t["name"]
    local surname = t["surname"]
    return name .. " " .. surname
end

Pokud jsou ovšem jako klíče zvoleny řetězce, je možné využít i alternativní způsob zápisu, který připomíná přístup k atributům objektu:

function full_name(t)
    local name = t.name
    local surname = t.surname
    return name .. " " .. surname
end

Na straně Pythonu nejdříve vytvoříme běžný slovník (dictionary) s prvky, jejichž klíče jsou řetězce. Poté je možné tento slovník přímo předat funkci full_name (možno použít obě verze):

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("table4.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
full_name = lua.globals().full_name
 
d = {"id": 42, "name": "John", "surname": "Doe", "address": None}
n = full_name(d)
 
print(n)

Otestování funkcionality pro obě varianty funkce full_name je snadné:

$ uv run lua_table_4.py
 
John Doe

a pro druhou variantu:

$ uv run lua_table_5.py
 
John Doe
Konverze tabulky z jazyka Lua do slovníku v Pythonu

Ještě nám zbývá poslední varianta konverze tabulek na objekt vhodného typu v Pythonu. Konkrétně se jedná o konverzi tabulky vrácené z nějaké Lua funkce na slovník použitelný na straně Pythonu:

Python Lua Směr konverze
seznam tabulka Python → Lua
slovník tabulka Python → Lua
seznam tabulka Python ← Lua
slovník tabulka Python ← Lua

Tabulku obsahující asociativní pole („slovníkovou část“) zkonstruujeme v jazyku Lua následujícím způsobem:

function gen_table()
    return {["foo"]=1; ["bar"]=2; ["baz"]=3}
end

Konverze na slovník na straně Pythonu je triviální:

t = gen_table()
d = dict(t)

Chování (i to, že skutečně získáme objekt požadovaného typu) si snadno ověříme spuštěním následujícího programu:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("table6.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
gen_table = lua.globals().gen_table
 
t = gen_table()
d = dict(t)
 
print(type(d))
print(d)

Výsledky ukazují, že skutečně dostaneme běžný slovník:

$ uv run lua_table_6.py
 
<class 'dict'>
{'bar': 2, 'foo': 1, 'baz': 3}
Stav Lua skriptů včetně globálních proměnných v případě využití většího množství interpretrů

V teoretické části dnešního článku jsme si řekli, že balíček lua-python podporuje inicializaci většího množství interpretrů jazyka Lua, které jsou od sebe izolovány. To ovšem znamená, že když budeme ze dvou interpretrů přistupovat například ke globální proměnné definované v Lua skriptu, bude každý interpret využívat vlastní instanci takové proměnné. Toto chování je možné relativně snadno otestovat. Využívat budeme Lua skript, který obsahuje definici globální proměnné x a dále dvojici funkcí typu „setter“ a „getter“, které ke globální proměnné přistupují (buď ji modifikují, nebo čtou její aktuální hodnotu):

x = 42
 
function set_x(new_value)
    x = new_value
end
 
function get_x()
    return x
end

V prvním programu si pouze ověříme, že globální proměnnou x je skutečně možné modifikovat přes funkci set_x a číst její obsah funkcí get_x:

from lupa import LuaRuntime
 
lua = LuaRuntime()
 
with open("global_vars.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua.execute(lua_script)
 
set_x = lua.globals().set_x
get_x = lua.globals().get_x
 
print("orig:", get_x())
set_x(-1)
print("new:", get_x())

Výsledky vypsané tímto programem by neměly být překvapující:

$ uv run lua_vars_1.py 
 
orig: 42
new: -1

Druhý program je již sofistikovanější, protože v něm inicializujeme dva interpretry jazyka Lua:

lua1 = LuaRuntime()
lua2 = LuaRuntime()

Každému interpretru je předán stejný Lua skript s globální proměnnou; každý z interpretrů si na základě jeho obsahu vytvoří vlastní tabulku globals:

lua1.execute(lua_script)
lua2.execute(lua_script)

Jednotlivé funkce, na které získáme reference, tedy přistupují ke dvěma rozdílným globálním proměnným x:

set_x_1 = lua1.globals().set_x
get_x_1 = lua1.globals().get_x
set_x_2 = lua2.globals().set_x
get_x_2 = lua2.globals().get_x

Což si vyzkoušíme:

print("orig x1:", get_x_1())
print("orig x2:", get_x_2())
set_x_1(6502)
print("new x1:", get_x_1())
print("new x2:", get_x_2())
set_x_2(8080)
print("new x1:", get_x_1())
print("new x2:", get_x_2())

S výsledkem:

$ uv run lua_vars_2.py 
 
orig x1: 42
orig x2: 42
new x1: 6502
new x2: 42
new x1: 6502
new x2: 8080

Pro úplnost si uveďme celý zdrojový kód dnešního posledního demonstračního příkladu:

Školení Zabbix

from lupa import LuaRuntime
 
lua1 = LuaRuntime()
lua2 = LuaRuntime()
 
with open("global_vars.lua", "r", encoding="utf-8") as fin:
    lua_script = fin.read()
 
lua1.execute(lua_script)
lua2.execute(lua_script)
 
set_x_1 = lua1.globals().set_x
get_x_1 = lua1.globals().get_x
set_x_2 = lua2.globals().set_x
get_x_2 = lua2.globals().get_x
 
print("orig x1:", get_x_1())
print("orig x2:", get_x_2())
set_x_1(6502)
print("new x1:", get_x_1())
print("new x2:", get_x_2())
set_x_2(8080)
print("new x1:", get_x_1())
print("new x2:", get_x_2())
Články a seriály o jazyku Lua a souvisejících technologiích na Rootu

S programovacím jazykem Lua jsme se již na stránkách Roota mnohokrát setkali; především ve dvojici seriálů a v několika dalších článcích, které vyšly mimo oba seriály:

  1. Seriál Programovací jazyk Lua
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  2. Seriál Torch: framework pro strojové učení
    https://www.root.cz/serialy/torch-framework-pro-strojove-uceni/
  3. Skriptovací jazyk Lua v aplikacích naprogramovaných v Go
    https://www.root.cz/clanky/skriptovaci-jazyk-lua-v-aplikacich-naprogramovanych-v-go/
  4. LuaJIT – Just in Time překladač pro programovací jazyk Lua
    https://www.root.cz/clanky/luajit-just-in-time-prekladac-pro-programovaci-jazyk-lua/
  5. Použití nástroje RQ (Redis Queue) pro správu úloh zpracovávaných na pozadí
    https://www.root.cz/clanky/pouziti-nastroje-rq-redis-queue-pro-spravu-uloh-zpracovavanych-na-pozadi/
  6. Proudy (streams) podporované systémem Redis
    https://www.root.cz/clanky/proudy-streams-podporovane-systemem-redis/
  7. Proudy (streams) podporované systémem Redis (dokončení)
    https://www.root.cz/clanky/proudy-streams-podporovane-systemem-redis-dokonceni/
  8. Databáze Redis (nejenom) pro vývojáře používající Python
    https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python/
  9. Databáze Redis (nejenom) pro vývojáře používající Python (dokončení)
    https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python-dokonceni/
  10. Použití databáze Redis v aplikacích naprogramovaných v Go
    https://www.root.cz/clanky/pouziti-databaze-redis-v-aplikacich-naprogramovanych-v-go/
  11. Použití databáze Redis v aplikacích naprogramovaných v Go (2)
    https://www.root.cz/clanky/pouziti-databaze-redis-v-aplikacich-naprogramovanych-v-go-2/
  12. Lua: univerzální distribuce programovacího jazyka Lua
    https://www.root.cz/clanky/ulua-univerzalni-distribuce-programovaciho-jazyka-lua/
Repositář s demonstračními příklady

Demonstrační příklady, s nimiž jsme se v dnešním článku seznámili a které jsou určeny pro Python 3.11 (a libovolnou vyšší verzi Pythonu) a knihovnu lupa, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy:

Odkazy na Internetu
  1. lupa na GitHubu
    https://github.com/scoder/lupa
  2. lupa na PyPi
    https://pypi.org/project/lupa/
  3. Lunatic Python
    https://labix.org/lunatic-python
  4. Seriál Programovací jazyk Lua
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  5. Python na Rootu
    https://www.root.cz/n/python/
  6. Lua na Wikipedii
    https://en.wikipedia.org/wiki/Lua
  7. Python (programming language) na Wikipedii
    https://en.wikipedia.org/wi­ki/Python_(programming_lan­guage)
  8. Languages that compile to Lua
    https://github.com/hengestone/lua-languages?tab=readme-ov-file#languages-that-compile-to-lua
  9. Repositář projektu Lua Fun
    https://github.com/luafun/luafun
  10. The Lua VM, on the Web
    https://kripken.github.io/lu­a.vm.js/lua.vm.js.html
  11. Lua.vm.js REPL
    https://kripken.github.io/lu­a.vm.js/repl.html
  12. lua2js
    https://www.npmjs.com/package/lua2js
  13. lua2js na GitHubu
    https://github.com/basicer/lua2js-dist
  14. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  15. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  16. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  17. ULua: Universal Lua Distribution
    https://ulua.io/index.html
  18. deque
    https://github.com/catwell/cw-lua/blob/master/deque/deque.test.lua
  19. Repositář projektu Lua Fun
    https://github.com/luafun/luafun
  20. Lua Functional 0.1.3 documentation
    https://luafun.github.io/re­ference.html
  21. Lua Functional: Getting Started
    https://luafun.github.io/get­ting_started.html
  22. LuaRocks
    https://luarocks.org/
  23. Documentation
    Dokumentace ke správci balíčků LuaRocks
  24. Rockspec knihovny Fun
    https://raw.githubusercon­tent.com/luafun/luafun/mas­ter/fun-scm-1.rockspec
  25. Awesome Lua – A curated list of quality Lua packages and resources.
    https://github.com/LewisJEllis/awesome-lua
  26. LuaJIT
    https://luajit.org/
  27. Running LuaJIT
    https://luajit.org/running.html
  28. LuaJIT na GitHubu
    https://github.com/luajit
  29. Repositář projektu Moses
    https://github.com/Yonaba/Moses/
  30. Návod k použití knihovny Moses
    https://github.com/Yonaba/Mo­ses/blob/master/doc/tutori­al.md
  31. Lambda the Ultimate: Coroutines in Lua
    http://lambda-the-ultimate.org/node/438
  32. Coroutines Tutorial
    http://lua-users.org/wiki/CoroutinesTutorial
  33. Lua Coroutines Versus Python Generators
    http://lua-users.org/wiki/LuaCorouti­nesVersusPythonGenerators
  34. Programming in Lua 9.1 – Coroutine Basics
    http://www.lua.org/pil/9.1.html
  35. How to understand clojure's lazy-seq
    https://stackoverflow.com/qu­estions/44095400/how-to-understand-clojures-lazy-seq
  36. Lua Implementations
    http://lua-users.org/wiki/LuaImplementations
  37. Generator (computer programming)
    https://en.wikipedia.org/wi­ki/Generator_(computer_pro­gramming)
  38. Lua 5.2 Reference Manual: 6.10 – The Debug Library
    http://www.lua.org/manual/5­.2/manual.html#6.10
  39. Issues k projektu LuaFun
    https://github.com/luafun/lu­afun/issues
  40. Archived | Embed Lua for scriptable apps
    https://developer.ibm.com/tutorials/l-embed-lua/
  41. Embedding Lua
    https://www.oreilly.com/li­brary/view/lua-quick-start/9781789343229/3a6f3daf-f74c-4a25-a125–584da58568e4.xhtml
  42. PDM: moderní správce balíčků a virtuálních prostředí Pythonu
    https://www.root.cz/clanky/pdm-moderni-spravce-balicku-a-virtualnich-prostredi-pythonu/
  43. Which Programming Languages Index by One?
    https://therenegadecoder.com/co­de/which-programming-languages-index-by-one/
  44. 2Zero-based indexing considered harmful
    https://craftofcoding.wor­dpress.com/2021/03/26/zero-based-indexing-considered-harmful/

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Softwarová sklizeň (12. 8. 2026): tvorba živé hudby programováním016.8211-08-2026
2Klíčová slova „auto“ a „typeof“ v C2306.8503-08-2026
3Создание энергоустановки на Луне — смелый и нужный проект. Он укрепит позиции ...7621-07-2026
4🌌🔧 Лунная АЭС — это не просто проект, это возможность ...8721-07-2026
5Космос из школьного кабинета: Как мы научили ИИ законам Кеплера после «разноса» от ученых06.0730-03-2026
6Создание атомной энергоустановки на Луне — это важный этап в ...7821-07-2026
7Когда речь идёт о ядерных технологиях в космосе, часто возникают ...2621-07-2026
8Überschreiben von Dateien in antlr4-project (Fedora)0519-07-2026
9Überschreiben von Dateien in antlr4-project (Fedora)0519-07-2026
10Российский учёные создали проект шлюзового отсека лунной базы08.7328-07-2026

Классификация: Пресс-релизы. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 8.26. Источник: www.root.cz.