So I’ve been working on this silly little project called PyLua and thought I’d share it with you all. It’s basically a Python interpreter that runs entirely inside Roblox using Luau. You can check the Github here
Important note: This isn’t a Python-to-Luau compiler or anything fancy like that - it actually parses and executes Python code in real-time while your game is running
What it does
You can literally write Python code as a string and execute it inside your Roblox scripts:
local PyLua = require("PyLua Module")
-- Create a Python runtime
local python = PyLua.new()
-- Execute multi-line Python code (statements)
python:execute([[
x = 10
y = 32
print("sum:", x + y)
]])
-- Evaluate a Python expression and get the result in Luau
local value = python:eval("1 + 2 * 3")
print("eval result:", value) -- 7
-- Share values via the globals table
python:execute("z = 5")
local globals = python:globals()
print("z from python globals:", globals.z) -- 5
Why did I make this?
Honestly? Just for the fun of it! I wanted to see if I could build a working interpreter in Luau, and it turned out to be a pretty awesome learning experience. Its easy to add new features, so I might keep expanding it whenever I’m bored.
Now go and check the Github maybe even download it? You chose, bye :3
I’ve always wanted to make an OS with programmable apps. This’ll defo help me to understand how to make a custom programming language and run it in roblox. Awesome work
You could gain a lot of performance here by targetting .pyc and running off that. Theres some interesting stuff out there with bytecode virtual machines, especially pyc. If you ever want to dip your hands into full on compilation, give it a try. CPython Internals is a good starting point imo.
You’d also allow scripts compiled from the outside world to run in your interpreter (not including library support) if your virtual machine correctly maps to each operation in the bytecode format.
Okay so, I actually looked into the idea of running raw .pyc files directly and dove deep into CPython’s internals. While it’s a cool concept, I decided not to fully replicate Python’s bytecode for a few key reasons:
Most games do not want to support dynamic code loading (like loadstring) and roblox luau does not support file access, and its memory model is tightly controlled.
Python bytecode (.pyc) is deeply tied to CPython’s internals (like its C-based memory management and stack frames), which don’t map cleanly to Luau’s VM.
Supporting full .pyc would introduce a ton of complexity for limited benefit — especially since the target environment is so different.
Instead, I’m taking inspiration from the concept and designing a simplified bytecode format for which allows:
Faster loading (compiled once, run multiple times),
Better performance than interpreting raw strings every time,
Safer and more controlled execution for in-game scripts and mods.
This also opens up AOT workflows (compile on PC, run on Roblox) while still supporting direct string-based execution for development.
In short: I’m taking .pyc, but optimizing it for Roblox and Luau’s limitations — and I think it is a good balance between them. Thanks again for the suggestion!
Bytecode executor has mostly all core features, have around 80% of opcodes defined and working, more inside PyLua/src/PyLua0.2/vm, going to start working on finalizing it and then moving on to the Bytecode Compiler. I’m open for feedback on the opcodes before I start on it :3
For better performance when running the same code repeatedly:
local python = require('path.to.PyLua0.2.python')
-- Compile Python code to bytecode
local bytecode, error = python.compile([[
x = 10
y = 20
result = x + y
print("Result:", result)
]])
if error then
print("Compilation error:", error)
return
end
-- Execute the bytecode multiple times
for i = 1, 5 do
print("Execution", i)
local success, variables = python.runBytecode(bytecode)
if success then
print("Variables:", variables)
end
end
This is pretty amazing and I like how it’s readable and modular so thats a big plus, but there is something that bothers me. I kno that the interpreter isn’t meant to be extremly fast and beat Luau in raw performance but there are some stuff especially in the built-in functions that could be improved and tuned for better performance. Also I wanted to point out that the len() function is not accurate for strings since I see that you are using string.len() which returns the number of bytes and not the number of characters in a string, this isn’t that big of a problem but just wanted to point it out. I recommend using utf8.len() instead to replicate the exact behaviour. I’m not saying any of this to be rude or anything, this resource is indeed amazing I’m just pointing out stuff that could be improved.
I forgot to mention that the string.len() function only becomes a problem with multi-byte characters like emojis where Python normally would return 1 for a single multi-byte character and Luau with string.len() would return the amount of bytes it takes that could be 2, 4 or even more.
It’d be cool if it was able to retrieve and modify instance properties through built-in functions, but generally besides that, this looks pretty awesome and I could see it bringing new creators on Roblox within the near future.
Hi, I wanted to share an update about PyLua’s v0.3 and its new API
What’s New in v0.3? (Still in development)
Runtime-Based Architecture
-- OLD v0.2 approach (static/global)
local result = PyLua.execute("print('Hello')")
-- NEW v0.3 approach (runtime instances)
local python = PyLua.new()
local result = python:eval("2 + 3") -- Returns 5
python:execute("message = 'Hello from Python!'")
Multiple Isolated Runtimes
-- Game modding example: Each mod gets its own Python environment
local mod1_runtime = PyLua.new()
local mod2_runtime = PyLua.new()
mod1_runtime:globals().mod_name = "Custom ducks"
mod2_runtime:globals().mod_name = "New animated ducks"
-- Completely isolated - no interference between mods
Custom Function Injection
local python = PyLua.new()
-- Inject Luau functions into Python environment
python:globals().roblox_raycast = function(origin, direction)
return workspace:Raycast(origin, direction)
end
-- Use in Python code
python:execute([[
result = roblox_raycast(Vector3.new(0, 10, 0), Vector3.new(0, -1, 0))
if result:
print("Hit:", result.Instance.Name)
]])
Some quick updates since this has been taking a bit longer than expected :3
I’m currently on section 4.3 of internalDocs/REWRITE_PLAN.md. Right now the focus is implementing for loops and the break / continue control-flow logic.
Major changes vs v0.2
Parser + AST: instead of compiling directly from source to bytecode, the new pipeline generates a proper AST using the modules in src/PyLua/parser and src/PyLua/ast.
Real object model: the new implementation uses a proper Python-style object system under src/PyLua/objects (for example, base.luau, functions.luau, collections.luau). Everything is treated as an object, aligning behavior with CPython semantics.