The point is that python doesn't have a notion of abstract methods. Abstract methods are part of an base class that defines an interface, without any code. Abstract methods can't be called directly, because they don't contain any code in their definition.
In the definition of the base class, you may want to include a specific method that is part of the interface, but the specific implementation is still unknown. A popular example seems to be the drawing of a point or a line in a graphical application.
The classes Point and Line share several implementation details, but differ on other. In particular, the way they are drawn is completely different (you will want to optimize the drawing of a line). Suppose these two classes are derived from the same class, Object. It is possible to separate the implementation of the method draw of these two classes, while draw can still be called from the base class Object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | class AbstractMethod (object):
"""Defines a class to create abstract methods
@example:
class Foo:
foo = AbstractMethod('foo')
"""
def __init__(self, func):
"""Constructor
@params func: name of the function (used when raising an
exception).
@type func: str
"""
self._function = func
def __get__(self, obj, type):
"""Get callable object
@returns An instance of AbstractMethodHelper.
This trickery is needed to get the name of the class for which
an abstract method was requested, otherwise it would be
sufficient to include a __call__ method in the AbstractMethod
class itself.
"""
return self.AbstractMethodHelper(self._function, type)
class AbstractMethodHelper (object):
"""Abstract method helper class
An AbstractMethodHelper instance is a callable object that
represents an abstract method.
"""
def __init__(self, func, cls):
self._function = func
self._class = cls
def __call__(self, *args, **kwargs):
"""Call abstract method
Raises a TypeError, because abstract methods can not be
called.
"""
raise TypeError('Abstract method `' + self._class.__name__ \
+ '.' + self._function + '\' called')
class Metaclass (type):
def __init__(cls, name, bases, *args, **kwargs):
"""Configure a new class
@param cls: Class object
@param name: Name of the class
@param bases: All base classes for cls
"""
super(Metaclass, cls).__init__(cls, name, bases, *args, **kwargs)
# Detach cls.new() from class Metaclass, and make it a method
# of cls.
cls.__new__ = staticmethod(cls.new)
# Find all abstract methods, and assign the resulting list to
# cls.__abstractmethods__, so we can read that variable when a
# request for allocation (__new__) is done.
abstractmethods = []
ancestors = list(cls.__mro__)
ancestors.reverse() # Start with __builtin__.object
for ancestor in ancestors:
for clsname, clst in ancestor.__dict__.items():
if isinstance(clst, AbstractMethod):
abstractmethods.append(clsname)
else:
if clsname in abstractmethods:
abstractmethods.remove(clsname)
abstractmethods.sort()
setattr(cls, '__abstractmethods__', abstractmethods)
def new(self, cls):
"""Allocator for class cls
@param self: Class object for which an instance should be
created.
@param cls: Same as self.
"""
if len(cls.__abstractmethods__):
raise NotImplementedError('Can\'t instantiate class `' + \
cls.__name__ + '\';\n' + \
'Abstract methods: ' + \
", ".join(cls.__abstractmethods__))
return object.__new__(self)
class Object (object):
__metaclass__ = Metaclass
draw = AbstractMethod('draw')
def update(self):
self.draw()
class Point (Object):
def draw(self):
print 'Point.draw called'
>>> Point().update()
Point.draw called
>>> Object().update()
NotImplementedError: Can't instantiate class `Object';
Abstract methods: draw
|
A bit more explanation about why some things had to be done is in the article I originally wrote to introduce this code: http://www.lychnis.net/index/programming/python-abstract-methods-3.lychnis
I don't understand. I'm a Python newbie; excuse me if don't get it right but i thought Python was about simplicity ...
What's wrong with:
def abstract_method(self):
Complexity. For one thing: clarity. My version gives the exact class and method names where the abstract method was defined.
Second: certainty. With that metaclass, it is guaranteed that you will never accitentally instantiate a class with one or more abstract classes.
Constructor of subclasses requires 2 arguments. I have created an abstract class which (besides self) has one argument on the __init__ method.
Later, I've created a subclass of that abstract one, which added one more parameter to the __init__.
When creating an instance of the later, the error below pops up:
Bug?
Am I restricted to __init__(self) only (no other parameters)?
Nice, while it is not amazingly clean it is great if you don't want somebody to instantiate an abstract class...This is probably the simplest recipe for creating an abstract class I have seen so far...can't wait for python 3000 when interfaces and abstract stuff is supported natively!!!
By the way if you want to have parameters in the __init__ of sub classes you just need to change line 80 for new to
Also, I noticed that you really don't need to pass the name of the function to instantiate AbstractMethod...maybe it was needed in previous versions of python but 2.5 works without it. I changed the __init__ for AbstractMethod to have func=None. That way in my abstract class I just do
that way it is not as redundant with passing the name again to the AbstractMethod. It still spits out the name when it says that I can't instantiate a class with abstract methods.