How to create abstract properties in python abstract classes

118,884

Solution 1

Since Python 3.3 a bug was fixed meaning the property() decorator is now correctly identified as abstract when applied to an abstract method.

Note: Order matters, you have to use @property above @abstractmethod

Python 3.3+: (python docs):

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

Python 2: (python docs)

from abc import ABC, abstractproperty

class C(ABC):
    @abstractproperty
    def my_abstract_property(self):
        ...

Solution 2

Until Python 3.3, you cannot nest @abstractmethod and @property.

Use @abstractproperty to create abstract properties (docs).

from abc import ABCMeta, abstractmethod, abstractproperty

class Base(object):
    # ...
    @abstractproperty
    def name(self):
        pass

The code now raises the correct exception:

Traceback (most recent call last):
  File "foo.py", line 36, in 
    b1 = Base_1('abc')  
TypeError: Can't instantiate abstract class Base_1 with abstract methods name

Solution 3

Based on James answer above

def compatibleabstractproperty(func):

    if sys.version_info > (3, 3):             
        return property(abstractmethod(func))
    else:
        return abstractproperty(func)

and use it as a decorator

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()

Solution 4

Using the @property decorator in the abstract class (as recommended in the answer by James) works if you want the required instance level attributes to use the property decorator as well.

If you don't want to use the property decorator, you can use super(). I ended up using something like the __post_init__() from dataclasses and it gets the desired functionality for instance level attributes:

import abc
from typing import List

class Abstract(abc.ABC):
    """An ABC with required attributes.

    Attributes:
        attr0
        attr1 
    """

    @abc.abstractmethod
    def __init__(self):
        """Forces you to implement __init__ in 'Concrete'. 
        Make sure to call __post_init__() from inside 'Concrete'."""

    def __post_init__(self):
        self._has_required_attributes()
        # You can also type check here if you want.

    def _has_required_attributes(self):
        req_attrs: List[str] = ['attr0', 'attr1']
        for attr in req_attrs:
            if not hasattr(self, attr):
                raise AttributeError(f"Missing attribute: '{attr}'")

class Concrete(Abstract):

    def __init__(self, attr0, attr1):
        self.attr0 = attr0
        self.attr1 = attr1
        self.attr2 = "some value" # not required
        super().__post_init__() # Enforces the attribute requirement.

Solution 5

In python 3.6+, you can also anotate a variable without providing a default. I find this to be a more concise way to make it abstract.

class Base():
    name: str
    
    def print_name(self):
        print(self.name)  # will raise an Attribute error at runtime if `name` isn't defined in subclass

class Base_1(Base):
    name = "base one"

it may also be used to force you to initialize the variable in the __new__ or __init__ methods

As another example, the following code will fail when you try to initialize the Base_1 class

    class Base():
        name: str

        def __init__(self):
            self.print_name()

    class Base_1(Base):
        _nemo = "base one"
    
    b = Base_1() 

AttributeError: 'Base_1' object has no attribute 'name'

Share:
118,884

Related videos on Youtube

Boris Gorelik
Author by

Boris Gorelik

Updated on May 09, 2022

Comments

  • Boris Gorelik
    Boris Gorelik about 2 years

    In the following code, I create a base abstract class Base. I want all the classes that inherit from Base to provide the name property, so I made this property an @abstractmethod.

    Then I created a subclass of Base, called Base_1, which is meant to supply some functionality, but still remain abstract. There is no name property in Base_1, but nevertheless python instatinates an object of that class without an error. How does one create abstract properties?

    from abc import ABCMeta, abstractmethod
    
    class Base(object):
        __metaclass__ = ABCMeta
        def __init__(self, strDirConfig):
            self.strDirConfig = strDirConfig
        
        @abstractmethod
        def _doStuff(self, signals):
            pass
        
        @property    
        @abstractmethod
        def name(self):
            # this property will be supplied by the inheriting classes
            # individually
            pass
        
    
    class Base_1(Base):
        __metaclass__ = ABCMeta
        # this class does not provide the name property, should raise an error
        def __init__(self, strDirConfig):
            super(Base_1, self).__init__(strDirConfig)
        
        def _doStuff(self, signals):
            print 'Base_1 does stuff'
            
    
    class C(Base_1):
        @property
        def name(self):
            return 'class C'
        
            
    if __name__ == '__main__':
        b1 = Base_1('abc')  
    
    • kevinarpe
      kevinarpe over 9 years
      Gotcha: If you forget to use decorator @property in class C, name will revert to a method.
  • flying sheep
    flying sheep over 11 years
    actually this answer is wrong for younger pythons: since 3.3, @abstractproperty is deprecated in favor of a combination like OP’s.
  • codeape
    codeape over 11 years
  • BoltzmannBrain
    BoltzmannBrain over 6 years
  • Sławomir Lenart
    Sławomir Lenart over 5 years
    so until 3.3, just raise NotImplementedError
  • himanshu219
    himanshu219 almost 5 years
    @James How to make it compatible for python 2 and as well?
  • himanshu219
    himanshu219 almost 5 years
    @James actually I meant for both but nevermind I posted a answer based on your solution
  • santhosh kumar
    santhosh kumar almost 4 years
    can we inherit from object and still use the ABC annotations? Will it work as shown in the example?
  • ierdna
    ierdna almost 3 years
    i don't think python checks that the implementation actually has @property decorator, it just checks that a method with the name my_abstract_property is created.
  • lmiguelvargasf
    lmiguelvargasf over 2 years
    @James, does this work with functools.cached_property?
  • Nav
    Nav about 2 years
    Didn't work for me. Python 3.9.6. Perhaps you missed something?
  • Nav
    Nav about 2 years
    I don't get it. OP is asking about having a property of a base class named name, which has to be implemented by all child classes, but the answers here are implementing abstract functions. Even the abc docs show only functions being abstract. Isn't there a way to make class properties/variables abstract in a way that child classes have to implement them? I need it to have an id variable which all child classes should implement.
  • Gers
    Gers about 2 years
    well, something that might not be clear from my response is that you'll only get the error when you try to access the missing attribute. so you could easily check that in the __init__ method for example. I'll update to my answer to demonstrate that. (by the way, I tested it with Python 3.8.6)
  • Colin D Bennett
    Colin D Bennett almost 2 years
    Maybe this answer is useful, but it's not what the OP question is asking about. You are defining an attribute, not a property.