Abstract properties in Python's abstract base classes: good practices

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

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