Rust style method definition in Python

zaghaghi

Hamed Zaghaghi

Posted on July 14, 2023

Rust style method definition in Python

It all started when I was browsing Rust documentation and noticed that in Rust language struct methods are defined separately from the struct itself. To understand what I mean, see below example extracted from Rust documentation.

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
Enter fullscreen mode Exit fullscreen mode

What a nice feature! I told to myself. Then I started thinking about this in Python.

Can we do the same in Python?

The short answer is Yes, although we need to write a little bit of code to make this style possible in Python. Here is a simple solution to it with the help of decorators and monkey-patching.

def impl(cls):
    def wrapper(fn):
        setattr(cls, fn.__name__, fn)
        return fn
    return wrapper
Enter fullscreen mode Exit fullscreen mode

And here is a simple usage. dataclass is used here to resemble Rust struct as there is no struct in Python.

from dataclasses import dataclass

@dataclass
class Rectangle:
    width: int
    height: int

@impl(Rectangle)
def area(self: Rectangle) -> int:
    return self.height * self.width

if __name__ == "__main__":
  rect = Rectangle(width=10, height=20)
  print(rect.area())
Enter fullscreen mode Exit fullscreen mode

The definition of area method is now outside of the class definition.


Use cases

I think this style offers a few use cases that can be used in our future Python projects.

Separated Implementation Modules

In some cases, implementing a method requires a few other dependencies which may be large and slow to import. Using this style you can have a class definition and multiple implementation files. Developer only import what they want to use.

Feature Toggles

Using this style, we can have feature toggles to choose between different implementations. Take a look at the following example.

def impl(cls: T, condition: bool=True):
    def wrapper(fn):
        if condition:
            setattr(cls, fn.__name__, fn)
        return fn
    return wrapper
Enter fullscreen mode Exit fullscreen mode

Note that the above condition is executed when a method is being defined, and not when the method is called.

from dataclasses import dataclass
from enum import IntFlag, auto

@dataclass
class Rectangle:
    width: int
    height: int

class FeatureFlag(IntFlag):
    FAST_AREA = auto()

@impl(FeatureFlag)
def read_from_config():
    return FeatureFlag.FAST_AREA
Enter fullscreen mode Exit fullscreen mode

The implementation file has two definition for area method, and the selection between these two is controlled by feature flags.

FLAGS = FeatureFlag.read_from_config()

@impl(Rectangle, FeatureFlag.FAST_AREA in FLAGS)
def area(self: Rectangle) -> int:
    return self.height * self.width

@impl(Rectangle, FeatureFlag.FAST_AREA not in FLAGS)
def area(self: Rectangle) -> int:
    area_value = 0
    for _ in range(self.width):
        area_value += self.height
    return area_value

if __name__ == "__main__":
  rect1 = Rectangle(width=10, height=20)
  print(rect1.area())
Enter fullscreen mode Exit fullscreen mode

Issues

Doing this kind of hacking in Python breaks type checkers like mypy .


I'd like to play with programming languages, and make programming more fun to work with.
It would be greatly appreciated if you could share your thoughts/ideas in the comments bellow.

💖 💪 🙅 🚩
zaghaghi
Hamed Zaghaghi

Posted on July 14, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related