子类避免使用父类的元类

时间:2017-05-10 19:38:03

标签: python python-3.x subclass metaclass subclassing

假设我有一个第三方库,其中元类要求我实现某些东西。但我希望有一个中级"抽象"没有的子类。我怎么能这样做?

将此视为第三方库具有的最小示例:

class ServingMeta(type):
    def __new__(cls, name, bases, classdict):
        if any(isinstance(b, ServingMeta) for b in bases):
            if "name" not in classdict:
                # Actual code fails for a different reason,
                # but the logic is the same.
                raise TypeError(f"Class '{name}' has no 'name' attribute")
        return super().__new__(cls, name, bases, classdict)

class Serving(object, metaclass=ServingMeta):
    def shout_name(self):
        return self.name.upper()

我无法修改上面的代码。它是一个外部依赖(我不想分叉)。

代码应该以这种方式使用:

class Spam(Serving):
    name = "SPAM"

spam = Spam()
print(spam.shout_name())

然而,我碰巧有很多垃圾邮件,我想介绍一个带有常见帮助方法的基类。像这样:

class Spam(Serving):
    def thrice(self):
        return " ".join([self.shout_name()] * 3)

class LovelySpam(Spam):
    name = "lovely spam"

class WonderfulSpam(Spam):
    name = "wonderful spam"

显然,这并没有奏效,并且因为预期的TypeError: Class 'SpamBase' has no 'name' attribute declared而失败。第三方图书馆是否有一个没有元类的SpamBase类,我可以将其分类 - 但这次没有这样的运气(我已经向图书馆作者提到了这种不便)。

我可以把它变成混音:

class SpamMixin(object):
    def thrice(self):
        return " ".join([self.shout_name()] * 3)

class LovelySpam(SpamMixin, Serving):
    name = "lovely spam"

class WonderfulSpam(SpamMixin, Serving):
    name = "wonderful spam"

然而,这让我和我的IDE有点畏缩,因为在任何地方重复SpamMixin很快变得很麻烦,而且因为object没有shout_name属性(我不会&#39} ; t想要沉默分析工具)。简而言之,我不喜欢这种方法。

我还能做些什么?

有没有办法获得无元类版本的Serving?我想到这样的事情:

ServingBase = remove_metaclass(Serving)

class Spam(ServingBase, metaclass=ServingMeta):
    ...

但是不知道如何实际实施remove_metaclass以及它何时合理可能(当然,它必须是可行的,有一些内省,但它可能需要更多的奥术魔法而不是我可以演员。)

欢迎任何其他建议。基本上,我想让我的代码DRY(一个基类来统治它们),并让我的linter / code分析图标全部为绿色。

1 个答案:

答案 0 :(得分:1)

确实没有直接从Python类中删除元类的方法,因为元类创建了该类。你可以尝试的是使用不同的元类重新创建类,这不会有不需要的行为。例如,您可以使用type(默认元类)。

In [6]: class Serving(metaclass=ServingMeta):
   ...:     def shout_name(self):
   ...:         return self.name.upper()
   ...: 

In [7]: ServingBase = type('ServingBase', Serving.__bases__, dict(vars(Serving)))

基本上这需要__bases__元组和Serving类的命名空间,并使用它们来创建新类ServingBase。注:这意味着ServingBase将从Serving接收所有基础和方法/属性,其中一些可能已由ServingMeta添加。