在类中定义一个装饰器,该装饰器也可在类定义内部使用。

5
我将在Python中实现一个可继承的“子命令”系统。我期望的使用场景是类似于以下内容:
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"

我将Command.subcommand实现为静态方法,一切都很好,直到我尝试向父类添加子命令时,出现了TypeError:'staticmethod' object is not callable。回想起来,显然这样行不通:

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"

到目前为止,我找到的唯一替代方法是在父类外部声明subcommand装饰器,然后在类定义完成后进行注入。
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

然而,对于那些在 Python 加入装饰器之前从未使用过 Python 的人来说,这种方式感觉非常笨拙。有没有更优雅的方法来实现这个呢?


首先,问题是为什么你要将装饰器实现为类的方法,除非该类用于一堆装饰器。如果是这样,那么为什么要装饰装饰器“common”?这可能感觉很笨拙,因为你的类设计有点奇怪... :) - Lennart Regebro
你希望这个装饰器为你完成什么具体的任务呢?Python已经知道子命令是什么,因为它知道哪些类是其他类的子类。 - Karl Knechtel
@Lennart — 纯粹是为了美学上的需求。我不喜欢 import command ... class MyCommand(command.Command)(感觉多余)或者 from command import * ... @subcommand(感觉像是在污染命名空间)。 - Ben Blank
@Karl — 装饰器本质上标记特定的方法作为入口点,然后将其暴露给命令行。 - Ben Blank
仅将自由函数暴露给命令行有什么问题吗?Command类层次结构为您做了什么?你知道,面向对象编程不仅仅是关于类。 - Karl Knechtel
显示剩余2条评论
1个回答

5

我能想到两种解决这个问题的方法。最简单的方法是在父类中使用后将其变为静态方法:

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.

这种方法有些脆弱,因为在将其变成静态方法后无法在父类中使用。更加健壮的方法是添加一个中间类:

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"

现在,您可以将所有的类从Command改为从CommandBase继承。


这有点令人惊讶。我本以为在执行类方法的装饰器时,类本身还不存在(元类在最后调用),但仍然找到了子命令。无论如何,你提供的两个例子都有错别字:第一个缺少结束引号,第二个可能是你想要使用 @Command.subcommand。我正确吗? - 6502
@6502 我已经修复了拼写错误。如果您查看第一个示例中“subcommand”的定义,您会发现它被定义为常规函数。所有方法定义都在找到“subcommand”的范围内进行,因此可以预期在该范围内可见“subcommand”。 - aaronasterling
是的,这很有道理... subcommand 只是一个本地变量,直到类体结束,在那时字典被构建并发送到元类。但我觉得在类语句的主体中编写 while 循环有点奇怪 :-D ... - 6502
你的第二个建议很有趣。目前,我正在混淆基类的功能(默认子命令)和子命令特性本身。如果我将子命令方法(__call__subcommand)移动到一个专用类中,我认为它会非常优雅。谢谢! - Ben Blank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接