Search code examples
pythoninheritanceabstract-class

@classmethod with Abstract Base Class


I have an Abstract Base Class and subclasses defined as follows (Python 2.7):

import abc
import MyDatabaseModule

class _DbObject(object):
    __metaclass__ = abc.ABCMeta

    def _GetObjectType(self):
        raise NotImplementedError, "Please override in the derived class"

    ObjectType = abc.abstractproperty(_GetObjectType, None)

class Entry(_DbObject):
    _objectTypeID = 'ENTRY'

    def _GetObjectType(self):
        return MyDatabaseModule.DoesSomethingWith(self._objectTypeID)

    ObjectType = property(_GetObjectType, None)

This works fine, meaning that the base class _DbObject cannot be instantiated because it has only an abstract version of the property getter method.

try:
    dbObject = _DbObject()
    print "dbObject.ObjectType: " + dbObject.ObjectType
except Exception, err:
    print 'ERROR:', str(err) 

Now I can do:

entry = Entry()
print entry.ObjectType

to get access to the ObjectType property. However, what I would like to be able to do is just:

print Entry.ObjectType

However, wherever I try to insert @classmethod, I get the error classmethod object is not callabale.


Solution

  • So, the magic for the way "property" works in Python is implemented using the descriptor protocol - property itself if a powerful built-in that provides a descriptor that works well for instances, not classes as you had seen.

    So, you need a "class property" - the property built-in can't give you that, but the descriptor protocol can. What the descriptor protocol says is that whenever an attribute is retrieved from the class, if it is an object with a __get__ method, that method is called with "self, instance, owner" - and if it is retrieved from the class, instead of from an instance, the "instance" parameter is set to None.

    BTW, as stated by @Constantinius, this does not have to do with the ABC's at all, just with you wanting a "class property".

    class classproperty(object):
        def __init__(self, func):
            self.func = func
        def __get__(self, instance, owner):
            return self.func(owner)
    
    
    class Entry(_DbObject):
        _objectTypeID = 'ENTRY'
    
        def _GetObjectType(cls):
            return MyDatabaseModule.DoesSomethingWith(cls._objectTypeID)
        ObjectType = classproperty(_GetObjectType, None)