Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining a decorator in a class, which is also usuable within the class definition

I'm trying to implement a "subcommand" system as an inheritable class in Python. My expected use case is something like:

from command import Command
import sys

class MyCommand(Command):
    @Command.subcommand
    def foo(self):
        print "this can be run as a subcommand"

    def bar(self):
        print "but this is a plain method and isn't exposed to the CLI"

MyCommand()(*sys.argv)

# at the command line, the user runs "mycommand.py foo"

I implemented Command.subcommand as a static method and everything worked fine until I tried to add a subcommand to the parent class, which got me TypeError: 'staticmethod' object is not callable. In hindsight, it's obvious that this won't work:

class Command(object):
    @staticmethod
    def subcommand(method):
        method.is_subcommand = True

        return method

    @subcommand
    def common(self):
        print "this subcommand is available to all child classes"

The only alternative I've found so far is to declare the subcommand decorator outside the parent class, then inject it after the class definition is complete.

def subcommand(method):
    method.is_subcommand = True

    return method

class Command(object):
    @subcommand
    def common(self):
        print "this subcommand is available to all child classes"

Command.subcommand = staticmethod(subcommand)
del subcommand

However, as someone who never used Python before decorators were added, this feels very clunky to me. Is there a more elegant way to accomplish this?

like image 699
Ben Blank Avatar asked Jan 30 '11 21:01

Ben Blank


1 Answers

There are two solutions to this problem that I can think of. The simplest is to make it a static method after you're done using it in the parent class:

class Command(object):
    def subcommand(method): # Regular function in class definition scope.
        method.is_subcommand = True

        return method

    @subcommand
    def common(self):
        print "this subcommand is available to all child classes"

    subcommand = staticmethod(subcommand)
    # Now a static method. Can no longer be called during class definition phase.

This is somewhat fragile in that you can't use it in the parent class after you make it a static method. The more robust way to do this is to add an intermediate class:

class Command(object):
    @staticmethod
    def subcommand(method):
        method.is_subcommand = True

        return method

class CommandBase(Command):

    @Command.subcommand
    def common(self):
        print "this subcommand is available to all child classes"

You can now inherit all of your classes from CommandBase instead of Command.

like image 150
aaronasterling Avatar answered Sep 30 '22 15:09

aaronasterling