{}const=>[]async()letfn</>var
DevelopmentLuaPython

Python + Lua: How to make two languages friends in one project

How to combine Python and Lua in one project, why it is necessary and what are the ways to integrate

К

Kodik

Author

4 min read

🤝 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

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

🎯 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 lupa

Python → 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.0

Lua → 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))  # 20

Pros: 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"])  # 42

Pros: 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 returncode and parse stderr.

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?

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card