Search code examples
pythonpython-3.xclassabstract-classabc

Implement abstract properties without redefining __init__ in child


I am learning the abc module and was wondering if what I want to do is possible.

Essentially, every child I make of the base class should have the SAME exact __init__. The base class will have a few abstract properties that will need to be defined by the child. I would to define those abstract properties without having to rewrite the entire __init__ every time.

Example:

I initially tried something like this

from abc import ABC,abstractmethod

class test(ABC):

    def __init__(self):
       pass

    @property
    @abstractmethod
    def prop(self):
        pass

class prop_ex(test):

    @property
    def prop(self):
        return "THIS WORKS"
>>> from abc_tests import prop_ex
>>> blah = prop_ex()
>>> blah.prop
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'prop_ex' object has no attribute 'prop'

it didn't work.

Then I tried

from abc import ABC,abstractmethod

class test(ABC):

    def __init__(self):
        self.prop = prop

    @property
    @abstractmethod
    def prop(self):
        pass

class prop_ex(test):
    prop = "THIS WORKS"

    @property
    def prop(self):
        return self._prop

Test

>>> from abc_tests import prop_ex
>>> blah = prop_ex()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "abc_tests.py", line 13, in __init__
    self.prop = prop
NameError: name 'prop' is not defined

No good either, so then I tried

from abc import ABC,abstractmethod

class test(ABC):

    def __init__(self):
        pass

    @property
    @abstractmethod
    def prop(self):
        pass

class prop_ex(test):
    self.prop = "THIS WORKS"

    @property
    def prop(self):
        return self._prop

test

>>> from dunder_tests import prop_ex
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "abc_tests.py", line 39, in <module>
    class prop_ex(test):
  File "abc_tests.py", line 40, in prop_ex
    self.prop = "THIS WORKS"
NameError: name 'self' is not defined

For the last one if you set a breakpoint in __init__ of the parent and do a dir(self) you'll see 'prop' in it.

>>> blah = prop_ex()
> abc_tests.py(14)__init__()
-> self.prop = prop
(Pdb) dir(self)
['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', 'prop']

So I thought that would work.

Edit:

I see I am completely over complicating this. I could have simply did

from abc import ABC,abstractmethod

class test(ABC):

    def __init__(self):
        pass

    @property
    @abstractmethod
    def prop(self):
        pass

class prop_ex(test):

    prop = "THIS WORKS"

Is there an issue with doing it this way?


Solution

  • As you've correctly summized, the most straight-forward solution would be to define an abstractproperty and simply let the subclass define it, no need for __init__ at all.

    I would go a step further and throw an Exception in the base-class just to make sure there's no stray super() call on it.

    import abc
    
    class test(abc.ABC):
        @property
        @abc.abstractmethod
        def prop(self):
            raise NotImplementedException()
    
    class prop_ex(test):
        prop = "THIS WORKS"
    

    By the way: Your first example already worked for me. The other two don't work because the second approach tries to find a local variable called prop which doesn't exist and the third approach references self in the class body - but self is only defined in methods/properties of a class, not in the class body.


    Also this doesn't prevent __init__ from being overridden in subclasses, any subclass may still modify the __init__.