Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define lazy variable in Python which will raise NotImplementedError for abstract code skeleton?

Tags:

python

I want to define some constants in class which will be define in class instance (derived classes) - how to signal error if this variables is not redefine in children classes? I want to raise NotImplementedError on first read.

class Parent(object):
   abstract_variable = ?

   # I want achieve same behavior for variable
   def abstract_function(self):
     raise NotImplementedError()

class Child(Parent):
   def __init__():
     # should throw NotImplementedError() on read
     print self.abstract_variable

Is it possible to do it in one line?

abstract_variable = ?
like image 479
Chameleon Avatar asked Sep 12 '15 07:09

Chameleon


2 Answers

First of all, the clearest would be to not do anything in the parent class. Then when reading you'd just get an attribute error:

AttributeError: Child instance has no attribute 'abstract_variable'

Or in the parent class you can have a property which raises NotImplementedError and overwrite it with a property with getter and setter in each child class; or in the child class set the value to None in the class body...


However if you want to raise NotImplementedError, you can make a non-data descriptor (that is, a descriptor class without __set__, a property always has __set__). This allows you to set the value in the subclass.

The most straightforward way to do it is

class abstract_attribute(object):
    def __get__(self, obj, type):
        raise NotImplementedError("This attribute was not set in a subclass")

And you use it like

class Parent(object):
    variable = abstract_attribute()

class Child(Parent):
    def __init__(self):
        try:
            print(self.variable)
        except Exception as e:
            print("Got error %s" % e)

        self.variable = 42
        print(self.variable)

Child()

Which outputs

Got error This attribute was not set in a subclass
42

A property does not make it possible to set the value as easily as with my abstract_attribute.


But wait, we can make it a bit more magic: the descriptor can find out which attribute it was accessed from:

class abstract_attribute(object):
    def __get__(self, obj, type):   
        # Now we will iterate over the names on the class,
        # and all its superclasses, and try to find the attribute
        # name for this descriptor
        # traverse the parents in the method resolution order
        for cls in type.__mro__:
            # for each cls thus, see what attributes they set
            for name, value in cls.__dict__.items():
                # we found ourselves here
                if value is self:
                    # if the property gets accessed as Child.variable,
                    # obj will be done. For this case
                    # If accessed as a_child.variable, the class Child is 
                    # in the type, and a_child in the obj.
                    this_obj = obj if obj else type

                    raise NotImplementedError(
                         "%r does not have the attribute %r "
                         "(abstract from class %r)" %
                             (this_obj, name, cls.__name__))

        # we did not find a match, should be rare, but prepare for it
        raise NotImplementedError(
            "%s does not set the abstract attribute <unknown>", type.__name__)

With this code, accessing self.variable raises the exception with a very informative message:

NotImplementedError: <__main__.Child object at 0x7f7c7a5dd860> does not
have the attribute 'variable' (abstract from class 'Parent')

and Child.variable gives

NotImplementedError: <class '__main__.Child'> does not have the 
attribute 'variable' (abstract from class 'Parent')
like image 171

Please see Antti Haapala's answer, he provides a better answer.

Use the property function as a decorator

class Parent(object):
   @property
   def abstract_variable(self):
     raise NotImplementedError()

Getting instance.abstract_variable will then throw a NotImplementedError.

like image 32
DanielB Avatar answered Sep 26 '22 19:09

DanielB