🤝 The “Python + Lua” pair combines the rich Python ecosystem and the speed/embeddability of Lua.
Below are 3 working ways to integrate with examples, tips for data exchange and checklists for a quick start.

🔍 Why combine Python and Lua
Python | Lua |
|---|---|
A sea of libraries: web, AI, data | Minimum runtime, easy to integrate |
Simple code, fast prototype | Often faster in tight loops (LuaJIT) |
API/backend, data processing | Scripting engines/games, plugins |
🎯 Typical scenarios
Games and simulations: server/tools in Python, game logic in Lua.
Plugins: users write extensions in Lua, the core in Python.
Automation: Python orchestration, Lua scripts for tasks.
🛠 Three ways to integrate
1) Embed Lua in Python via lupa (LuaJIT)
pip install lupaPython → calls Lua
from lupa import LuaRuntime
lua = LuaRuntime(unpack_returned_tuples=True)
# 1) We get the function directly
greet = lua.eval('function(name) return "Hello, " .. name .. "!" end'int(greet("Code")) # Hi, Kodik!
# 2) We work with the table as an object
Vector = lua.eval('''
function (x, y)
return { x = x or 0, y = y or 0,
len = function(self) return math.sqrt(self.x*self.x + self.y*self.y) end
}
end
''')
v = Vector(3, 4)
print(v.len(v)) # 5.0Lua → calls Python (callbacks)
from lupa import LuaRuntime
lua = LuaRuntime()
def py_log(msg): print("[PY]", msg)
lua.globals().py_log = py_log
lua.execute('py_log("Hello from Lua!")'') # [PY] Hello from Lua!
# Passing a Python function to a Lua algorithm
lua.execute('''
function apply_twice(fn, x) return fn(fn(x)) end
''')
apply_twice = lua.eval('apply_twice')
print(apply_twice(lambda v: v * 2, 5)) # 20Pros: simple, very fast (LuaJIT), two-way calls.
Cons: dependency on binaries, nuances of types.
2) Run Lua as a process (subprocess + JSON)
An isolated and beginner-friendly way: we communicate through standard streams and serialization.
Lua script (stdin → stdout)
-- file: worker.lua
local json = require("dkjson") -- или cjson, если доступен
local input = io.read("*a")
local req = json.decode(input)
local result = { sum = req.a + req.b, ok = true }
io.write(json.encode(result))Python wrapper
import json, subprocess
payload = json.dumps({"a": 2, "b": 40})
proc = subprocess.run(
["lua", "worker.lua"],
input=payload, text=True, capture_output=True, check=True
)
res = json.loads(proc.stdout)
print(res["sum"]) # 42Pros: media independence, easy to debug/scale. Cons: IPC is more expensive than a direct call.
3) Advanced: common C-layer / FFI
Suitable when there is an existing C/C++ module: Python and Lua are linked to the same native code. For a beginner, it is enough to know that there is such a path; deeper — later.
🔄 Painless data exchange
What we are sending | How we transmit | Comment |
|---|---|---|
Numbers, strings, bool | Function arguments (lupa) / JSON | Note that in Lua, numbers are floats by default |
Arrays/tables | Lua table ↔ Python list/dict | For complex structures — serialization in JSON/MsgPack |
Binary data | Base64 / files / shared memory | In the subprocess scheme, file/pipe is more convenient |
Common mistakes: floating types (int vs float), string encoding (UTF-8!), recursion in structures.
🧯 Errors and logging
Exceptions
lupa: Lua error will raise Python exception — wrap calls
try/except.subprocess: check
returncodeand parsestderr.
Logs
Make a common prefix for logs:
[PY]and[LUA].For subprocess — log input/output (trimming large payloads).
💡 Mini-example: "rules" in Lua, service in Python
-- rules.lua
return {
discount = function(price)
if price >= 100 then return price * 0.9 end
return price
end
}# service.py
from lupa import LuaRuntime
lua = LuaRuntime()
rules = lua.eval("dofile")("rules.lua")
def final_price(x): return rules["discount"](x)
print(final_price(150)) # 135.0💡 Mini-example: mini-RPC via JSON
-- rpc.lua (Lua-процесс)
local json = require "dkjson"
local fn = {
hello = function(args) return "Hi, " .. (args.name or "anon") end,
add = function(a,b) return a+b end
}
local req = json.decode(io.read("*a"))
local ok, out = pcall(function()
if req.fn == "add" then return fn.add(req.a, req.b)
else return fn.hello(req) end
end)
io.write(json.encode({ ok = ok, result = out }))# py_rpc.py
import json, subprocess
def call(fn, **kw):
req = json.dumps(dict(fn=fn, **kw))
p = subprocess.run(["lua","rpc.lua"], input=req, text=True,
capture_output=True, check=True)
return json.loads(p.stdout)["result"]
print(call("hello", name="Code"))
print(call("add", a=2, b=40))📚 Do you want to delve into the topic?
In the attachment Code you will find detailed lessons on Puthon and Lua, step-by-step exercises, error analysis and convenient practice right on your phone or browser.
And if you want to be aware of the news, new features and useful materials - subscribe to our Telegram channel. It's cozy, businesslike and with love for code ❤️
💬 What will you take out in Lua — rules, configs, or "hot" calculations?
