Skip to content

Модули и пакеты

This content is not available in your language yet.

Пока все примеры курса помещались в один файл. Настоящая программа так не живёт — и хорошая новость в том, что раскладывать её по файлам в Mojo просто: в обычном случае ни настройки, ни возни с sys.path не требуется.

Правило ровно одно: файл — это модуль, каталог — это пакет.

Пусть рядом с app.mojo лежит geometry.mojo. Тогда работают все привычные формы импорта:

import geometry # весь модуль
from geometry import PI, to_centimeters # отдельные имена
import geometry as geo # с псевдонимом
from geometry import * # всё сразу

Обращение — как в Python: geometry.PI или просто PI, если имя импортировано напрямую.

Если модуль не нашёлся:

error: unable to locate module 'shapes'
Что это значит

Компилятор не видит модуль или пакет с таким именем ни рядом с главным файлом, ни в путях поиска.

Как исправить

Проверьте имя файла (оно и есть имя модуля) и его расположение. Если код лежит в другом месте — добавьте путь флагом -I:

Окно терминала
uv run mojo -I ../общая-библиотека app.mojo

Каталог с файлами .mojo — это уже пакет:

from shapes.circle import Circle

Никакого __init__.mojo для этого не требуется — в отличие от старых версий Python, каталог работает как пакет сам по себе.

Тогда зачем __init__.mojo нужен? Чтобы собрать публичные имена в одном месте и спрятать внутреннее устройство пакета:

shapes/__init__.mojo
"""Пакет фигур: собирает публичные имена в одном месте."""
from .circle import Circle
from .rectangle import Rectangle

После этого пользователь пишет коротко и не знает, в каком именно файле лежит Circle:

from shapes import Circle, Rectangle

Захотите завтра разбить circle.mojo на три файла — код, который вас импортирует, не заметит.

Проверим то, чего питонист опасается инстинктивно. Два модуля, каждый импортирует другой:

c_mod.mojo
from d_mod import dee
def see() -> Int:
return dee() + 1
d_mod.mojo
from c_mod import see
def dee() -> Int:
return 10
def both() -> Int:
return see() + dee()

Это компилируется и работает: both() возвращает 21.

🐍 Python
# ImportError: cannot import name 'see'
# from partially initialized module
# (most likely due to a circular import)
🔥 Mojo
# работает

В Python импорт — это выполнение файла сверху вниз, поэтому кольцо приводит к обращению к недособранному модулю. В Mojo импорт — разрешение имён на этапе компиляции: компилятор видит оба файла целиком и просто связывает символы.

Опираться на это не стоит — кольцевые зависимости всё равно усложняют чтение кода, — но чинить их срочными костылями, как в Python, здесь не придётся.

Подчёркивание в начале имени — только соглашение:

from geometry import _helper # компилируется

Компилятор не мешает импортировать «внутреннее» имя. Обозначайте намерение подчёркиванием, но не рассчитывайте, что язык вас защитит.

Запустить можно только файл с функцией main:

error: module does not define a `main` function
Что это значит

Вы попытались запустить модуль-библиотеку: в нём есть функции и структуры, но нет точки входа.

Как исправить

Запускайте файл приложения, а модуль подключайте импортом. Обратное ограничение не действует: модуль с собственной main импортировать можно, его main просто не вызовется.

Когда библиотека готова, её можно собрать в один файл — чтобы не таскать за собой дерево исходников и не пересобирать пакет при каждом запуске:

Окно терминала
uv run mojo precompile shapes -o shapes.mojoc
uv run mojo app.mojo

Импорт при этом не меняется ни на строчку: from shapes import Circle работает одинаково и с каталогом исходников, и с файлом .mojoc. Флаг -I нужен, только если .mojoc лежит не рядом с главным файлом.

КомандаЧто делает
mojo app.mojoсобирает и сразу запускает (то же, что mojo run)
mojo build app.mojoсобирает исполняемый файл
mojo precompile пакет -o имя.mojocпредкомпилирует пакет
mojo format файл.mojoформатирует код
mojo doc файл.mojoизвлекает документацию в JSON
mojo replинтерактивная сессия
mojo debugзапуск под отладчиком

Собранный через mojo build файл запускается из любого каталога — модули в него уже вкомпилированы, исходники рядом не нужны. Полностью автономным он при этом не становится: рантайм Mojo подключается динамически, поэтому на чужой машине всё равно понадобится установленная среда Mojo.

Маленький проект из пяти файлов:

  • app.mojo
  • geometry.mojo
  • Directoryshapes/
    • __init__.mojo
    • circle.mojo
    • rectangle.mojo
geometry.mojo
"""Общие константы и функции, не привязанные к конкретной фигуре."""
comptime PI = 3.14159
def to_centimeters(meters: Float64) -> Float64:
"""Переводит метры в сантиметры."""
return meters * 100.0
shapes/circle.mojo
"""Круг."""
from geometry import PI
@fieldwise_init
struct Circle(ImplicitlyCopyable, Writable):
"""Круг, заданный радиусом."""
var radius: Float64
def area(self) -> Float64:
"""Возвращает площадь круга."""
return PI * self.radius * self.radius
def write_to[W: Writer](self, mut writer: W):
"""Печатает круг человекочитаемо."""
writer.write("круг r=", self.radius)
shapes/rectangle.mojo
"""Прямоугольник."""
@fieldwise_init
struct Rectangle(ImplicitlyCopyable, Writable):
"""Прямоугольник, заданный сторонами."""
var width: Float64
var height: Float64
def area(self) -> Float64:
"""Возвращает площадь прямоугольника."""
return self.width * self.height
def write_to[W: Writer](self, mut writer: W):
"""Печатает прямоугольник человекочитаемо."""
writer.write("прямоугольник ", self.width, "x", self.height)
shapes/__init__.mojo
"""Пакет фигур: собирает публичные имена в одном месте."""
from .circle import Circle
from .rectangle import Rectangle
app.mojo
"""Точка входа: собирает пакет фигур и общий модуль вместе."""
import geometry
from geometry import to_centimeters
from shapes import Circle, Rectangle
def main():
var circle = Circle(2.0)
var rectangle = Rectangle(3.0, 4.0)
print(circle, "площадь", circle.area())
print(rectangle, "площадь", rectangle.area())
print("PI из модуля:", geometry.PI)
print("два метра в сантиметрах:", to_centimeters(2.0))
Результат

круг r=2.0 площадь 12.56636 прямоугольник 3.0x4.0 площадь 12.0 PI из модуля: 3.14159 два метра в сантиметрах: 200.0

Запускается это одной командой, без сборочных файлов и настройки путей:

Окно терминала
uv run mojo app.mojo

🎯 Проверь себя

Нужен ли `__init__.mojo`, чтобы каталог стал пакетом?

Нет, каталог работает как пакет и без него. __init__.mojo нужен для другого: собрать публичные имена, чтобы пользователь писал from shapes import Circle, не зная внутреннего устройства пакета.

Почему циклический импорт в Mojo не приводит к ошибке?

Потому что импорт здесь — разрешение имён на этапе компиляции, а не выполнение файла сверху вниз, как в Python. Компилятор видит оба модуля целиком и связывает символы. Злоупотреблять этим всё равно не стоит.

Чем заменить устаревшую команду `mojo package`?

Командой mojo precompile, а расширение .mojopkg — на .mojoc. Старый вариант ещё работает, но выдаёт предупреждение об устаревании.

Где Mojo ищет модуль, который вы импортируете?

Рядом с запускаемым файлом, а не в текущем каталоге терминала. Дополнительные пути добавляются флагом -I.

На этом «Базовый Mojo» закончен: вы умеете объявлять переменные и типы, писать функции, обрабатывать ошибки, создавать собственные структуры и раскладывать код по файлам. Этого достаточно, чтобы писать программы.

Дальше — то, ради чего язык и создавался. Владение значениями объясняет, почему var b = a требует разрешения, что означают mut, ref и out и как Mojo обходится без сборщика мусора, не заставляя вас, как C, следить за памятью вручную.

Примеры проверены на Mojo 1.0.0

Тексты курса — CC BY-NC-SA 4.0, код примеров — Apache 2.0