子类避免父类的元类
Subclass avoiding parent's metaclass
假设我有一个第三方库,其中的元class 需要我实现一些东西。但是我想要一个没有的中间 "abstract" subclass 。我该怎么做?
将此视为第三方库具有的一个非常小的示例:
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有常用的辅助方法。像这样:
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 而没有元 class,我可以将其替换 class - 但这次没有这样的运气(我已经提到这给图书馆作者带来了不便。
我可以把它变成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
的无元class 版本?我想到了这样的事情:
ServingBase = remove_metaclass(Serving)
class Spam(ServingBase, metaclass=ServingMeta):
...
但不知道如何实际实施 remove_metaclass
以及在合理可行的情况下(当然,它必须是可行的,需要一些反省,但它可能需要比我能施展的更多的奥术魔法) .
也欢迎任何其他建议。基本上,我想让我的代码变干(一个基础 class 来统治它们),并让我的 linter/code 分析图标全是绿色。
确实没有从 Python class 中删除元 class 的直接方法,因为元 class 创建了 那 class。您可以尝试使用不同的 metaclass 重新创建 class,它不会出现不良行为。例如,您可以使用 type
(默认元 class)。
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
class 的命名空间,并使用它们创建一个新的 class ServingBase
。 N.B。这意味着 ServingBase
将从 Serving
接收所有基数和 methods/attributes,其中一些可能已由 ServingMeta
.
添加
mixin 方法是正确的方法。如果您的 IDE "cringe" 是该工具的一个缺陷 - 只需禁用一些 "features",这在为 Python 这样的动态语言编码时显然是不正确的调整。
这甚至与动态创建事物无关,它只是多重继承,语言自始至终都支持多重继承。多重继承的主要用途之一就是能够创建您需要的混入。
另一种基于继承的解决方法是让你的层次结构更深一层,并在你想出你的混合方法之后引入元class:
class Mixin(object):
def mimixin(self): ...
class SpamBase(Mixin, metaclass=ServingMeta):
name = "stub"
或者只是在中间子中添加 mixinclass:
class Base(metaclass=Serving Meta):
name = "stub"
class MixedBase(Mixin, Base):
name = "stub"
class MyLovingSpam(MixedBase):
name = "MyLovingSpam"
如果您不想在每个 class 中重复 mixin=-base 名称,那就是要走的路。
"Removing" 一个 metaclass 只是为了后期的 mixin 太过分了。真的。破碎的。这样做的方法是动态地重新创建 class,就像 @vaultah 在另一个答案中提到的那样,但是在中间 class 中这样做是你不应该做的事情。这样做是为了取悦 IDE 是你不应该做两次的事情:搞乱 metaclasses 已经够难的了。删除语言自然放置的 inheritance/class 创建的东西是令人讨厌的东西(参见这个答案: )。另一方面,混入和多重继承是很自然的。
你还在吗?我告诉过你不要这样做:
现在,关于你的问题 - 而不是在中间 class 中使用 "supressing the metaclass",继承那里的元 class 并改变它的行为会更可行 - 所以它不会检查特别标记的 classes 中的约束 - 创建一个供您使用的属性,例如 _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"
假设我有一个第三方库,其中的元class 需要我实现一些东西。但是我想要一个没有的中间 "abstract" subclass 。我该怎么做?
将此视为第三方库具有的一个非常小的示例:
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有常用的辅助方法。像这样:
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 而没有元 class,我可以将其替换 class - 但这次没有这样的运气(我已经提到这给图书馆作者带来了不便。
我可以把它变成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
的无元class 版本?我想到了这样的事情:
ServingBase = remove_metaclass(Serving)
class Spam(ServingBase, metaclass=ServingMeta):
...
但不知道如何实际实施 remove_metaclass
以及在合理可行的情况下(当然,它必须是可行的,需要一些反省,但它可能需要比我能施展的更多的奥术魔法) .
也欢迎任何其他建议。基本上,我想让我的代码变干(一个基础 class 来统治它们),并让我的 linter/code 分析图标全是绿色。
确实没有从 Python class 中删除元 class 的直接方法,因为元 class 创建了 那 class。您可以尝试使用不同的 metaclass 重新创建 class,它不会出现不良行为。例如,您可以使用 type
(默认元 class)。
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
class 的命名空间,并使用它们创建一个新的 class ServingBase
。 N.B。这意味着 ServingBase
将从 Serving
接收所有基数和 methods/attributes,其中一些可能已由 ServingMeta
.
mixin 方法是正确的方法。如果您的 IDE "cringe" 是该工具的一个缺陷 - 只需禁用一些 "features",这在为 Python 这样的动态语言编码时显然是不正确的调整。
这甚至与动态创建事物无关,它只是多重继承,语言自始至终都支持多重继承。多重继承的主要用途之一就是能够创建您需要的混入。
另一种基于继承的解决方法是让你的层次结构更深一层,并在你想出你的混合方法之后引入元class:
class Mixin(object):
def mimixin(self): ...
class SpamBase(Mixin, metaclass=ServingMeta):
name = "stub"
或者只是在中间子中添加 mixinclass:
class Base(metaclass=Serving Meta):
name = "stub"
class MixedBase(Mixin, Base):
name = "stub"
class MyLovingSpam(MixedBase):
name = "MyLovingSpam"
如果您不想在每个 class 中重复 mixin=-base 名称,那就是要走的路。
"Removing" 一个 metaclass 只是为了后期的 mixin 太过分了。真的。破碎的。这样做的方法是动态地重新创建 class,就像 @vaultah 在另一个答案中提到的那样,但是在中间 class 中这样做是你不应该做的事情。这样做是为了取悦 IDE 是你不应该做两次的事情:搞乱 metaclasses 已经够难的了。删除语言自然放置的 inheritance/class 创建的东西是令人讨厌的东西(参见这个答案:
你还在吗?我告诉过你不要这样做:
现在,关于你的问题 - 而不是在中间 class 中使用 "supressing the metaclass",继承那里的元 class 并改变它的行为会更可行 - 所以它不会检查特别标记的 classes 中的约束 - 创建一个供您使用的属性,例如 _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"