How to require implementation of method in Python?
-
04-10-2019 - |
Question
I'm using duck typing in Python.
def flagItem(object_to_flag, account_flagging, flag_type, is_flagged):
if flag_type == Flags.OFFENSIVE:
object_to_flag.is_offensive=is_flagged
elif flag_type == Flags.SPAM:
object_to_flag.is_spam=is_flagged
object_to_flag.is_active=(not is_flagged)
object_to_flag.cleanup()
return object_to_flag.put()
Where different objects are passed in as object_to_flag
, all of which have is_active
, is_spam
, is_offensive
attributes. They also happen to have a cleanup()
method.
The objects I'm passing in all have the same base class (they're db objects in Google App Engine):
class User(db.Model):
...
is_active = db.BooleanProperty(default = True)
is_spam = db.BooleanProperty(default=False)
is_offensive = db.BooleanProperty(default=False)
def cleanup():
pass
class Post(db.Model):
...
is_active = db.BooleanProperty(default = True)
is_spam = db.BooleanProperty(default=False)
is_offensive = db.BooleanProperty(default=False)
def cleanup():
pass
How can I make the cleanup()
method abstract so that I can have the same parent class for all these objects that requires the children provide implementation?
Perhaps more importantly, is this 'pythonic'? Should I go this route, or should I just rely on the duck typing? My background is in Java and I'm trying to learn the Python way of doing things.
Thanks!
Solution
Since you don't have abc available, you can do this with a simple metaclass
class Abstract(type(db.Model)):
def __new__(metacls, clsname, bases, clsdict):
for methodname in clsdict.pop('_abstract_methods_', []):
try:
if not callable(clsdict[methodname]):
raise TypeError("{0} must implement {1}".format(clcname, methodname))
except KeyError:
raise TypeError("{0} must implement {1}".format(clcname, methodname))
return super(Abstract, metacls).__new__(metacls, clsname, bases, clsdict)
class RequireCleanup(db.Model):
__metaclass__ = Abstract
_abstract_methods_ = ['cleanup']
def cleanup(self):
pass
the expression type(db.Model)
resolves to whatever metaclass gets used for db.Model
so we don't step on google's code. Also, we pop _abstract_methods_
out of the class dictionary before it get's passed to google's __new__
method so that we don't break anything. If db.Model
already has an attribute with that name, then you will need to change it to something else.
OTHER TIPS
Use the abc
module. Specifically, set your base class's metaclass to ABCMeta
and use the @abstractmethod
decorator on your cleanup
method.
The debate on whether this is "pythonic" is split. PEP 3119, which describes the standard, lists some of the pros and cons (but obviously favors ABCs). It made it into the standard library, which is a pretty good indication that many people consider it useful in some circumstances. In your case, I think it is appropriate.
If you want to ensure that the cleanup method is implemented, you can wrap with the @abc.virtualmethod
decorator. This will cause an error on instantiation of any object that hasn't overridden the virtualmethod. This also requires that you make abc.ABCMeta
your class's __metaclass__
.
See the abc module for more info and some examples.
This is not commonly done: usually there will just be docs to the effect that implementers must override the given method. However this may be more due to the newness of the abc
module (new in Python 2.6) than a perceived unpythonicness of this approach.
Why not have the cleanup
method just raise an NotImplementedError
when it is called? If they want your children classes to work they'll have to put some sort of implementation in place.
Although I've never used it personally, I've seen many references to Zope interfaces. This may be overkill for your task, but it may give you some direction. And it may feel comfortable to someone with a Java background.