Структуры¶
Структура Mojo - это структура данных, которая позволяет вам инкапсулировать поля и методы, работающие с абстракцией, такой как тип данных или объект. Поля - это переменные, которые содержат данные, относящиеся к структуре, а методы - это функции внутри структуры, которые обычно воздействуют на данные полей.
Например, если вы создаете графическую программу, вы можете использовать структуру для определения изображения, содержащую поля для хранения информации о каждом изображении (например, о пикселях) и методы, которые выполняют над ним действия (например, поворачивают его).
По большей части формат struct от Mojo предназначен для обеспечения статической, безопасной для памяти структуры данных для высокоуровневых типов данных, используемых в программах. Например, все типы данных в стандартной библиотеке Mojo (такие как Int, Bool, String и Tuple) определены как структуры.
Если вы понимаете, как работают функции и переменные в Mojo, вы, вероятно, заметили, что Mojo предназначен для предоставления функций динамического программирования в функции def, обеспечивая при этом более высокую безопасность кода в функциях fn. Когда дело доходит до структур, Mojo перестраховывается: вы все еще можете выбрать, использовать ли объявления def или fn для методов, но все поля должны быть объявлены с помощью var.
Определение структур¶
Вы можете определить простую структуру под названием MyPair с двумя такими полями:
struct MyPair:
var first: Int
var second: Int
Однако вы не можете создать экземпляр этой структуры, поскольку у нее нет метода конструктора. Итак, здесь мы используем конструктор для инициализации двух полей:
struct MyPair:
var first: Int
var second: Int
fn __init__(out self, first: Int, second: Int):
self.first = first
self.second = second
Обратите внимание, что первым аргументом в методе __init__() является out self. У вас будет аргумент self в качестве первого аргумента во всех методах struct. Он ссылается на текущий экземпляр struct (это позволяет коду в методе ссылаться на "себя"). Когда вы вызываете конструктор, вы никогда не передаете значение self— Mojo передает его автоматически.
Часть out в out self - это соглашение об аргументации, которое объявляет self как изменяемую ссылку, которая начинается как неинициализированная и должна быть инициализирована до возврата функции.
Многие типы используют полевой конструктор, подобный тому, который показан для MyPair выше: он принимает аргумент для каждого поля и инициализирует поля непосредственно из аргументов. Чтобы сэкономить на вводе текста, Mojo предоставляет декоратор @fieldwise_init, который генерирует конструктор с разбивкой по полям для структуры. Таким образом, вы можете переписать приведенный выше пример MyPair следующим образом:
@fieldwise_init
struct MyPair:
var first: Int
var second: Int
Метод __init__() является одним из многих специальных методов (также известных как "более простые методы", потому что они имеют двойное подчеркивание) с заранее определенными именами.
Вы не можете присваивать значения при объявлении полей. Вы должны инициализировать все поля структуры в конструкторе. (Если вы попытаетесь оставить поле неинициализированным, код не будет скомпилирован).
Построение типа структуры¶
Как только у вас будет конструктор, используя __init__ или @fieldwise_init, вы можете создать экземпляр MyPair и задать поля:
Construct an instance:
var mine = MyPair(2, 4)
print(mine.first)
2
Списки инициализаторов упрощают создание экземпляров без указания имени типа. Например, вот стандартный способ вызова функции:
Calling a function:
def process_pair(pair: MyPair):
...
process_pair(MyPair(2, 4))
Поскольку process_pair ожидает экземпляр MyPair, Mojo выводит это из контекста. Используйте фигурные скобки, чтобы создать экземпляр со списком инициализаторов:
Using an initializer list:
process_pair({2, 4})
Списки инициализаторов поддерживают все стили аргументов Mojo, включая аргументы с ключевыми словами. Например:
Initializer lists with a keyword argument:
def utility_function(argument: CustomType):
...
utility_function(CustomType(0.5, fish="swordfish"))
...is equivalent to...
utility_function({0.5, fish="swordfish"})
Результат является кратким, и оба вызова функционально идентичны в предыдущем примере.
Вы также можете обновить объявленные экземпляры с помощью списков инициализаторов. Mojo знает тип переменной. Например:
Reassignment with initializer lists:
# Construct a new instance
myCustomType = CustomType(0.5, fish="swordfish")
# Use the instance
...
# Update the instance before further use
myCustomType = {0.7, fish="salmon"}
Создание структуры, доступной для копирования и перемещения¶
Структуры Mojo по умолчанию недоступны для копирования или перемещения.
Например, в следующем коде возникают ошибки:
var a = MyPair(1, 2)
# Implicit copy
var b = a # value of type 'MyPair' is not implicitly copyable, it does not
# conform to 'ImplicitlyCopyable'
# Explicit copy
var c = a.copy() # 'MyPair' value has no attribute 'copy'
# Move
var d = a^ # value of type 'MyPair' cannot be copied or moved; consider
# conforming it to 'Movable'
В большинстве случаев вы можете сделать структуру доступной для копирования и перемещения, просто добавив соответствующие элементы.
Возможность копирования¶
Чтобы сделать структуру доступной для копирования, добавьте трейт Copyable:
struct MyPair(Copyable):
...
В большинстве случаев это все, что вам нужно сделать. Mojo сгенерирует для вас конструктор копирования (метод __copyinit__()). Вам не нужно создавать свой собственный, если только вам не нужна пользовательская логика в конструкторе копирования — например, если ваша структура динамически выделяет память. Дополнительные сведения см. в разделе, посвященном конструкторам копирования.
Свойство Copyable также предоставляет метод copy(), который обеспечивает более удобный способ копирования значения, чем прямой вызов конструктора копирования.
Неявная возможность копирования¶
Чтобы сделать структуру неявно копируемой, добавьте trait ImplicitlyCopyable:
struct MyPair(ImplicitlyCopyable):
...
ImplicitlyCopyable автоматически подразумевает возможность копирования, поэтому здесь применимы все замечания, связанные с возможностью копирования. Тип должен быть неявно копируемым только в том случае, если копирование типа обходится недорого и не имеет побочных эффектов. Ненужные копии могут привести к значительному снижению объема памяти и производительности, поэтому используйте эту функцию с осторожностью.
Возможность перемещения¶
Чтобы сделать структуру подвижной, добавьте трейт Movable:
struct MyPair(Copyable, Movable):
...
Mojo сгенерирует для вас конструктор перемещений. Вам редко приходится создавать свой собственный конструктор перемещений. Дополнительную информацию смотрите в разделе, посвященном конструкторам перемещений.
Movable и Сopyable MyPair¶
Вот подвижная и неявно копируемая версия MyPair:
@fieldwise_init
struct MyPair(ImplicitlyCopyable, Movable):
var first: Int
var second: Int
Поскольку структура MyPair - это простая структура данных, содержащая два значения Int, нет ничего плохого в том, чтобы сделать ее неявно доступной для копирования.
И вот как использовать конструкторы копирования и перемещения:
var original_pair = MyPair(2, 6)
var copied_pair = original_pair # implicit copy
var another_pair = original_pair.copy() # explicit copy
var moved_pair = original_pair^ # move
Методы¶
В дополнение к специальным методам, таким как __init__(), вы можете добавить в свою структуру любой другой метод, который вы хотите. Например:
@fieldwise_init
struct MyPair:
var first: Int
var second: Int
fn get_sum(self) -> Int:
return self.first + self.second
var mine = MyPair(6, 8)
print(mine.get_sum())
14
Обратите внимание, что функция get_sum() также использует аргумент self, потому что это единственный способ получить доступ к полям структуры в методе. Имя self - это всего лишь условное обозначение, и вы можете использовать любое имя, которое вы хотите использовать для ссылки на экземпляр struct, который всегда передается в качестве первого аргумента.
Методы, которые принимают неявный аргумент self, называются методами экземпляра, потому что они воздействуют на экземпляр struct.
Аргумент
selfв методеstruct- это единственный аргумент в функцииfn, для которого не требуется тип. Вы можете включить его, если хотите, но можете и не указывать, потому что Mojo уже знает его тип (в данном случаеMyPair).
fn vs def в методах структуры¶
Методы Struct могут быть объявлены с ключевыми словами def или fn. Одно из важных отличий заключается в том, что функция fn без ключевого слова raises не может вызвать ошибку. Когда вы вызываете функцию, которая может вызвать ошибку, из метода, который не может вызвать ошибку, Mojo требует, чтобы вы обрабатывали любые ошибки.
Если вы пишете код, который, как ожидается, будет широко использоваться или распространяться в виде пакета, вы можете захотеть использовать функции fn для API, которые не могут вызывать ошибку, чтобы ограничить количество мест, где пользователям необходимо добавлять код обработки ошибок.
Метод __del__() структуры, или деструктор, должен быть не вызывающим методом, поэтому он всегда объявляется с помощью fn (и без ключевого слова raises).
Статические методы¶
Структура также может иметь статические методы. Статический метод может быть вызван без создания экземпляра структуры. В отличие от методов экземпляра, статический метод не получает неявный аргумент self, поэтому он не может получить доступ к каким-либо полям в структуре.
Чтобы объявить статический метод, используйте декоратор @staticmethod и не включайте аргумент self:
struct Logger:
fn __init__(out self):
pass
@staticmethod
fn log_info(message: String):
print("Info: ", message)
Вы можете вызвать статический метод, вызвав его для типа (в данном случае Logger). Вы также можете вызвать его для экземпляра типа. Обе формы показаны ниже:
Logger.log_info("Static method called.")
var l = Logger()
l.log_info("Static method called from instance.")
Info: Static method called.
Info: Static method called from instance.
Сравнение структур и классов¶
Если вы знакомы с другими объектно-ориентированными языками, то структуры могут во многом походить на классы, и в них есть как сходство, так и важные различия. Со временем Mojo также будет поддерживать классы, чтобы они соответствовали поведению классов Python.
Итак, давайте сравним структуры Mojo с классами Python. Они оба поддерживают методы, поля, перегрузку операторов, декораторы для метапрограммирования и многое другое, но их ключевые отличия заключаются в следующем:
-
Классы Python динамичны: они допускают динамическую диспетчеризацию, исправление ошибок (или “переключение”) и динамическую привязку полей экземпляра во время выполнения.
-
Структуры Mojo статичны: они привязываются во время компиляции (вы не можете добавлять методы во время выполнения). Структуры позволяют вам сочетать гибкость с производительностью, оставаясь при этом безопасными и простыми в использовании.
-
Структуры Mojo не поддерживают наследование ("подклассификацию"), но структура может реализовывать трейты(
traits). -
Классы Python поддерживают атрибуты класса — значения, которые являются общими для всех экземпляров класса, что эквивалентно переменным класса или статическим элементам данных в других языках.
-
Структуры Mojo не поддерживают статические элементы данных.
Синтаксически, самое большое отличие от класса Python заключается в том, что все поля в struct должны быть явно объявлены с помощью var.
В Mojo структура и содержимое struct устанавливаются во время компиляции и не могут быть изменены во время выполнения программы. В отличие от Python, где вы можете добавлять, удалять или изменять атрибуты объекта "на лету", Mojo не позволяет этого для структур.
Однако статическая природа структур помогает Mojo быстрее выполнять ваш код. Программа точно знает, где найти информацию о структуре и как ее использовать без каких-либо дополнительных шагов или задержек во время выполнения.
Структуры Mojo также очень хорошо работают с функциями, которые вы, возможно, уже знаете из Python, такими как перегрузка операторов (которая позволяет вам изменять способ работы математических символов, таких как + и -, с вашими собственными данными, используя специальные методы).
Как упоминалось выше, все стандартные типы Mojo (Int, String и т.д.) создаются с использованием структур, а не встроены в сам язык. Это дает вам больше гибкости и контроля при написании кода, а также означает, что вы можете определять свои собственные типы со всеми теми же возможностями (для стандартных библиотечных типов нет специального режима).
Специальные методы¶
Специальные методы (или "более простые методы"), такие как __init__(), - это заранее определенные имена методов, которые вы можете определить в структуре для выполнения специальной задачи.
Хотя можно вызывать специальные методы с их именами, суть в том, что вы никогда не должны этого делать, потому что Mojo автоматически вызывает их в тех случаях, когда они необходимы (вот почему их также называют "магическими методами"). Например, Mojo вызывает метод __init__() при создании экземпляра структуры; а когда Mojo уничтожает экземпляр, он вызывает метод __del__() (если он существует).
Даже поведение оператора, которое кажется встроенным (+, <, ==, |, и так далее) реализуются в виде специальных методов, которые Mojo неявно вызывает для выполнения операций или сравнений с типом, к которому применяется оператор.
Mojo поддерживает длинный список специальных методов; их слишком много, чтобы обсуждать их здесь, но в целом они соответствуют всем специальным методам Python и обычно выполняют один из двух типов задач:
Перегрузка операторов: Для перегрузки операторов, таких как < (меньше, чем), + (добавить) и | (или), разработано множество специальных методов, чтобы они работали соответствующим образом с каждым типом. Дополнительные сведения см. в разделе Реализация операторов для пользовательских типов.
Обработка событий жизненного цикла: Эти специальные методы имеют дело с жизненным циклом и ценностью владения экземпляром. Например, __init__() и __del__() определяют начало и конец срока службы экземпляра, а другие специальные методы определяют поведение для других событий жизненного цикла, таких как копирование или перемещение значения.
Вы можете узнать все о специальных методах жизненного цикла в разделе Жизненный цикл значения. Однако большинство структур являются простыми агрегатами других типов, поэтому, если ваш тип не требует пользовательского поведения при создании, копировании, перемещении или уничтожении экземпляра, вы можете синтезировать необходимые вам основные методы жизненного цикла (и сэкономить некоторое время), используя декоратор @fieldwise_init (описанный в разделе Определение структуры), и копируемые и перемещаемые свойства (описаны в разделе Создание структуры, доступной для копирования и перемещения).