Javed
Javed

Reputation: 6239

How to enforce a child class to set attributes using abstractproperty decorator in python?

I want to enforce a Child class to set several attributes(instance variables), to achieve I am using abstractproperty decorator in python 2.7. Here is the sample code:

from abc import ABCMeta, abstractproperty
class Parent(object):
    __metaclass__ = ABCMeta
    @abstractproperty
    def name(self):
        pass

class Child1(Parent):
    pass

class Child2(Parent):
    name = 'test'

class Child3(Parent):
    def __init__(self):
        self.name = 'test'

obj_child1 = Child1()

Child1 object creation gives an error as expected.

obj_child2 = Child2()

Child2 object creation works fine as because abstract attribute 'name' is set

However

 obj_child3 = Child3()

gives TypeError: Can't instantiate abstract class Child3 with abstract methods name

I did not understand:although the attribute 'name' is set in the init method, why the Child3 object creation is throwing type error.

My requirement is set attributes inside a method of child class. An explanation of what is wrong with child2 class and if there is a better way to enforce a child class to set attributes in a method will help me. Thanks in advance.

Upvotes: 11

Views: 5569

Answers (3)

jak44
jak44

Reputation: 91

A simpler solution will be

class Parent(object):
    name = None
    def __init__(self):
        if self.name == None:
            raise NotImplementedError('Subclasses must define bar')

class Child1(Parent):
    pass

class Child2(Parent):
    name = 'test'

class Child3(Parent):
    def __init__(self)
        self.name = 'test'

obj1 = Child1() raises NotImplementedError

obj2 = Child2() works fine

obj3 = Child3() works fine. This is what you need to enforce a child class to set attribute name and set the attribute from a method.

Upvotes: 9

Abdullah Yousef
Abdullah Yousef

Reputation: 46

You can do some like this, parent class:

class Parent(object):
    __metaclass__ = ABCMeta
    @abstractproperty
   def name(self):
       pass

Child class:

class Child(Parent):
    name = None
    def __init__(self):
        self.name = 'test'

Now

obj = Child() obj.name

gives the required output of 'test'.

By doing this you can enforce a class to set an attribute of parent class and in child class you can set them from a method. It is not a perfect solution that you require, but it is close. The only problem with this solution is you will need to define all the abstractproperty of parent as None in child class and them set them using a method.

Upvotes: 3

Tagc
Tagc

Reputation: 9072

Here's what I believe is happening.

When you try to instantiate Child1, you have not provided an implementation for the abstract property name. You may have done so like this:

class Child1(Parent):
    @property
    def name(self):
        return "foo"

Since the class does not provide an implementation for all abstract methods inherited from its parents, it is effectively an abstract class itself, and therefore cannot be instantiated. Trying to instantiate it gives you your TypeError.

In the case of Child2, you define it as:

class Child2(Parent):
    name = 'test'

In this case, you're overwriting the name property of the class so that it's no longer referencing an abstract property that needs to be implemented. This means that Child2 is not abstract and can be instantiated.

One difference I noticed was that when name = 'test' as you've implemented it, vars(Child2) returns output like this:

{..., '__abstractmethods__': frozenset(), ..., 'name': 'test'}

However, when you change this to something like foo = 'test', you get this:

{..., '__abstractmethods__': frozenset({'name'}), ..., 'foo': 'test'}

Notice that the __abstractmethods__ property is an empty set in the case that you define name = 'test' i.e. when Child2 can be instantiated.

Finally, in the case of Child3 you have to bear in mind that the abstract property is stored as a property of the class itself, which you don't redefine anywhere.

As soon as you try to create an instance of it, the interpreter will see that it's missing an implementation for at least one abstract method and throw the TypeError you see. It doesn't even reach the assignment self.name = 'test' in the constructor.


To answer your second part about how to enforce children to always provide an implementation for abstract properties/methods when they can do something like you did - I'm not actually sure if it's possible. Python is a language of consenting adults.

Upvotes: 5

Related Questions