python - 子类避免父类的元类

标签 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()

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

代码应该这样使用:

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 statements。如果第三方库有一个没有元类的 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 属性(我不想让分析工具保持沉默)。简而言之,我只是不喜欢这种方法。

我还能做什么?

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

ServingBase = remove_metaclass(Serving)

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

但不知道如何实际实现 remove_metaclass 以及只要它是合理可能的(当然,它必须是可行的,经过一些内省(introspection),但它可能需要比我可以施展的更多奥术魔法)。

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

最佳答案

mixin 方法是正确的方法。如果您的 IDE “畏缩”是该工具的缺陷 - 只需禁用一些在为 Python 等动态语言编码时明显不正确的调整的“功能”。

这甚至不是动态创建事物,它只是多重继承,这是该语言永远支持的。多重继承的主要用途之一就是能够创建您需要的 mixin。

另一种基于继承的解决方法是使层次结构更深一层,并在提出 mixin 方法后引入元类:

class Mixin(object):
    def mimixin(self): ...

class SpamBase(Mixin, metaclass=ServingMeta):
    name = "stub"

或者只是在中间子类中添加 mixin:

class Base(metaclass=Serving Meta):
     name = "stub"
class MixedBase(Mixin, Base):
     name = "stub"
class MyLovingSpam(MixedBase):
     name = "MyLovingSpam"

如果您不想在每个类中重复 mixin=-base 名称,那么就可以这样做。

仅仅为了后期混合而“删除”元类是太过分了。真的。 splinter 的。这样做的方法是动态地重新创建类,就像 @vaultah 在另一个答案中提到的那样,但在中间类中这样做是你不应该做的事情。为了取悦 IDE 而这样做是不应该重复的:弄乱元类已经够难的了。删除语言自然放置在继承/类创建上的东西是令人讨厌的(参见这个答案: How to make a class attribute exclusive to the super class )。另一方面,mixin 和多重继承是很自然的。

你还在吗?我告诉过你不要这样做:

现在,回答你的问题 - 与其在中间类中“抑制元类”,不如继承你在那里的元类并更改其行为更可行 - 这样它就不会检查特别标记中的约束类 - 创建一个供您使用的属性,例如 _skip_checking

class MyMeta(ServingMeta):
    def __new__(metacls, name, bases, namespace):
         if namespace.get("_skip_checking", False):
              # hardcode call to "type" metaclass:
              del namespace["_skip_checking"]
              cls = type.__new__(metacls, name, bases, namespace)
         else:
              cls = super().__new__(metacls, name, bases, namespace) 
         return cls
     # repeat for __init__ if needed.  


class Base(metaclass=MyMeta):
     _skip_checking = True
     # define mixin methods

class LoveSpam(Base):
    name = "LoveSpam"

关于python - 子类避免父类的元类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43901350/

相关文章:

python - snakemake:规则的可选输入

python - 除了 Python 编解码器错误?

python - 无法将每个子列表中包含 2 个元素的列表转换为单个字符串

c# - 使用 IoC 将依赖项注入(inject)基类和子类?

java - 初始化子类字段

python - 如何匹配整个数据框中的元素并返回该特定匹配元素的整行或索引?

python - Tkinter 机智 while 循环 (python 3)

python - 有没有办法禁止在 say 命令中使用@everyone 或@here

python - 我可以在我的win10机器上同时安装python 2.7和3.5吗?

java - 子类类型作为父类(super class)构造函数java中的参数类型