#set document(title: "10.2 Calling Python from Mojo", author: "Modular Inc. / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 10.2#h(0.6em)Calling Python from Mojo The Python ecosystem is full of useful libraries, so you shouldn't have to rewrite them in Mojo. Instead, you can simply import Python packages and call Python APIs from Mojo. The Python code runs in a standard Python interpreter (CPython), so your existing Python code doesn't need to change. === Specify your Python version Mojo doesn't include a CPython interpreter—it uses the CPython interpreter provided by your environment's default Python version. So be sure you know which Python version you're using in each environment where your Mojo code will run. To ensure you get consistent results, we recommend you #link("https://pixi.prefix.dev/latest/installation/")[use Pixi] to manage your package dependency and virtual environment. In a Pixi project, you can specify the Python version like this: pixi add "python==3.11" Now, even if your operating system's default Python version is something else, your Pixi project (and the Mojo code inside) always uses Python 3.11. pixi run python --version Python 3.11.0 === Import a Python module in Mojo To import a Python module in Mojo, just call #link("https://mojolang.org/docs/std/python/python/Python/#import_module")[Python.import\_module()] with the module name. The following shows an example of importing the standard Python #link("https://numpy.org/")[NumPy] package: from std.python import Python def main() raises: \# This is equivalent to Python's \`import numpy as np\` np = Python.import\_module("numpy") \# Now use numpy as if writing in Python array = np.array(Python.list(1, 2, 3)) print(array) \[1 2 3\] Running this program produces the following output: Assuming that you have the NumPy package installed in your environment, this imports NumPy and you can use any of its features. If you want to use Python builtin APIs, you just need to import the builtins module the same way. For example: from std.python import Python def main() raises: np = Python.import\_module("numpy") array = np.array(Python.list(1, 2, 3)) builtins = Python.import\_module("builtins") print(builtins.type(array)) \ A few things to note: - The import\_module() method returns a reference to the module in the form of a #link("https://mojolang.org/docs/std/python/python_object/PythonObject/")[PythonObject] wrapper. You must store the reference in a variable and then use it as shown in the example above to access functions, classes, and other objects defined by the module. See Mojo wrapper objects for more information about the PythonObject type. - Currently, you cannot import individual members (such as a single Python class or function). You must import the whole Python module and then access members through the module name. - Mojo doesn't yet support top-level code, so the import\_module() call must be inside another method. This means you may need to import a module multiple times or pass around a reference to the module. This works the same way as Python: importing the module multiple times won't run the initialization logic more than once, so you don't pay any performance penalty. - import\_module() may raise an exception. Raising exceptions is much more common in Python code than in the Mojo standard library, which #link("https://mojolang.org/docs/roadmap#the-standard-library-has-limited-exceptions-use")[limits their use for performance reasons]. - We recommend using a package manager such as pixi, uv, or conda to manage your environment. For instructions on setting up a Mojo project with pixi, see Create a Mojo project in the Get started with Mojo tutorial. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #link("https://mojolang.org/docs/cli/build/")[mojo build] doesn't include the Python packages used by your Mojo project. Instead, Mojo loads the Python interpreter and Python packages at runtime, so they must be provided in the environment where you run the Mojo program (such as inside the pixi environment where you built the executable). ] ==== Import a local Python module If you have some local Python code you want to use in Mojo, just add the directory to the Python path and then import the module. For example, suppose you have a Python file named mypython.py: import numpy as np def gen\_random\_values(size, base): \# generate a size x size array of random numbers between base and base+1 random\_array = np.random.rand(size, size) return random\_array + base Here's how you can import it and use it in a Mojo file: from std.python import Python def main() raises: Python.add\_to\_path("path/to/module") mypython = Python.import\_module("mypython") values = mypython.gen\_random\_values(2, 3) print(values) Both absolute and relative paths work with #link("https://mojolang.org/docs/std/python/python/Python/#add_to_path")[add\_to\_path()]. For example, you can import from the local directory like this: Python.add\_to\_path(".")