views:

95

answers:

5

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!

+4  A: 

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.

Tim Yates
I don't think it's available on app-engine. It's introduced in 2.6 and app-engine is, last I heard, on 2.5
aaronasterling
Ahh I didn't know that. In that case, the old way (just document it) is probably sufficient.
Tim Yates
@AaronMcSmooth: When someone proposed using GAE for a project, I was afraid of being stuck with whatever old versions of things Google decided to expose, and so didn't use it--if it's really still on 2.5 in 2010 (or even 2009), I'm very glad I avoided it.
Glenn Maynard
+1  A: 

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.

intuited
+1  A: 

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.

wheaties
-1. This breaks super.
aaronasterling
Works well except if overriding methods are to call `super`.
intuited
@AaronMcSmooth I thought that was the point. @intuited that's true.
wheaties
@wheaties. I can't use cooperative multiple inheritance when subclassing a class that does this because it breaks super. You call super so that the _sibling_ class' method runs. The correct thing to do is define the method with a `pass` statement.
aaronasterling
A: 

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.

aaronasterling
A: 

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.

ma3