DEV Community

Pavel Morava
Pavel Morava

Posted on

How to use single dispatch to compose your class

Originally written as a comment under Factory Pattern & Abstraction in Python

This is not about avoiding patterns or dismissing SOLID principles as useless. This is about realizing how dangerous is to mindlessly copy-paste Java code in Python. I gave up on three books about design patterns in Python because all of them just poorly translated Java to Python. Horrible! Most of their construction didn't make any sense because Java, especially the old one, is a different language based on pure OOP.

For instance, a static method. In Java, this is a necessity, in Python one has modules and functions. By the way, this is exactly how C# translates F# code involving functions and modules. By static classes and static methods.

Allow me to offer you a simple example of code, which is almost completely divided into datatypes (represented by NamedTuple, or I could've used dataclasses) and functions. Note I have added Circle after class Shape was declared. I could do that much later, I just need to import function calculate_perimeter and register it to new Shape.

The code is short and not redundant, at least in my opinion. By the way, tested in Python 3.8.


I rewrote the code to achieve a new typesafe version. PyCharm and mypy are going to complain immediately if you try something funny with your types. Unfortunately, I had to introduce dummy type CustomShape to achieve that.

If you know have better idea, please let me know.

from functools import singledispatch, cached_property
from typing import NamedTuple
from unittest import TestCase

class CustomShape:

class Rectangle(NamedTuple, CustomShape):
    length: int
    width: int

class Triangle(NamedTuple, CustomShape):
    a: int
    b: int
    c: int

def calculate_perimeter(shape: CustomShape):
    raise NotImplementedError()

def _(shape: Rectangle):
    return shape.length + shape.width

def _(shape: Triangle):
    return shape.a + shape.b + shape.c

class Shape:
    def __init__(self, shape: CustomShape):
        self.shape = shape

    def perimeter(self):
        return calculate_perimeter(self.shape)

class Circle(NamedTuple, CustomShape):
    radius: int

def _(shape: Circle):
    return 2 * 3.14 * shape.radius

if __name__ == '__main__':
    test = TestCase()
        Shape(Triangle(1, 1, 1)).perimeter, 3
        Shape(Rectangle(width=1, length=1)).perimeter, 2
        Shape(Circle(1)).perimeter, 6.28
    with test.assertRaises(NotImplementedError) as _:
        # PyCharm and mypy are going to complain in advance!
        Shape((1, 2, 3)).perimeter

Enter fullscreen mode Exit fullscreen mode

If you don't mind to step out of Python, I would recommend reading articles from S. Wlaschin here. It can help you understand better the type system.

Discussion (0)