Модули и пакеты
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 нужен? Чтобы собрать публичные имена в одном
месте и спрятать внутреннее устройство пакета:
"""Пакет фигур: собирает публичные имена в одном месте."""
from .circle import Circlefrom .rectangle import RectangleПосле этого пользователь пишет коротко и не знает, в каком именно файле лежит
Circle:
from shapes import Circle, RectangleЗахотите завтра разбить circle.mojo на три файла — код, который вас
импортирует, не заметит.
Циклические импорты не ломаются
Заголовок раздела «Циклические импорты не ломаются»Проверим то, чего питонист опасается инстинктивно. Два модуля, каждый импортирует другой:
from d_mod import dee
def see() -> Int: return dee() + 1from c_mod import see
def dee() -> Int: return 10
def both() -> Int: return see() + dee()Это компилируется и работает: both() возвращает 21.
# ImportError: cannot import name 'see'# from partially initialized module# (most likely due to a circular import)# работаетВ 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.mojocuv run mojo app.mojoИмпорт при этом не меняется ни на строчку: from shapes import Circle
работает одинаково и с каталогом исходников, и с файлом .mojoc. Флаг -I
нужен, только если .mojoc лежит не рядом с главным файлом.
Команды mojo, которые пригодятся
Заголовок раздела «Команды mojo, которые пригодятся»| Команда | Что делает |
|---|---|
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
"""Общие константы и функции, не привязанные к конкретной фигуре."""
comptime PI = 3.14159
def to_centimeters(meters: Float64) -> Float64: """Переводит метры в сантиметры.""" return meters * 100.0"""Круг."""
from geometry import PI
@fieldwise_initstruct 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)"""Прямоугольник."""
@fieldwise_initstruct 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)"""Пакет фигур: собирает публичные имена в одном месте."""
from .circle import Circlefrom .rectangle import Rectangle"""Точка входа: собирает пакет фигур и общий модуль вместе."""
import geometryfrom geometry import to_centimetersfrom 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, следить за памятью вручную.
Тексты курса — CC BY-NC-SA 4.0, код примеров — Apache 2.0