A Guide to MoonBit Python Integration
Introduction
Python, with its concise syntax and vast ecosystem, has become one of the most popular programming languages today. However, discussions around its performance bottlenecks and the maintainability of its dynamic typing system in large-scale projects have never ceased. To address these challenges, the developer community has explored various optimization paths.
The python.mbt
tool, officially launched by MoonBit, offers a new perspective. It allows developers to call Python code directly within the MoonBit environment. This combination aims to merge MoonBit's static type safety and high-performance potential with Python's mature ecosystem. Through python.mbt
, developers can leverage MoonBit's static analysis capabilities, modern build and testing tools, while enjoying Python's rich library functions, making it possible to build large-scale, high-performance system-level software.
This article aims to delve into the working principles of python.mbt
and provide a practical guide. It will answer common questions such as: How does python.mbt
work? Is it slower than native Python due to an added intermediate layer? What are its advantages over existing tools like C++'s pybind11
or Rust's PyO3
? To answer these questions, we first need to understand the basic workflow of the Python interpreter.
How the Python Interpreter Works
The Python interpreter executes code in three main stages:
-
Parsing: This stage includes lexical analysis and syntax analysis. The interpreter breaks down human-readable Python source code into tokens and then organizes these tokens into a tree-like structure, the Abstract Syntax Tree (AST), based on syntax rules.
For example, for the following Python code:
def add(x, y): return x + y a = add(1, 2) print(a)
We can use Python's
ast
module to view its generated AST structure:Module( body=[ FunctionDef( name='add', args=arguments( args=[ arg(arg='x'), arg(arg='y')]), body=[ Return( value=BinOp( left=Name(id='x', ctx=Load()), op=Add(), right=Name(id='y', ctx=Load())))]), Assign( targets=[ Name(id='a', ctx=Store())], value=Call( func=Name(id='add', ctx=Load()), args=[ Constant(value=1), Constant(value=2)])), Expr( value=Call( func=Name(id='print', ctx=Load()), args=[ Name(id='a', ctx=Load())]))])
-
Compilation: Next, the Python interpreter compiles the AST into a lower-level, more linear intermediate representation called bytecode. This is a platform-independent instruction set designed for the Python Virtual Machine (PVM).
Using Python's
dis
module, we can view the bytecode corresponding to the above code:2 LOAD_CONST 0 (<code object add>) MAKE_FUNCTION STORE_NAME 0 (add) 5 LOAD_NAME 0 (add) PUSH_NULL LOAD_CONST 1 (1) LOAD_CONST 2 (2) CALL 2 STORE_NAME 1 (a) 6 LOAD_NAME 2 (print) PUSH_NULL LOAD_NAME 1 (a) CALL 1 POP_TOP RETURN_CONST 3 (None)
-
Execution: Finally, the Python Virtual Machine (PVM) executes the bytecode instructions one by one. Each instruction corresponds to a C function call in the CPython interpreter's underlying layer. For example,
LOAD_NAME
looks up a variable, andBINARY_OP
performs a binary operation. It is this process of interpreting and executing instructions one by one that is the main source of Python's performance overhead. A simple1 + 2
operation involves the entire complex process of parsing, compilation, and virtual machine execution.
Understanding this process helps us grasp the basic approaches to Python performance optimization and the design philosophy of python.mbt
.
Paths to Optimizing Python Performance
Currently, there are two mainstream methods for improving Python program performance:
- Just-In-Time (JIT) Compilation: Projects like PyPy analyze a running program and compile frequently executed "hotspot" bytecode into highly optimized native machine code, thereby bypassing the PVM's interpretation and significantly speeding up computationally intensive tasks. However, JIT is not a silver bullet; it cannot solve the inherent problems of Python's dynamic typing, such as the difficulty of effective static analysis in large projects, which poses challenges for software maintenance.
- Native Extensions: Developers can use languages like C++ (with
pybind11
) or Rust (withPyO3
) to directly call Python functions or to write performance-critical modules that are then called from Python. This method can achieve near-native performance, but it requires developers to be proficient in both Python and a complex system-level language, presenting a steep learning curve and a high barrier to entry for most Python programmers.
python.mbt
is also a native extension. But compared to languages like C++ and Rust, it attempts to find a new balance between performance, ease of use, and engineering capabilities, with a greater emphasis on using Python features directly within the MoonBit language.
- High-Performance Core: MoonBit is a statically typed, compiled language whose code can be efficiently compiled into native machine code. Developers can implement computationally intensive logic in MoonBit to achieve high performance from the ground up.
- Seamless Python Calls:
python.mbt
interacts directly with CPython's C-API to call Python modules and functions. This means call overhead is minimized, bypassing Python's parsing and compilation stages and going straight to the virtual machine execution layer. - Gentler Learning Curve: Compared to C++ and Rust, MoonBit's syntax is more modern and concise. It also has comprehensive support for functional programming, a documentation system, unit testing, and static analysis tools, making it more friendly to developers accustomed to Python.
- Improved Engineering and AI Collaboration: MoonBit's strong type system and clear interface definitions make code intent more explicit and easier for static analysis tools and AI-assisted programming tools to understand. This helps maintain code quality in large projects and improves the efficiency and accuracy of collaborative coding with AI.
Using Pre-wrapped Python Libraries in MoonBit
To facilitate developer use, MoonBit will officially wrap mainstream Python libraries once the build system and IDE are mature. After wrapping, users can use these Python libraries in their projects just like importing regular MoonBit packages. Let's take the matplotlib
plotting library as an example.
First, add the matplotlib
dependency in your project's root moon.pkg.json
or via the terminal:
moon update
moon add Kaida-Amethyst/matplotlib
Then, declare the import in the moon.pkg.json
of the sub-package where you want to use the library. Here, we follow Python's convention and set an alias plt
:
{
"import": [
{
"path": "Kaida-Amethyst/matplotlib",
"alias": "plt"
}
]
}
After configuration, you can call matplotlib
in your MoonBit code to create plots:
let (Double) -> Double
sin : (Double
Double) -> Double
Double = (x : Double) -> Double
Calculates the sine of a number in radians. Handles special cases and edge
conditions according to IEEE 754 standards.
Parameters:
x
: The angle in radians for which to calculate the sine.
Returns the sine of the angle x
.
Example:
inspect(@math.sin(0.0), content="0")
inspect(@math.sin(1.570796326794897), content="1") // pi / 2
inspect(@math.sin(2.0), content="0.9092974268256817")
inspect(@math.sin(-5.0), content="0.9589242746631385")
inspect(@math.sin(31415926535897.9323846), content="0.0012091232715481885")
inspect(@math.sin(@double.not_a_number), content="NaN")
inspect(@math.sin(@double.infinity), content="NaN")
inspect(@math.sin(@double.neg_infinity), content="NaN")
@math.sin
fn main {
let Array[Double]
x = type Array[T]
An Array
is a collection of values that supports random access and can
grow in size.
Array::(Int, (Int) -> Double) -> Array[Double]
Creates a new array of the specified length, where each element is
initialized using an index-based initialization function.
Parameters:
length
: The length of the new array. If length
is less than or equal
to 0, returns an empty array.
initializer
: A function that takes an index (starting from 0) and
returns a value of type T
. This function is called for each index to
initialize the corresponding element.
Returns a new array of type Array[T]
with the specified length, where each
element is initialized using the provided function.
Example:
let arr = Array::makei(3, i => i * 2)
inspect(arr, content="[0, 2, 4]")
makei(100, fn(Int
i) { Int
i.(self : Int) -> Double
Converts a 32-bit integer to a double-precision floating-point number. The
conversion preserves the exact value since all integers in the range of Int
can be represented exactly as Double
values.
Parameters:
self
: The 32-bit integer to be converted.
Returns a double-precision floating-point number that represents the same
numerical value as the input integer.
Example:
let n = 42
inspect(n.to_double(), content="42")
let neg = -42
inspect(neg.to_double(), content="-42")
to_double() (self : Double, other : Double) -> Double
Multiplies two double-precision floating-point numbers. This is the
implementation of the *
operator for Double
type.
Parameters:
self
: The first double-precision floating-point operand.
other
: The second double-precision floating-point operand.
Returns a new double-precision floating-point number representing the product
of the two operands. Special cases follow IEEE 754 standard:
- If either operand is NaN, returns NaN
- If one operand is infinity and the other is zero, returns NaN
- If one operand is infinity and the other is a non-zero finite number,
returns infinity with the appropriate sign
- If both operands are infinity, returns infinity with the appropriate sign
Example:
inspect(2.5 * 2.0, content="5")
inspect(-2.0 * 3.0, content="-6")
let nan = 0.0 / 0.0 // NaN
inspect(nan * 1.0, content="NaN")
* 0.1 })
let Array[Double]
y = Array[Double]
x.(self : Array[Double], f : (Double) -> Double) -> Array[Double]
Maps a function over the elements of the array.
Example
let v = [3, 4, 5]
let v2 = v.map((x) => {x + 1})
assert_eq(v2, [4, 5, 6])
map((Double) -> Double
sin)
// To ensure type safety, the wrapped subplots interface always returns a tuple of a fixed type.
// This avoids the dynamic behavior in Python where the return type depends on the arguments.
let (_, Unit
axes) = (Int, Int) -> (Unit, Unit)
plt::subplots(1, 1)
// Use the .. cascade call syntax
Unit
axes[0(Int) -> Unit
][0]
..(Array[Double], Array[Double], Unit, Unit, Int) -> Unit
plot(Array[Double]
x, Array[Double]
y, Unit
color = Unit
Green, Unit
linestyle = Unit
Dashed, Int
linewidth = 2)
..(String) -> Unit
set_title("Sine of x")
..(String) -> Unit
set_xlabel("x")
..(String) -> Unit
set_ylabel("sin(x)")
() -> Unit
@plt.show()
}
Currently, on macOS and Linux, MoonBit's build system can automatically handle dependencies. On Windows, users may need to manually install a C compiler and configure the Python environment. Future MoonBit IDEs will aim to simplify this process.
Using Unwrapped Python Modules in MoonBit
The Python ecosystem is vast, and even with AI technology, relying solely on official wrappers is not realistic. Fortunately, we can use the core features of python.mbt
to interact directly with any Python module. Below, we demonstrate this process using the simple time
module from the Python standard library.
Introducing python.mbt
First, ensure your MoonBit toolchain is up to date, then add the python.mbt
dependency:
moon update
moon add Kaida-Amethyst/python
Next, import it in your package's moon.pkg.json
:
{
"import": ["Kaida-Amethyst/python"]
}
python.mbt
automatically handles the initialization (Py_Initialize
) and shutdown of the Python interpreter, so developers don't need to manage it manually.
Importing Python Modules
Use the @python.pyimport
function to import modules. To avoid performance loss from repeated imports, it is recommended to use a closure technique to cache the imported module object:
// Define a struct to hold the Python module object for enhanced type safety
pub struct TimeModule {
?
time_mod: PyModule
}
// Define a function that returns a closure for getting a TimeModule instance
fn () -> () -> TimeModule
import_time_mod() -> () -> struct TimeModule {
time_mod: ?
}
TimeModule {
// The import operation is performed only on the first call
guard (String) -> Unit
@python.pyimport("time") is (?) -> Unit
Some(?
time_mod) else {
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("Failed to load Python module: time")
() -> () -> TimeModule
panic("ModuleLoadError")
}
let TimeModule
time_mod = struct TimeModule {
time_mod: ?
}
TimeModule::{ ?
time_mod }
// The returned closure captures the time_mod variable
fn () { TimeModule
time_mod }
}
// Create a global time_mod "getter" function
let () -> TimeModule
time_mod: () -> struct TimeModule {
time_mod: ?
}
TimeModule = () -> () -> TimeModule
import_time_mod()
In subsequent code, we should always call time_mod()
to get the module, not import_time_mod
.
Converting Between MoonBit and Python Objects
To call Python functions, we need to convert between MoonBit objects and Python objects (PyObject
).
-
Integers: Use
PyInteger::from
to create aPyInteger
from anInt64
, andto_int64()
for the reverse conversion.test "py_integer_conversion" { let
n:Int64
Int64 = 42 letInt64
py_int =&Show
PyInteger::from((Int64) -> &Show
n)Int64
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.
Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
py_int,&Show
content="42")String
assert_eq((a : Int64, b : Int64, msg? : String, loc~ : SourceLoc = _) -> Unit raise Error
Asserts that two values are equal. If they are not equal, raises a failure with a message containing the source location and the values being compared.
Parameters:
a
: First value to compare.b
: Second value to compare.loc
: Source location information to include in failure messages. This is usually automatically provided by the compiler.
Throws a
Failure
error if the values are not equal, with a message showing the location of the failing assertion and the actual values that were compared.Example:
assert_eq(1, 1) assert_eq("hello", "hello")
py_int.&Show
to_int64(), 42L) }() -> Int64
-
Floats: Use
PyFloat::from
andto_double
.test "py_float_conversion" { let
n:Double
Double = 3.5 letDouble
py_float =&Show
PyFloat::from((Double) -> &Show
n)Double
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.
Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
py_float,&Show
content="3.5")String
assert_eq((a : Double, b : Double, msg? : String, loc~ : SourceLoc = _) -> Unit raise Error
Asserts that two values are equal. If they are not equal, raises a failure with a message containing the source location and the values being compared.
Parameters:
a
: First value to compare.b
: Second value to compare.loc
: Source location information to include in failure messages. This is usually automatically provided by the compiler.
Throws a
Failure
error if the values are not equal, with a message showing the location of the failing assertion and the actual values that were compared.Example:
assert_eq(1, 1) assert_eq("hello", "hello")
py_float.&Show
to_double(), 3.5) }() -> Double
-
Strings: Use
PyString::from
andto_string
.test "py_string_conversion" { let
py_str =&Show
PyString::from("hello")(String) -> &Show
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.
Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
py_str,&Show
content="'hello'")String
assert_eq((a : String, b : String, msg? : String, loc~ : SourceLoc = _) -> Unit raise Error
Asserts that two values are equal. If they are not equal, raises a failure with a message containing the source location and the values being compared.
Parameters:
a
: First value to compare.b
: Second value to compare.loc
: Source location information to include in failure messages. This is usually automatically provided by the compiler.
Throws a
Failure
error if the values are not equal, with a message showing the location of the failing assertion and the actual values that were compared.Example:
assert_eq(1, 1) assert_eq("hello", "hello")
py_str.&Show
to_string(), "hello") }(&Show) -> String
-
Lists: You can create an empty
PyList
andappend
elements, or create one directly from anArray[&IsPyObject]
.test "py_list_from_array" { let
one =Unit
PyInteger::from(1) let(Int) -> Unit
two =Unit
PyFloat::from(2.0) let(Double) -> Unit
three =Unit
PyString::from("three") let(String) -> Unit
arrArray[Unit]
:Array[Unit]
Arraytype Array[T]
An
Array
is a collection of values that supports random access and can grow in size.[&IsPyObject] = [Array[Unit]
one,Unit
two,Unit
three] letUnit
list =&Show
PyList::from((Array[Unit]) -> &Show
arr)Array[Unit]
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.
Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
list,&Show
content="[1, 2.0, 'three']") }String
-
Tuples:
PyTuple
requires specifying the size first, then filling elements one by one using theset
method.test "py_tuple_creation" { let
tuple =&Show
PyTuple::new(3)(Int) -> &Show
tuple ..&Show
set(0,(Int, Unit) -> Unit
PyInteger::from(1)) ..(Int) -> Unit
set(1,(Int, Unit) -> Unit
PyFloat::from(2.0)) ..(Double) -> Unit
set(2,(Int, Unit) -> Unit
PyString::from("three"))(String) -> Unit
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.
Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
tuple,&Show
content="(1, 2.0, 'three')") }String
-
Dictionaries:
PyDict
mainly supports strings as keys. Usenew
to create a dictionary andset
to add key-value pairs. For non-string keys, useset_by_obj
.test "py_dict_creation" { let
dict =&Show
PyDict::new()() -> &Show
dict ..&Show
set("one",(String, Unit) -> Unit
PyInteger::from(1)) ..(Int) -> Unit
set("two",(String, Unit) -> Unit
PyFloat::from(2.0))(Double) -> Unit
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.
Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
dict,&Show
content="{'one': 1, 'two': 2.0}") }String
When getting elements from Python composite types, python.mbt
performs runtime type checking and returns an Optional[PyObjectEnum]
to ensure type safety.
test "py_list_get" {
let Unit
list = () -> Unit
PyList::new()
Unit
list.(Unit) -> Unit
append((Int) -> Unit
PyInteger::from(1))
Unit
list.(Unit) -> Unit
append((String) -> Unit
PyString::from("hello"))
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Unit
list.(Int) -> Unit
get(0).() -> &Show
unwrap(), String
content="PyInteger(1)")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Unit
list.(Int) -> Unit
get(1).() -> &Show
unwrap(), String
content="PyString('hello')")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Unit
list.(Int) -> &Show
get(2), String
content="None") // Index out of bounds returns None
}
Calling Functions in a Module
Calling a function is a two-step process: first, get the function object with get_attr
, then execute the call with invoke
. The return value of invoke
is a PyObject
that requires pattern matching and type conversion.
Here is the MoonBit wrapper for time.sleep
and time.time
:
// Wrap time.sleep
pub fn (seconds : Double) -> Unit
sleep(Double
seconds: Double
Double) -> Unit
Unit {
let TimeModule
lib = () -> TimeModule
time_mod()
guard TimeModule
lib.?
time_mod.(String) -> Unit
get_attr("sleep") is (_/0) -> Unit
Some((Unit) -> _/0
PyCallable(Unit
f)) else {
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("get function `sleep` failed!")
() -> Unit
panic()
}
let Unit
args = (Int) -> Unit
PyTuple::new(1)
Unit
args.(Int, Unit) -> Unit
set(0, (Double) -> Unit
PyFloat::from(Double
seconds))
match (try? Unit
f.(Unit) -> Unit
invoke(Unit
args)) {
Result[Unit, Error]
Ok(_) => Unit
Ok(())
(Error) -> Result[Unit, Error]
Err(Error
e) => {
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("invoke `sleep` failed!")
() -> Unit
panic()
}
}
}
// Wrap time.time
pub fn () -> Double
time() -> Double
Double {
let TimeModule
lib = () -> TimeModule
time_mod()
guard TimeModule
lib.?
time_mod.(String) -> Unit
get_attr("time") is (_/0) -> Unit
Some((Unit) -> _/0
PyCallable(Unit
f)) else {
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("get function `time` failed!")
() -> Double
panic()
}
match (try? Unit
f.() -> Unit
invoke()) {
(Unit) -> Result[Unit, Error]
Ok((_/0) -> Unit
Some((Unit) -> _/0
PyFloat(Unit
t))) => Unit
t.() -> Double
to_double()
_ => {
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("invoke `time` failed!")
() -> Double
panic()
}
}
}
After wrapping, we can use them in a type-safe way in MoonBit:
test "sleep" {
let Unit
start = () -> Double
time().() -> Unit
unwrap()
(seconds : Double) -> Unit
sleep(1)
let Unit
end = () -> Double
time().() -> Unit
unwrap()
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("start = \{Unit
start}")
(input : String) -> Unit
Prints any value that implements the Show
trait to the standard output,
followed by a newline.
Parameters:
value
: The value to be printed. Must implement the Show
trait.
Example:
println(42)
println("Hello, World!")
println([1, 2, 3])
println("end = \{Unit
end}")
}
Practical Advice
-
Define Clear Boundaries: Treat
python.mbt
as the "glue layer" connecting MoonBit and the Python ecosystem. Keep core computation and business logic in MoonBit to leverage its performance and type system advantages, and only usepython.mbt
when necessary to call Python-exclusive libraries. -
Use ADTs Instead of String Magic: Many Python functions accept specific strings as arguments to control behavior. In MoonBit wrappers, these "magic strings" should be converted to Algebraic Data Types (ADTs), i.e., enums. This leverages MoonBit's type system to move runtime value checks to compile time, greatly enhancing code robustness.
-
Thorough Error Handling: The examples in this article use
panic
or return simple strings for brevity. In production code, you should define dedicated error types and pass and handle them through theResult
type, providing clear error context. -
Map Keyword Arguments: Python functions extensively use keyword arguments (kwargs), such as
plot(color='blue', linewidth=2)
. This can be elegantly mapped to MoonBit's Labeled Arguments. When wrapping, prioritize using labeled arguments to provide a similar development experience.For example, a Python function that accepts
kwargs
:# graphics.py def draw_line(points, color="black", width=1): # ... drawing logic ... print(f"Drawing line with color {color} and width {width}")
Its MoonBit wrapper can be designed as:
fn draw_line(points: Array[Point], color~: Color = Black, width: Int = 1) -> Unit { let points : PyList = ... // convert Array[Point] to PyList // construct args let args = PyTuple::new(1) args .. set(0, points) // construct kwargs let kwargs = PyDict::new() kwargs ..set("color", PyString::from(color)) ...set("width", PyInteger::from(width)) match (try? f.invoke(args~, kwargs~)) { Ok(_) => () _ => { // handle error } } }
-
Beware of Dynamism: Always remember that Python is dynamically typed. Any data obtained from Python should be treated as "untrusted" and must undergo strict type checking and validation. Avoid using
unwrap
as much as possible; instead, use pattern matching to safely handle all possible cases.
Conclusion
This article has outlined the working principles of python.mbt
and demonstrated how to use it to call Python code in MoonBit, whether through pre-wrapped libraries or by interacting directly with Python modules. python.mbt
is not just a tool; it represents a fusion philosophy: combining MoonBit's static analysis, high performance, and engineering advantages with Python's vast and mature ecosystem. We hope this article provides developers in the MoonBit and Python communities with a new, more powerful option for building future software.