Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 641
javed Avatar asked Jan 17 '17 11:01

javed


People also ask

How do you set a class attribute in Python?

Use dot notation or setattr() function to set the value of a class attribute. Python is a dynamic language. Therefore, you can assign a class variable to a class at runtime. Python stores class variables in the __dict__ attribute.

What is @property in Python?

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is: property(fget=None, fset=None, fdel=None, doc=None)


2 Answers

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.

like image 194
Tagc Avatar answered Oct 07 '22 23:10

Tagc


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.

like image 22
jak44 Avatar answered Oct 08 '22 00:10

jak44