Rust style method definition in Python
Tags: python, rust, oop
Published: 2023-07-14 20:59:21

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.

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

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
```

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.

```python
def impl(cls):
    def wrapper(fn):
        setattr(cls, fn.__name__, fn)
        return fn
    return wrapper
```

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

```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())
```

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.

```python
def impl(cls: T, condition: bool=True):
    def wrapper(fn):
        if condition:
            setattr(cls, fn.__name__, fn)
        return fn
    return wrapper
```

Note that the above condition is executed when a method is being defined, and not when the method is called.
```python
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
```

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

```python
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())
```
---

## 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.