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
}
}
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
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())
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
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
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())
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.
Top comments (0)