Question about software architecture. Below there is a snip from kwant
's system.py
and its InfiniteSystem
class, but it is not specific.

I think that implicit definition of abstract properties in mixin/abstract classes is a bad coding practice, confusing for reading the code and when trying to use these mixins in practice (bonus points for confusing my static code analyzer). On the flip side, explicit definition with
@property
@abc.abstractmethod
def cell_size(self):
"""
This docstring is needed for making a tip about the type for your IDE.
Returns
-------
int
A size of the cell
"""
pass
is a bit ugly and oververbose. What would be a good practice for dealing with such complicated inheritance patterns?
1 Like
I think in Python ≥ 3.6 the way to go is attribute annotations:
class InfiniteSystem:
cell_size: int
Note that cell_size
is an attribute, not a property.
The thing I like in the proposed variant of abstract property is the clear error message, if you forget to define it:
import abc
class ColoredText(metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def color(self):
pass
def render(self):
print('rendering text with color {}'.format(self.color))
class Button(ColoredText):
pass
button = Button() # TypeError: Can't instantiate abstract class Button with abstract methods color
Ideally, I would like to have some trick like:
class ColoredText:
color = abstractattribute(int) # Or something similar, inspired by traitlets
def render(self):
print('rendering text with color {}'.format(self.color))
class BlackButton(ColoredText):
def __init__(self):
self.color = 0x000000
class Button(ColoredText):
pass
black_button = BlackButton() # OK
button = Button() # TypeError: Can't instantiate abstract class Button with abstract attribute color
On the design side, it may be better to shift the definition of these attributes into the mixin constructor though, like here, for example:
class Text:
def __init__(self, text):
self.text = text
class ClickableMixin:
def on_click(self):
print("I was clicked!")
class ColoredTextMixin:
def __init__(self, *args, **kwargs):
self.color = kwargs.pop('color')
super().__init__(*args, **kwargs)
def render(self):
print('rendering text with color {}'.format(self.color))
class Button(ColoredTextMixin, ClickableMixin, Text): # order is important!
pass
button = Button('OK', color=0xAAAAAA)
button.on_click()
button.render()
print(button.text)
Then at least the definitions of mixin-relevant stuff does not spread over derivative classes. However, this solution also has some disadvantages, mostly on the level of constructor signatures and documentation.
1 Like