This article is part of a series dedicated to comparing Mojo and Python. After the speed comparison in the previous article, we now turn to the comparison of Mojo structs with Python classes.
Mojo Update
In November 2024 Magic was released, a new virtual environment and package manager that replaces the Modular CLI. Mojo’s latest version, 24.5.0, is available through the new Magic package manager. Install Magic and set up your repository according to the guidelines here. If you have an existing repository, like me, just run magic init
inside the repository, update the Mojo version if required, and you can run your Mojo file with magic run mojo your-mojo.mojo
. You can read about the changes brought by Mojo 24.5.0 here.
Python Class vs Mojo Struct
Mojo currently doesn’t support classes like Python, though future updates aim to include them to align with Python’s behavior. Instead, in Mojo we use structs. As the documentation states, a Mojo struct is a data structure that allows you to encapsulate fields and methods that operate on an abstraction, such as a data type or an object. Fields are variables that hold data relevant to the struct, and methods are functions inside a struct that generally act upon the field data. So far, sounds pretty much like a class. However. Python classes are dynamic, meaning you can add or delete attributes/methods of an object at runtime. Mojo’s structs are static, bound at compile-time, so you cannot change it dynamically at runtime. However, Mojo’s structs are faster and memory safe, as they are statically typed and compiled ahead of time. Another difference is that while Python classes may have class attributes, Mojo structs don’t support it. Also, Mojo structs don’t support inheritance either.
Let’s compare a Python class and a Mojo struct side by side.
The Example
You can find the complete code in my GitHub repository dedicated to comparing Mojo with Python.
I created a class/struct that represents a car. Its attributes are brand
, model
, year
and color
. Apart from the constructor, __init__
, and the __str__
magic method, I added two instance methods, get_year
that returns the year, and update_color
that changes the car’s color as per the argument. At last, I implemented a static method that creates a Car
object from a dictionary.
Python:
class Car:
def __init__(self, brand: str, model: str, year: int, color: str):
self.brand: str = brand
self.model: str = model
self.year: int = year
self.color: str = color
def __str__(self) -> str:
return '{} | {} | {} | {}'.format(self.brand, self.model,
self.year, self.color)
def get_year(self) -> int:
return self.year
def update_color(self, color: str):
self.color = color
@staticmethod
def create_from_dict(car_dict: dict) -> Car:
return Car(**car_dict)
Examples of working with Car
objects:
# Honda Civic
car_1 = Car('Honda', 'Civic', 2000, 'green')
print(car_1)
car_1.update_color('grey')
print(car_1)
# Toyota Corolla
toyota_corolla = {'brand': 'Toyota',
'model': 'Corolla',
'year': 2018,
'color': 'black'}
car2 = Car.create_from_dict(toyota_corolla)
print(car2)
print(car2.get_year())
Output:
Honda | Civic | 2000 | green
Honda | Civic | 2000 | grey
Toyota | Corolla | 2018 | black
2018
Now, let’s see the same example using a Mojo struct.
Mojo:
struct Car:
var brand: String
var model: String
var year: Int16
var color: String
fn __init__(inout self, brand: String, model: String, year: Int16,
color: String):
self.brand = brand
self.model = model
self.year = year
self.color = color
fn __str__(self) -> String:
return self.brand + ' | ' + self.model + ' | ' +
str(self.year) + ' | ' + self.color
fn get_year(self) -> Int16:
return self.year
fn update_color(inout self, color: String):
self.color = color
@staticmethod
def create_from_dict(car_dict: Dict[String, String]) -> Car:
return Car(car_dict['brand'], car_dict['model'],
int(car_dict['year']), car_dict['color'])
Examples of working with Car
objects:
# Honda Civic
var car1: Car = Car('Honda', 'Civic', 2000, 'green')
print(str(car1))
car1.update_color('grey')
print(str(car1))
# Toyota Corolla
var toyota_corolla: Dict[String, String] = Dict[String, String]()
toyota_corolla['brand'] = 'Toyota'
toyota_corolla['model'] = 'Corolla'
toyota_corolla['year'] = '2018'
toyota_corolla['color'] = 'black'
var car2: Car = Car.create_from_dict(toyota_corolla)
print(str(car2))
print(car2.get_year())
Output:
Honda | Civic | 2000 | green
Honda | Civic | 2000 | grey
Toyota | Corolla | 2018 | black
2018
As I mentioned in previous articles, in Mojo a def
function provides more dynamic features, while fn
is stricter but safer and faster. When it comes to structs, we can still choose to declare methods with def
or fn
, but all of the struct fields must be declared with var
.
Just like in Python, instance methods have self
as their first argument. It refers to the object itself. You can call it anyhow, but self
is the convention. In the constructor and the update_color
method I marked self
as inout
which declares it as a mutable reference.
Mojo’s special methods basically match those in Python, giving it a Pythonic style. In this example I implemented __str__
which is called by str()
and it returns the informal string representation of the object.
The staticmethod
annotation is used in Mojo just like in Python, reminding us again of Mojo’s Pythonic style.
In summary, while Mojo structs share great similarities with Python classes, there are significant differences that set them apart.
This article ends here, I’ll be thinking on the next one soon by the Christmas tree. As the year is ending, try to have some rest and enjoy the holidays.
Good luck on your journey with Mojo and Happy Coding! 🔥🔥🔥
Comments
Post a Comment