153

What is the shortest / most elegant way to implement the following Scala code with an abstract attribute in Python?

abstract class Controller {

    val path: String

}

A subclass of Controller is enforced to define "path" by the Scala compiler. A subclass would look like this:

class MyController extends Controller {

    override val path = "/home"

}
deamon
  • 89,107
  • 111
  • 320
  • 448
  • 1
    What have you tried? Please post your Python code with any problems or question you have about your solution. – S.Lott Apr 29 '10 at 09:54
  • "A subclass of Controller is enforced to define "path" by the Scala compiler." ... Enforced when? If it's compile time, you're out of luck. If it's runtime, then how exactly do you want it "enforced"? In other words, is there a difference between raising an AttributeError and a NotImplementedError? Why? – detly Apr 29 '10 at 10:38
  • 4
    I know that Python is a dynamic language and that the python interpreter cannot enforce static types. It is important to me, that it fails as early as possibly and that it is easy to find the place where the error orrured and why. – deamon Apr 29 '10 at 11:30
  • Related: http://stackoverflow.com/questions/1151212/equivalent-of-notimplementederror-for-fields-in-python – guettli Jun 16 '16 at 14:33
  • 2
    There are some relevant answers to a newer duplicate: https://stackoverflow.com/questions/23831510/abstract-attribute-not-property, but basically the takeaway is that as of python 3.8 there is no nice solution. – Janus Jul 04 '19 at 13:25

13 Answers13

168

Python 3.3+

from abc import ABCMeta, abstractmethod


class A(metaclass=ABCMeta):
    def __init__(self):
        # ...
        pass

    @property
    @abstractmethod
    def a(self):
        pass

    @abstractmethod
    def b(self):
        pass


class B(A):
    a = 1

    def b(self):
        pass

Failure to declare a or b in the derived class B will raise a TypeError such as:

TypeError: Can't instantiate abstract class B with abstract methods a

Python 2.7

There is an @abstractproperty decorator for this:

from abc import ABCMeta, abstractmethod, abstractproperty


class A:
    __metaclass__ = ABCMeta

    def __init__(self):
        # ...
        pass

    @abstractproperty
    def a(self):
        pass

    @abstractmethod
    def b(self):
        pass


class B(A):
    a = 1

    def b(self):
        pass
Asclepius
  • 57,944
  • 17
  • 167
  • 143
Wtower
  • 18,848
  • 11
  • 103
  • 80
  • My "A" class is a subclass of Exception, and this appears to break the example. – Chris2048 Nov 13 '18 at 11:11
  • 1
    @Chris2048 how does it break it? what error are you getting? – joel Jan 28 '19 at 14:54
  • Is there a way to write this in a Python 2/3 agnostic way similar to [this question](https://stackoverflow.com/questions/35673474/using-abc-abcmeta-in-a-way-it-is-compatible-both-with-python-2-7-and-python-3-5)? – bluenote10 Feb 26 '19 at 10:10
  • 23
    Your python 3 solution implements `a` as a class-property of `B`, instead of an object property. If you instead do ``` class B(A): def __init__(self): self.a=1 ... ``` you get an error when instantiating `B`: "Can't instantiate abstract class B with abstract methods a" – Janus Jul 04 '19 at 13:19
  • @Janus makes an important point ... being able to `class MyController(val path: String) extends Controller` is a valuable option available in the scala version but missing here – joel Jul 08 '19 at 14:21
  • I don0t know why I don't get any error when creating class B without property `a` . `class B(A): def b(self): pass` – DaniTeba Feb 23 '21 at 09:22
  • https://stackoverflow.com/questions/66330411/my-abstractclass-contructor-do-not-raise-and-error-not-error-when-missing-proper – DaniTeba Feb 23 '21 at 09:30
  • 4
    This doesn't pass typechecking for me: literal cannot be assigned to declared type 'property' – rjh May 12 '22 at 15:55
114

Python has a built-in exception for this, though you won't encounter the exception until runtime.

class Base(object):
    @property
    def path(self):
        raise NotImplementedError


class SubClass(Base):
    path = 'blah'
JBSnorro
  • 6,048
  • 3
  • 41
  • 62
  • 33
    Specifically, you won't encounter the exception until the attrtibute is accessed, in which case you would have got an AttributeError anyway. – Ben James Apr 29 '10 at 10:16
  • 8
    I think that raising a `NotImplementedError` is more explicit and therefore probably better than leaving it to an `AttributeError`. – blokeley Apr 29 '10 at 12:48
  • Also you can add a message such "Can't instantiate abstract class Base" when raising an exception yourself. – Bastien Léonard Apr 29 '10 at 14:23
  • 16
    Not that this only works if `path` is set directly on the `SubClass`. Given an instance `sc = SubClass()`, if you try to set `sc.path = 'blah'` or have a method that contains something like `self.path = 'blah'` without defining `path` directly on `SubClass`, you will get an `AttributeError: can't set attribute`. – erik Mar 31 '15 at 16:59
  • How to provide the abstract property a default value? – Vikas Tikoo Dec 06 '16 at 19:30
  • 7
    It is not the answer!! This is not instance field. Scala filed in class is instance field because Scala separate the static and instance. – WeiChing 林煒清 Feb 28 '19 at 11:05
  • 3
    should be `raise NotImplementedError()` otherwise you just raise the type as the exception not the exception object itself. – Simmovation Feb 15 '21 at 18:14
  • 4
    @Simmovation see https://stackoverflow.com/a/16707102/308451 – JBSnorro Apr 20 '21 at 18:24
  • above solution using abc module enforces that sub-classes override! – Harry Jul 29 '23 at 04:22
107

Since this question was originally asked, python has changed how abstract classes are implemented. I have used a slightly different approach using the abc.ABC formalism in python 3.6. Here I define the constant as a property which must be defined in each subclass.

from abc import ABC, abstractmethod


class Base(ABC):

    @classmethod
    @property
    @abstractmethod
    def CONSTANT(cls):
        raise NotImplementedError

    def print_constant(self):
        print(type(self).CONSTANT)


class Derived(Base):
    CONSTANT = 42

This forces the derived class to define the constant, or else a TypeError exception will be raised when you try to instantiate the subclass. When you want to use the constant for any functionality implemented in the abstract class, you must access the subclass constant by type(self).CONSTANT instead of just CONSTANT, since the value is undefined in the base class.

There are other ways to implement this, but I like this syntax as it seems to me the most plain and obvious for the reader.

The previous answers all touched useful points, but I feel the accepted answer does not directly answer the question because

  • The question asks for implementation in an abstract class, but the accepted answer does not follow the abstract formalism.
  • The question asks that implementation is enforced. I would argue that enforcement is stricter in this answer because it causes a runtime error when the subclass is instantiated if CONSTANT is not defined. The accepted answer allows the object to be instantiated and only throws an error when CONSTANT is accessed, making the enforcement less strict.

This is not to fault the original answers. Major changes to the abstract class syntax have occurred since they were posted, which in this case allow a neater and more functional implementation.

James
  • 1,180
  • 1
  • 7
  • 8
  • 1
    this appears to be the only correct answer which also appeases `mypy` – anthony sottile Jan 31 '19 at 21:49
  • 1
    Thank you for one of the only answers that *actually* addresses the question. Can I ask what the purpose of the `print_constant` method is? As far as I can tell it doesn't do anything? – Brian Aug 27 '19 at 18:11
  • 1
    Here you can't call it without instantiating the Derived class. Thus no difference if you remove @classmethod decorator – zhukovgreen Sep 06 '19 at 13:25
  • 1
    I had looked up this question before and never noticed your answer. It should be the accepted one, if I had known this was possible to stack `@property` and `@classmethod` decorators I would've used that long ago. Thank you – Valentin B. Jan 23 '20 at 16:45
  • what if you want to initialize `CONSTANT` according to user input? – pcko1 Jan 27 '20 at 21:14
  • 4
    @BrianJoseph - The `print_constant` method is there to illustrate how to access the constant in the parent class, given that it is only defined in the child class – James Feb 22 '20 at 05:33
  • *Since this question was originally asked, python has changed how abstract classes are implemented.* – could you please specify what change do you refer to here? – Piotr Dobrogost Jul 17 '20 at 08:02
  • 2
    What is the point of including the `@classmethod` decorator? Isn't this unnecessary? – YPCrumble Sep 02 '20 at 14:46
  • I thought that by making it a `@classmethod`, it'd essentially be an abstract class attribute. However, its definition in subclasses is not enforced if accessed on the type rather than an instance of the subclass - the former just returns a property object. To get such behaviour, `__init_subclass__` would be more appropriate. – bgfvdu3w Oct 16 '20 at 23:24
  • 1
    If you add a return type declaration to the definition of `Base.CONSTANT` then mypy will also check that the subclass uses the right type for the definition of `CONSTANT`. – Florian Brucker Nov 06 '20 at 15:39
  • be aware, throwing TypeError when instantiated only happens if the Python compiler/interpreter supports it, so e.g. it does work with CPython (tested with `GCC 9.3.0`), but may not work on some others (I know of at least 1 online testing page when this does not throw an error) – jave.web Jan 29 '21 at 14:29
  • 3
    `self.CONSTANT` will do as well, instead of `type(self).CONSTANT`. – Jonathan Scholbach Dec 17 '21 at 03:05
  • 2
    FYI: due to the way the decorators interact, `@classmethod` should be on top of `@property` – Mason3k Jan 11 '22 at 18:24
  • Thanks @Mason3k - I made an edit so now `@classmethod` precedes `@property` – James Feb 25 '22 at 03:53
  • 1
    What is the point of returning `NotImplementedError`? Why not raise it or return `NotImplemented`(returning `NotImplemented` also has no effect, I think)? – Boseong Choi Mar 31 '22 at 09:08
  • Thank you Boseong, you are right that raising the error is the correct thing to do. I will edit the answer to reflect this. Regarding the option of returning `NotImplemented`, my understanding is that this is only done for comparison operators. – James Mar 31 '22 at 14:42
  • I can confirm what @JonathanScholbach said: `type(self).CONSTANT` is not needed, just use `self.CONSTANT`. @James did we miss anything here? If not, you could improve the answer even more :) – NicoHood Dec 03 '22 at 17:19
  • 5
    As a note, [stacking `@classmethod` and `@property` seems to be deprecated as of Python 3.11](https://docs.python.org/3.11/library/functions.html#classmethod) (and yes, it was just introduced in Python 3.9). See [this answer on a different question](https://stackoverflow.com/a/64738850/8754471) for a bit of discussion and some relevant links. – mitchnegus Mar 12 '23 at 19:00
75

In Python 3.6+, you can annotate an attribute of an abstract class (or any variable) without providing a value for that attribute.

from abc import ABC

class Controller(ABC):
    path: str

class MyController(Controller):
    def __init__(self, path: str):
        self.path = path

This makes for very clean code where it is obvious that the attribute is abstract.

It should be noted that this will not raise an exception at definition time if a subclass does not provide an implementation. However, an AttributeError exception will be raised if anythhing tries to access the undefined attribute.

drhagen
  • 8,331
  • 8
  • 53
  • 82
  • 1
    In Python 3.7 i dont get any errors if MyController is define without path property. Why? – DaniTeba Feb 23 '21 at 09:20
  • 12
    @DaniTeba This solution raises and error at access time, not definition time. You would have to use one of the decorator solutions if raising the error at class definition time is important to you. – drhagen Feb 23 '21 at 13:40
  • Thanks, this is a great solution that passes type-checking too. – rjh May 12 '22 at 15:57
  • 6
    This is not appropriate. These are completely different categories of attributes. `Controller.path` is an attribute of the class, this shared among instances (equivalent to `static` attributes in `C#`). `MyController().path` is an atribute of the instance. – Eduardo Pignatelli Dec 19 '22 at 14:21
  • 1
    @EduardoPignatelli Are you sure? I'm using Python 3.9 with the example above, and I can see that `path` is an _annotation_ of the `Controller` class (in `Controller.__dict__`), but trying to use `Controller.path` is an attribute error: `AttributeError: type object 'Controller' has no attribute 'path'`. Even if the `path` assignment was removed in the `MyController` init method, instances of it still wouldn't have the `path` attribute and would raise the same attribute error – Bilbottom Aug 24 '23 at 16:55
29

You could create an attribute in the abc.ABC abstract base class with a value such as NotImplemented so that if the attribute is not overriden and then used, a clear error that expresses intent is shown at run time.

The following code uses a PEP 484 type hint to help PyCharm correctly statically analyze the type of the path attribute as well.

from abc import ABC

class Controller(ABC):
    path: str = NotImplemented

class MyController(Controller):
    path = "/home"
phoenix
  • 7,988
  • 6
  • 39
  • 45
  • 2
    Note this is not instance field. The Scala field is always instance member in the class definition. Because Scala separate the static and instance. – WeiChing 林煒清 Feb 28 '19 at 11:01
  • 1
    Note this is abusing `NotImplemented` that is there for a different purpose. This was my idea too, so I kind of approve though ^^ I think @drhagen's answer is more correct, but using `= NotImplemented` is way more visible. – Ctrl-C Apr 23 '21 at 18:35
  • 1
    Correct me if I'm wrong, but even without this, if an attribute is used and not overriden or defined, it will throw an error. It's basic behaviour for any class. – Corel May 25 '21 at 21:18
  • @Corel the idea is that this makes the intent very explicit and discoverable. – phoenix May 26 '21 at 12:19
24

For Python 3.3+ there's an elegant solution

from abc import ABC, abstractmethod
    
class BaseController(ABC):
    @property
    @abstractmethod
    def path(self) -> str:
        ...

class Controller(BaseController):
    path = "/home"


# Instead of an elipsis, you can add a docstring for clarity
class AnotherBaseController(ABC):
    @property
    @abstractmethod
    def path(self) -> str:
        """
        :return: the url path of this controller
        """

Despite some great answers have already been given, I thought this answer would nevertheless add some value. This approach has two advantages:

  1. ... in an abstract method's body is more preferable than pass. Unlike pass, ... implies no operations, where pass only means the absence of an actual implementation

  2. ... is more recommended than throwing NotImplementedError(...). This automatically prompts an extremely verbose error if the implementation of an abstract field is missing in a subclass. In contrast, NotImplementedError itself doesn't tell why the implementation is missing. Moreover, it requires manual labor to actually raise it.

evgfilim1
  • 116
  • 1
  • 10
Sergei Voitovich
  • 2,804
  • 3
  • 25
  • 33
  • 3
    Your point #2 is not correct. Whether you type `...` or raise `NotImplementedError` or simply `pass` has no impact on the error raised when trying to instantiate an abstract class (or subclass) with unimplemented methods. – Jephron Nov 15 '19 at 19:24
  • 1
    And when you raise NotImplementedError you can pass a string message to tell more detailed information to the caller about why the error is being thrown. – DtechNet Jan 15 '20 at 17:40
  • 2
    Instead of `pass` or an elipsis (`...`) it's better to declare at least a docstring for the abstract method. pylint told me that. – sausix Dec 12 '20 at 12:29
  • Thanks sausix, it's a good point. I'll include it into the answer – Sergei Voitovich Dec 13 '20 at 11:42
22

As of Python 3.6 you can use __init_subclass__ to check for the class variables of the child class upon initialisation:

from abc import ABC

class A(ABC):
    @classmethod
    def __init_subclass__(cls):
        required_class_variables = [
            'foo',
            'bar',
        ]
        for var in required_class_variables:
            if not hasattr(cls, var):
                raise NotImplementedError(
                    f'Class {cls} lacks required `{var}` class attribute'
                )

This raises an Error on initialisation of the child class, if the missing class variable is not defined, so you don't have to wait until the missing class variable would be accessed.

Jonathan Scholbach
  • 4,925
  • 3
  • 23
  • 44
  • `__init_subclass__` of the parent class is called before the subclass is initialised. So if you want to check for properties set in the subclass's `__init__`, this won't work. – anna_hope Nov 22 '19 at 22:42
  • 5
    @notnami That is true. However, the question asks for class variables, not for instance variables which would be set during the `__init__` method. – Jonathan Scholbach Nov 24 '19 at 21:31
  • 5
    Hum... Is it me or are you the only one to strictly address the question ? :) +1. – keepAlive Jul 04 '20 at 10:35
11

I've modified just a bit @James answer, so that all those decorators do not take so much place. If you had multiple such abstract properties to define, this is handy:

from abc import ABC, abstractmethod

def abstractproperty(func):
   return property(classmethod(abstractmethod(func)))

class Base(ABC):

    @abstractproperty
    def CONSTANT(cls): ...

    def print_constant(self):
        print(type(self).CONSTANT)


class Derived(Base):
    CONSTANT = 42

class BadDerived(Base):
    BAD_CONSTANT = 42

Derived()       # -> Fine
BadDerived()    # -> Error

dankal444
  • 3,172
  • 1
  • 23
  • 35
  • The one line `abstractproperty` looks very useful, but it gives me an error on Python 3.10 and Pylance: Argument of type "classmethod[Unknown]" cannot be assigned to parameter "fget" of type "((Any) -> Any) | None" in function "__init__" Type "classmethod[Unknown]" cannot be assigned to type "((Any) -> Any) | None" Type "classmethod[Unknown]" cannot be assigned to type "(Any) -> Any" Type cannot be assigned to type "None" – Jan Dolejsi Feb 03 '23 at 08:43
  • @JanDolejsi I have tested it on the [online python 3.11 tool](https://www.programiz.com/python-programming/online-compiler/) and it works. Maybe some python 3.10 bug? – dankal444 Feb 03 '23 at 11:51
7

Python3.6 implementation might looks like this:

In [20]: class X:
    ...:     def __init_subclass__(cls):
    ...:         if not hasattr(cls, 'required'):
    ...:             raise NotImplementedError

In [21]: class Y(X):
    ...:     required = 5
    ...:     

In [22]: Y()
Out[22]: <__main__.Y at 0x7f08408c9a20>
Asclepius
  • 57,944
  • 17
  • 167
  • 143
zhukovgreen
  • 1,551
  • 16
  • 26
  • Not sure this will raise a type error though if your subclasses don't implement the attribute, in case you're using type checking from an IDE – Allen Wang Jun 08 '20 at 21:43
5

Your base class could implement a __new__ method that check for class attribute:

class Controller(object):
    def __new__(cls, *args, **kargs):
        if not hasattr(cls,'path'): 
            raise NotImplementedError("'Controller' subclasses should have a 'path' attribute")
        return object.__new__(cls)

class C1(Controller):
    path = 42

class C2(Controller):
    pass


c1 = C1() 
# ok

c2 = C2()  
# NotImplementedError: 'Controller' subclasses should have a 'path' attribute

This way the error raise at instantiation

OverLordGoldDragon
  • 1
  • 9
  • 53
  • 101
Juh_
  • 14,628
  • 8
  • 59
  • 92
1

Bastien Léonard's answer mentions the abstract base class module and Brendan Abel's answer deals with non-implemented attributes raising errors. To ensure that the class is not implemented outside of the module, you could prefix the base name with an underscore which denotes it as private to the module (i.e. it is not imported).

i.e.

class _Controller(object):
    path = '' # There are better ways to declare attributes - see other answers

class MyController(_Controller):
    path = '/Home'
Brendan
  • 18,771
  • 17
  • 83
  • 114
  • 1
    is it possible to raise some error if the subclass does not redefine the attribute? It would be easy for methods, but how about attributes? – Mario F Apr 29 '10 at 10:08
  • Wouldn't it be better to leave out the path declaration in `_Controller` class? Duck Typing wouldn't take effect if there is already a (invalid) value. Otherwise at some point, where I need the `path` field to be defined, there would be no error because there is already a value. – deamon Apr 29 '10 at 10:25
  • @Mario - yes, Brendan Abel's answer gives a good way to do this – Brendan Apr 29 '10 at 13:07
1
class AbstractStuff:
    @property
    @abc.abstractmethod
    def some_property(self):
        pass

As of 3.3 abc.abstractproperty is deprecated, I think.

william_grisaitis
  • 5,170
  • 3
  • 33
  • 40
-1

Have a look at the abc (Abtract Base Class) module: http://docs.python.org/library/abc.html

However, in my opinion the simplest and most common solution is to raise an exception when an instance of the base class is created, or when its property is accessed.

Brendan
  • 18,771
  • 17
  • 83
  • 114
Bastien Léonard
  • 60,478
  • 20
  • 78
  • 95