0

I need to define a number of classes that have some common data and behavior. To avoid code duplication, I inherit all of them from a base class and put all common stuff into the base class. The base class itself inherits from a class that is part of an external package I use.

The base class will not represent any real object, it is just a collection of what is common between its children. So I want to make the base class abstract. I have to define the MergeMetas class below because the external class Transport itself has an abstract class defives from a metaclass (different from ABC).

This is what I came up with (not my real classes of course, just to demonstrate):

from some_external_package import Transport
from abc import ABC, abstractmethod


class MergeMetas(type(ABC), type(Transport)): pass


class UnrealCar(Transport, metaclass=MergeMetas):
    @abstractmethod
    def __init__(self, a, b, c):

    # process a, b and c

    @abstractmethod
    def do_something(self):
        # do some common stuff

    @abstractmethod
    def do_something_specific(self):
        """
        All child classes have to implement this method but there's nothing in common
        """
        raise NotImplementedError


class Volvo(UnrealCar):
    def __init__(self, a, b, c):
        super().__init__(a, b, c)

    def do_something(self):
        super().do_something()
        # do some specific staff

    def do_something_specific(self):
        # do some really specific staff


class Opel(UnrealCar):
    def __init__(self, a, b, c, d):
        super().__init__(a, b, c)
        # proceed d

    def do_something(self):
        super().do_something()
        # do some specific stuff

    def do_something_specific(self):
        # do some really specific stuff

Is my approach correct for the problem stated in the first part?

The __init__ method will have several parameters, some of them will be common for all children, others can be specific. Common parameters will be passed to the base class' __init__. Knowing this, do I have to repeat arguments a, b, and c each time or rather go for *args and **kwargs? I read in some places using keyword args is not advised.

Eka AW
  • 231
  • 1
  • 9
  • `class UnrealCar(Transport, ABC):` should be more than sufficient. There is no need to "premerge" the metaclasses. – chepner Mar 17 '23 at 13:27
  • As far as naming, a `Volvo` is not a kind of `UnrealCar`; your abstract class should just be called `Car`. – chepner Mar 17 '23 at 13:28
  • See https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ for advice on how to handle `__init__` correctly in an inheritance hierarchy. – chepner Mar 17 '23 at 13:28
  • 1
    @chepner, it won't work, I get an error if I just inherit from both Transport and ABC. Similar to described here: https://stackoverflow.com/questions/11276037/resolving-metaclass-conflicts Class name does not really matter, in my real case the base class corresponds to no real object hence the "Unreal". – Eka AW Mar 17 '23 at 13:40
  • 1
    Hm, OK. You should, though, be able to use `abc.ABCMeta` directly, rather than using `type(ABC)`. (`ABC` itself is little more than an empty class that uses `metaclass=ABCMeta`, intended to allow making an abstract class via inheritance rather than explicit use of the `metaclass` argument.) – chepner Mar 17 '23 at 13:46
  • Right, ABCMeta works fine, thanks! – Eka AW Mar 17 '23 at 13:49

1 Answers1

0

First of all: check if you really need the "abstract class" functionality in your project. It is a nice way to check at runtime that no methods that should be defined in subclasses are missing - but it will just error at runtime (at which point, the missing methods would error anyway, just a bit later, and with a different message. The ideal scenario would be missing methods to error before runing the code, which can be achieved through testing or static type checking (using Protocols)).

If you want that anyway, them creating the "MergeMeta" is the way to go but the other metaclass you are inheriting from (type(Transport)) has to be built in a way it can work colaboratively with other metaclasses. Leaving it second in your base order, like you do, ensures everything works even if the other class' __new__ method does not use super - but still,it could feature a non-colaborative __call__ or a special __prepare__ which could make it simply not work along with ABCMeta. (that would be an exception, but the only way to know for sure is checking its source code - and writing tests to cover corner cases, as future updates of that package could change the way it works).

Now that the worries are taken out of the way: you can certainly use kwargs in inheritance of a class which you don't control - Python named parameter design works so that your methods in the derived classes will pull its parameters in an elegant way, while not having to worry about the named parameters to the superclass (not counting name-clashes). Positional parameters, on the other hand, could be avoided in this scenario - or at least, keep they as they are needed by the superclass.

So,

class A(Transport):
     def __init__(self, a0_param, a1_param=None, **kwargs):
         # code to consume and bind a0_param and a1_param  
         ...
         super().__init__(**kwargs)

class B(A):
class A(Transport):
     def __init__(self, b0_param, **kwargs):
         # code to consume and bind b0_param  
         ...
         super().__init__(**kwargs)
jsbueno
  • 99,910
  • 10
  • 151
  • 209