【发布时间】:2017-10-09 15:01:33
【问题描述】:
假设我有一个第三方库,其中元类需要我实现一些东西。但我想要一个没有的中间“抽象”子类。我该怎么做?
认为这是第三方库所具有的一个非常小的示例:
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 类,我本可以将其子类化 - 但这次没有这样的运气(我已经提到了给库作者带来的不便)。
我可以把它做成一个mixin:
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 属性(而且我不想让分析工具静音) .简而言之,我就是不喜欢这种做法。
我还能做什么?
有没有办法获得Serving 的无元类版本?我想到了这样的事情:
ServingBase = remove_metaclass(Serving)
class Spam(ServingBase, metaclass=ServingMeta):
...
但不知道如何实际实现 remove_metaclass 以及在任何可能的情况下(当然,它必须是可行的,需要一些内省,但它可能需要比我能施展更多的奥术魔法)。
也欢迎任何其他建议。基本上,我想让我的代码 DRY(一个基类来统治它们),并让我的 linter/代码分析图标全是绿色的。
【问题讨论】:
-
我认为你可以尝试使用
type重建类型对象:ServingBase = type('ServingBase', Serving.__bases__, dict(vars(Serving)))我认为 mixin 会使代码更简洁 -
@vaultah 是的,我没有想过这个。看起来是一个选择。您可以将此作为答案发布吗?我会等待其他建议,但如果没有更好的建议,我希望能够将您的建议标记为已接受的答案。至于清洁度——我个人觉得,如果用自我描述的名称(
ServingBase、remove_metaclass)合理地装饰东西并得到很好的评论,那么应该没问题。
标签: python python-3.x subclass metaclass subclassing