I've written the following code to help enforce the definition of certain methods for a class:
def createClassTemplate(name, requiredMethods=[], inherits=object):
def require(name):
def errorRaiser(self, *args, **kwargs):
raise RequireException("method '{}' must be defined.".format(name))
setattr(wrapper, "__name__", name)
return errorRaiser
class Custom(inherits): pass
setattr(Custom, "__name__", name)
for methodName in requiredMethods:
setattr(Custom, methodName, require(methodName))
return Custom
Which is implemented like this:
Model = createClassTemplate("Model", ["foo", "bar", "baz"])
class MyClass(Model):
pass
This way, when I'm missing a method, the calling class will generate a meaningful error indicating that I've failed to define a required method.
The problem is, the above code seems uncomfortably hacky and unpythonic. Am I going about this right way? Should I even be forcing class templates like this? And is there a better way to accomplish the same thing?
You can use the ABCMeta metaclass:
from abc import ABCMeta, abstractmethod
class MyClass:
__metaclass__ = ABCMeta
#abstractmethod
def foo(self):
pass
Subclasses of MyClass must override foo to be allowed to be instantiated.
You should use a metaclass instead.
The standard library comes with a ready-made metaclass for just this task, the ABCMeta metaclass:
import abc
class Model(object):
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
def foo(self):
pass
#abc.abstractmethod
def bar(self):
pass
#abc.abstractmethod
def baz(self):
pass
class MyClass(Model):
pass
Demonstration:
>>> import abc
>>> class Model(object):
... __metaclass__ = abc.ABCMeta
... #abc.abstractmethod
... def foo(self):
... pass
... #abc.abstractmethod
... def bar(self):
... pass
... #abc.abstractmethod
... def baz(self):
... pass
...
>>> class MyClass(Model):
... pass
...
>>> myclass = MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods bar, baz, foo
Once MyClass does provide implementations of the abstract methods, instantiation succeeds:
>>> class MyClass(Model):
... def foo(self): pass
... def bar(self): pass
... def baz(self): pass
...
>>> myclass = MyClass()
If you're using Python 2.6+, there's the concept of an ABC - http://docs.python.org/2/library/abc.html
It seems to me that you are trying to reinvent the Abstract Base Classes
I think you want something like this:
import abc
class MyBaseClass:
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
def foo(self):
pass
#abc.abstractmethod
def bar(self):
pass
#abc.abstractmethod
def baz(self):
pas
Related
I'm trying to create an abstract class that would force the implementation to implement both a getter and a setter. I'm following what is described at https://docs.python.org/3.4/library/abc.html#abc.abstractproperty but I keep getting
Traceback (most recent call last):
File "test.py", line 30, in <module>
test = B('bar')
TypeError: Can't instantiate abstract class B with abstract methods foo
even though I implement both foo getter and setter in B.
Here is my code:
from abc import ABC, abstractmethod
class A(ABC):
#property
#abstractmethod
def foo(self):
pass
#foo.setter
#abstractmethod
def foo(self, val):
pass
def do_stuff(self):
print(self.foo)
class B(A):
def __init__(self, val):
self._foo = val
#property
def foo(self):
return self._foo
#A.foo.setter
def foo(self, val):
self._foo = val
if __name__ == '__main__':
test = B('bar')
test.do_stuff()
test.foo = 'barr'
test.do_stuff()
I know there is a way to do this by defining the property without using the decorator, like so
from abc import ABC, abstractmethod
class A(ABC):
#abstractmethod
def _get_foo(self):
pass
#abstractmethod
def _set_foo(self, val):
pass
def do_stuff(self):
print(self.foo)
foo = property(_get_foo, _set_foo)
class B(A):
def __init__(self, val):
self._foo = val
def _get_foo(self):
return self._foo
def _set_foo(self, val):
self._foo = val
foo = property(_get_foo, _set_foo)
if __name__ == '__main__':
test = B('bar')
test.do_stuff()
test.foo = 'barr'
test.do_stuff()
While this does work, there surely must be a way to do that with just the decorators, no?
I'm running python 3.4.
Your code raises an exception because you're using the A.foo.setter decorator on the B.foo method, so the B.foo method does not actually implement the abstract method A.foo with the same signature.
Remove the A. specification from the decorator, and your code would work:
#foo.setter
def foo(self, val):
self._foo = val
With the fix, you'll find that the class A does enforce that the child classes implement both the getter and the setter for foo (the exception you saw was actually a result of you not implementing the setter).
According to this issue you would need to go over an additional class in the hierarchy that implements solely the getter. The setter would then stay with the concrete class:
from abc import ABC, abstractmethod
class A(ABC):
#property
#abstractmethod
def foo(self):
pass
#foo.setter
#abstractmethod
def foo(self, val):
pass
def do_stuff(self):
print(self.foo)
class B(A):
_foo = None
#A.foo.getter
def foo(self):
return self._foo
class C(B):
def __init__(self, val):
self._foo = val
#B.foo.setter
def foo(self, val):
self._foo = val
if __name__ == '__main__':
test = C('bar')
test.do_stuff()
test.foo = 'barr'
test.do_stuff()
If you remove either the getter in B or the setter in C you will get the desired TypeError exception.
This solution, however, forces you to put the definitions of getter and setter in two different classes, which might be impractical in real applications.
Problem:
I'd like to generate an abstract class with a few method stubs that should be implemented by its sub-classes thereby avoiding any hard-coding.
That is, if I define a class as follows:
>>> class Sample(SomeClass):
... ACTIONS = ('run', 'sleep', 'kill')
The above class should always implement the methods run, sleep, and kill.
Attempted solution:
The snippet below works as expected and raises an error when the abstract method run is not defined in subclass FooBar.
>>> class Foo(object):
... __metaclass__ = ABCMeta
...
... #abstractmethod
... def run(self, *args, **kwargs):
... pass
>>> class FooBar(Foo):
... def __init__(self, a, b):
... self.a = a
... self.b = b
>>> FooBar(1, 2)
TypeError: Can't instantiate abstract class FooBar with abstract methods run
I was trying to achieve the same by generating the Foo class using type:
>>> Foo2 = type('Foo', (), {'__metaclass__': ABCMeta,
... 'run' : abstractmethod(lambda self, *args, **kwargs: None)})
>>> class FooBar(Foo2):
... def __init__(self, a, b):
... self.a = a
... self.b = b
However, the below call does not raise any error:
>>> FooBar(1, 2)
<__main__.FooBar at ...>
Noticed that __abstractmethods__ doesn't get defined in Foo2 (comparing vars(Foo) and vars(Foo2)).
I did find a similar attempt here, but wasn't able to follow why the above wouldn't work and why I can't make use of ABCMeta directly (which defines __new__ in a generic manner).
Realized I could just do this :)
>>> Foo2 = ABCMeta('Foo', (), {'run': abstractmethod(lambda self, *args, **kwargs: None)})
>>> FooBar(1, 2)
TypeError: Can't instantiate abstract class FooBar with abstract methods run
I have the following snippet:
from abc import abstractproperty
class Base(object):
#abstractproperty
def foo(self):
print 'wat'
class C(Base):
def __init__(self):
self.foo = 'test'
self.bar = 'test2'
c = C()
When I execute it, it gives the stacktrace:
in __init__ AttributeError: can't set attribute
on the line self.foo = 'test'.
Anyone know why this is happening?
First of all, note that you forgot to use ABCMeta as your metaclass. Change your code to:
For Python 2.x:
from abc import ABCMeta
class Base(object):
__metaclass__ = ABCMeta
...
For Python 3.x, any of the following:
from abc import ABC, ABCMeta
class Base(ABC):
...
class Base(metaclass=ABCMeta):
...
And you'll see this nice error:
Traceback (most recent call last):
File "a.py", line 14, in <module>
c = C()
TypeError: Can't instantiate abstract class C with abstract methods foo
It's telling you that you need a concrete implementation for your abstract property.
But that's not enough: to properly have self.foo = 'test' working, you need to implement a concrete setter for your property.
In the end, your code should look like this:
from abc import ABCMeta, abstractproperty
class Base(object):
__metaclass__ = ABCMeta
#abstractproperty
def foo(self):
print 'wat'
class C(Base):
#property
def foo(self):
# do something
#foo.setter
def foo(self, value):
# do something else
def __init__(self):
self.foo = 'test'
self.bar = 'test2'
c = C()
Remember that you can use super() in your concrete property code to use the code from the abstract property.
Note: that since Python 3.3, #abstractproperty has been deprecated. Instead, you should use a combination of #property and #abstractmethod:
class Base(ABC):
#property
#abstractmethod
def foo(self):
...
Your code defines a read-only abstractproperty. See docs on ABC. In addition, you did not set ABCMeta as meta class, which is obligatory. Furthermore, an abstractproperty is abstract which means that it has to be overwritten in the child class.
If you want to create a read-write abstractproperty, go with something like this:
from abc import ABCMeta, abstractproperty
class Base(object):
__metaclass__ = ABCMeta # This is required.
def getfoo(self):
pass # Getter for preprocessing when property 'foo' is requested
def setfoo(self, thefoo):
pass # Setter for preprocessing when property 'foo' is set
foo = abstractproperty(getfoo, setfoo)
class C(Base):
foo = '' # Overwrite abstract property
def __init__(self):
self.foo = 'test'
self.bar = 'test2'
Using the code above, you can instantiate your class C and set its property foo:
c = C()
print(c.foo)
>>> test
How do I create a decorator for an abstract class method in Python 2.7?
Yes, this is similar to this question, except I would like to combine abc.abstractmethod and classmethod, instead of staticmethod. Also, it looks like abc.abstractclassmethod was added in Python 3 (I think?), but I'm using Google App Engine, so I'm currently limited to Python 2.7
Thanks in advance.
Here's a working example derived from the source code in Python 3.3's abc module:
from abc import ABCMeta
class abstractclassmethod(classmethod):
__isabstractmethod__ = True
def __init__(self, callable):
callable.__isabstractmethod__ = True
super(abstractclassmethod, self).__init__(callable)
class DemoABC:
__metaclass__ = ABCMeta
#abstractclassmethod
def from_int(cls, n):
return cls()
class DemoConcrete(DemoABC):
#classmethod
def from_int(cls, n):
return cls(2*n)
def __init__(self, n):
print 'Initializing with', n
Here's what it looks like when running:
>>> d = DemoConcrete(5) # Succeeds by calling a concrete __init__()
Initializing with 5
>>> d = DemoConcrete.from_int(5) # Succeeds by calling a concrete from_int()
Initializing with 10
>>> DemoABC() # Fails because from_int() is abstract
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class DemoABC with abstract methods from_int
>>> DemoABC.from_int(5) # Fails because from_int() is not implemented
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class DemoABC with abstract methods from_int
Note that the final example fails because cls() won't instantiate. ABCMeta prevents premature instantiation of classes that haven't defined all of the required abstract methods.
Another way to trigger a failure when the from_int() abstract class method is called is to have it raise an exception:
class DemoABC:
__metaclass__ = ABCMeta
#abstractclassmethod
def from_int(cls, n):
raise NotImplementedError
The design ABCMeta makes no effort to prevent any abstract method from being called on an uninstantiated class, so it is up to you to trigger a failure by invoking cls() as classmethods usually do or by raising a NotImplementedError. Either way, you get a nice, clean failure.
It is probably tempting to write a descriptor to intercept a direct call to an abstract class method, but that would be at odds with the overall design of ABCMeta (which is all about checking for required methods prior to instantiation rather than when methods are called).
Another possible workaround:
class A:
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
def some_classmethod(cls):
"""IMPORTANT: this is a class method, override it with #classmethod!"""
class B(A):
#classmethod
def some_classmethod(cls):
print cls
Now, one still can't instantiate from A until some_classmethod is implemented, and it works if you implement it with a #classmethod.
You could upgrade to Python 3.
Starting with Python 3.3, it is possible to combine #classmethod and #abstractmethod:
import abc
class Foo(abc.ABC):
#classmethod
#abc.abstractmethod
def my_abstract_classmethod(...):
pass
Thanks to #gerrit for pointing this out to me.
I recently encountered the same problem. That is, I needed abstract classmethods but was unable to use Python 3 because of other project constraints. The solution I came up with is the following.
abc-extend.py:
import abc
class instancemethodwrapper(object):
def __init__(self, callable):
self.callable = callable
self.__dontcall__ = False
def __getattr__(self, key):
return getattr(self.callable, key)
def __call__(self, *args, **kwargs):
if self.__dontcall__:
raise TypeError('Attempted to call abstract method.')
return self.callable(*args,**kwargs)
class newclassmethod(classmethod):
def __init__(self, func):
super(newclassmethod, self).__init__(func)
isabstractmethod = getattr(func,'__isabstractmethod__',False)
if isabstractmethod:
self.__isabstractmethod__ = isabstractmethod
def __get__(self, instance, owner):
result = instancemethodwrapper(super(newclassmethod, self).__get__(instance, owner))
isabstractmethod = getattr(self,'__isabstractmethod__',False)
if isabstractmethod:
result.__isabstractmethod__ = isabstractmethod
abstractmethods = getattr(owner,'__abstractmethods__',None)
if abstractmethods and result.__name__ in abstractmethods:
result.__dontcall__ = True
return result
class abstractclassmethod(newclassmethod):
def __init__(self, func):
func = abc.abstractmethod(func)
super(abstractclassmethod,self).__init__(func)
Usage:
from abc-extend import abstractclassmethod
class A(object):
__metaclass__ = abc.ABCMeta
#abstractclassmethod
def foo(cls):
return 6
class B(A):
pass
class C(B):
#classmethod
def foo(cls):
return super(C,cls).foo() + 1
try:
a = A()
except TypeError:
print 'Instantiating A raises a TypeError.'
try:
A.foo()
except TypeError:
print 'Calling A.foo raises a TypeError.'
try:
b = B()
except TypeError:
print 'Instantiating B also raises a TypeError because foo was not overridden.'
try:
B.foo()
except TypeError:
print 'As does calling B.foo.'
#But C can be instantiated because C overrides foo
c = C()
#And C.foo can be called
print C.foo()
And here are some pyunit tests which give a more exhaustive demonstration.
test-abc-extend.py:
import unittest
import abc
oldclassmethod = classmethod
from abc-extend import newclassmethod as classmethod, abstractclassmethod
class Test(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def testClassmethod(self):
class A(object):
__metaclass__ = abc.ABCMeta
#classmethod
#abc.abstractmethod
def foo(cls):
return 6
class B(A):
#classmethod
def bar(cls):
return 5
class C(B):
#classmethod
def foo(cls):
return super(C,cls).foo() + 1
self.assertRaises(TypeError,A.foo)
self.assertRaises(TypeError,A)
self.assertRaises(TypeError,B)
self.assertRaises(TypeError,B.foo)
self.assertEqual(B.bar(),5)
self.assertEqual(C.bar(),5)
self.assertEqual(C.foo(),7)
def testAbstractclassmethod(self):
class A(object):
__metaclass__ = abc.ABCMeta
#abstractclassmethod
def foo(cls):
return 6
class B(A):
pass
class C(B):
#oldclassmethod
def foo(cls):
return super(C,cls).foo() + 1
self.assertRaises(TypeError,A.foo)
self.assertRaises(TypeError,A)
self.assertRaises(TypeError,B)
self.assertRaises(TypeError,B.foo)
self.assertEqual(C.foo(),7)
c = C()
self.assertEqual(c.foo(),7)
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
I haven't evaluated the performance cost of this solution, but it has worked for my purposes so far.
How do I create a decorator for an abstract class method in Python 2.7?
Yes, this is similar to this question, except I would like to combine abc.abstractmethod and classmethod, instead of staticmethod. Also, it looks like abc.abstractclassmethod was added in Python 3 (I think?), but I'm using Google App Engine, so I'm currently limited to Python 2.7
Thanks in advance.
Here's a working example derived from the source code in Python 3.3's abc module:
from abc import ABCMeta
class abstractclassmethod(classmethod):
__isabstractmethod__ = True
def __init__(self, callable):
callable.__isabstractmethod__ = True
super(abstractclassmethod, self).__init__(callable)
class DemoABC:
__metaclass__ = ABCMeta
#abstractclassmethod
def from_int(cls, n):
return cls()
class DemoConcrete(DemoABC):
#classmethod
def from_int(cls, n):
return cls(2*n)
def __init__(self, n):
print 'Initializing with', n
Here's what it looks like when running:
>>> d = DemoConcrete(5) # Succeeds by calling a concrete __init__()
Initializing with 5
>>> d = DemoConcrete.from_int(5) # Succeeds by calling a concrete from_int()
Initializing with 10
>>> DemoABC() # Fails because from_int() is abstract
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class DemoABC with abstract methods from_int
>>> DemoABC.from_int(5) # Fails because from_int() is not implemented
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class DemoABC with abstract methods from_int
Note that the final example fails because cls() won't instantiate. ABCMeta prevents premature instantiation of classes that haven't defined all of the required abstract methods.
Another way to trigger a failure when the from_int() abstract class method is called is to have it raise an exception:
class DemoABC:
__metaclass__ = ABCMeta
#abstractclassmethod
def from_int(cls, n):
raise NotImplementedError
The design ABCMeta makes no effort to prevent any abstract method from being called on an uninstantiated class, so it is up to you to trigger a failure by invoking cls() as classmethods usually do or by raising a NotImplementedError. Either way, you get a nice, clean failure.
It is probably tempting to write a descriptor to intercept a direct call to an abstract class method, but that would be at odds with the overall design of ABCMeta (which is all about checking for required methods prior to instantiation rather than when methods are called).
Another possible workaround:
class A:
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
def some_classmethod(cls):
"""IMPORTANT: this is a class method, override it with #classmethod!"""
class B(A):
#classmethod
def some_classmethod(cls):
print cls
Now, one still can't instantiate from A until some_classmethod is implemented, and it works if you implement it with a #classmethod.
You could upgrade to Python 3.
Starting with Python 3.3, it is possible to combine #classmethod and #abstractmethod:
import abc
class Foo(abc.ABC):
#classmethod
#abc.abstractmethod
def my_abstract_classmethod(...):
pass
Thanks to #gerrit for pointing this out to me.
I recently encountered the same problem. That is, I needed abstract classmethods but was unable to use Python 3 because of other project constraints. The solution I came up with is the following.
abc-extend.py:
import abc
class instancemethodwrapper(object):
def __init__(self, callable):
self.callable = callable
self.__dontcall__ = False
def __getattr__(self, key):
return getattr(self.callable, key)
def __call__(self, *args, **kwargs):
if self.__dontcall__:
raise TypeError('Attempted to call abstract method.')
return self.callable(*args,**kwargs)
class newclassmethod(classmethod):
def __init__(self, func):
super(newclassmethod, self).__init__(func)
isabstractmethod = getattr(func,'__isabstractmethod__',False)
if isabstractmethod:
self.__isabstractmethod__ = isabstractmethod
def __get__(self, instance, owner):
result = instancemethodwrapper(super(newclassmethod, self).__get__(instance, owner))
isabstractmethod = getattr(self,'__isabstractmethod__',False)
if isabstractmethod:
result.__isabstractmethod__ = isabstractmethod
abstractmethods = getattr(owner,'__abstractmethods__',None)
if abstractmethods and result.__name__ in abstractmethods:
result.__dontcall__ = True
return result
class abstractclassmethod(newclassmethod):
def __init__(self, func):
func = abc.abstractmethod(func)
super(abstractclassmethod,self).__init__(func)
Usage:
from abc-extend import abstractclassmethod
class A(object):
__metaclass__ = abc.ABCMeta
#abstractclassmethod
def foo(cls):
return 6
class B(A):
pass
class C(B):
#classmethod
def foo(cls):
return super(C,cls).foo() + 1
try:
a = A()
except TypeError:
print 'Instantiating A raises a TypeError.'
try:
A.foo()
except TypeError:
print 'Calling A.foo raises a TypeError.'
try:
b = B()
except TypeError:
print 'Instantiating B also raises a TypeError because foo was not overridden.'
try:
B.foo()
except TypeError:
print 'As does calling B.foo.'
#But C can be instantiated because C overrides foo
c = C()
#And C.foo can be called
print C.foo()
And here are some pyunit tests which give a more exhaustive demonstration.
test-abc-extend.py:
import unittest
import abc
oldclassmethod = classmethod
from abc-extend import newclassmethod as classmethod, abstractclassmethod
class Test(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def testClassmethod(self):
class A(object):
__metaclass__ = abc.ABCMeta
#classmethod
#abc.abstractmethod
def foo(cls):
return 6
class B(A):
#classmethod
def bar(cls):
return 5
class C(B):
#classmethod
def foo(cls):
return super(C,cls).foo() + 1
self.assertRaises(TypeError,A.foo)
self.assertRaises(TypeError,A)
self.assertRaises(TypeError,B)
self.assertRaises(TypeError,B.foo)
self.assertEqual(B.bar(),5)
self.assertEqual(C.bar(),5)
self.assertEqual(C.foo(),7)
def testAbstractclassmethod(self):
class A(object):
__metaclass__ = abc.ABCMeta
#abstractclassmethod
def foo(cls):
return 6
class B(A):
pass
class C(B):
#oldclassmethod
def foo(cls):
return super(C,cls).foo() + 1
self.assertRaises(TypeError,A.foo)
self.assertRaises(TypeError,A)
self.assertRaises(TypeError,B)
self.assertRaises(TypeError,B.foo)
self.assertEqual(C.foo(),7)
c = C()
self.assertEqual(c.foo(),7)
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
I haven't evaluated the performance cost of this solution, but it has worked for my purposes so far.