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: pass class Rectangle(NamedTuple, CustomShape): length: int width: int class Triangle(NamedTuple, CustomShape): a: int b: int c: int @singledispatch def calculate_perimeter(shape: CustomShape): raise NotImplementedError() @calculate_perimeter.register def _(shape: Rectangle): return shape.length + shape.width @calculate_perimeter.register def _(shape: Triangle): return shape.a + shape.b + shape.c class Shape: def __init__(self, shape: CustomShape): self.shape = shape @cached_property def perimeter(self): return calculate_perimeter(self.shape) class Circle(NamedTuple, CustomShape): radius: int @calculate_perimeter.register def _(shape: Circle): return 2 * 3.14 * shape.radius if __name__ == '__main__': test = TestCase() test.assertEqual( Shape(Triangle(1, 1, 1)).perimeter, 3 ) test.assertEqual( Shape(Rectangle(width=1, length=1)).perimeter, 2 ) test.assertAlmostEqual( Shape(Circle(1)).perimeter, 6.28 ) with test.assertRaises(NotImplementedError) as _: # PyCharm and mypy are going to complain in advance! pass Shape((1, 2, 3)).perimeter
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.