一个类继承了两个不同的元类(abcmeta和用户定义的元类)。

22

我有一个类class1,需要继承两个不同的元类Meta1和abc.ABCMeta。

当前实现:

Meta1的实现:

class Meta1(type):
    def __new__(cls, classname, parent, attr):
        new_class = type.__new__(cls, classname, parent, attr)
        return super(Meta1, cls).__new__(cls, classname, parent, attr)

class1Abstract的实现

class class1Abstract(object):
    __metaclass__ = Meta1
    __metaclass__ = abc.ABCMeta

主类的实现

class mainClass(class1Abstract):
    # do abstract method stuff

我知道这样实现两个不同的元类是错误的。

我改变了加载元类的方式(尝试了几种方法),但是出现了以下错误: TypeError: 在调用元类基类时出错

metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我没有更多的想法了...


编辑1

我尝试了这个解决方案,它可以工作,但是mainClass不是class1Abstract类的实例。

print issubclass(mainClass, class1Abstract) # true
print isinstance(mainClass, class1Abstract) # false

实现class1Abstract类

class TestMeta(Meta1):
    pass


class AbcMeta(object):
    __metaclass__ = abc.ABCMeta
    pass


class CombineMeta(AbcMeta, TestMeta):
    pass


class class1Abstract(object):
    __metaclass__ = CombineMeta

    @abc.abstractmethod
    def do_shared_stuff(self):
        pass

    @abc.abstractmethod
    def test_method(self):
        ''' test method '''

主类的实现

class mainClass(class1Abstract):
    def do_shared_stuff(self):
        print issubclass(mainClass, class1Abstract) # True
        print isinstance(mainClass, class1Abstract) # False

由于mainClass继承自抽象类,Python应该会抱怨mainClass中没有实现test_method方法。但是它没有抱怨任何事情,因为print isinstance(mainClass,class1Abstract)#False

dir(mainClass)中没有

['__abstractmethods__', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry']

求救!


已编辑 2

class1Abstract 类的实现

CombineMeta = type("CombineMeta", (abc.ABCMeta, Meta1), {})
class class1Abstract(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def do_shared_stuff(self):
        pass

    @abc.abstractmethod
    def test_method(self):
        ''' test method '''

主类的实现

class mainClass(class1Abstract):
    __metaclass__ = CombineMeta
    def do_shared_stuff(self):
        print issubclass(mainClass, class1Abstract) # True
        print isinstance(mainClass, class1Abstract) # False

dir(mainClass)现在具有abstractmethod的魔术方法

['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry', 'do_shared_stuff', 'test_method']

但是Python并没有警告test_method未被实例化

帮帮我!


这只支持 Python 2 吗? - Dunes
所以你的主要问题是mainClass没有抱怨抽象方法未被实例化?我相信Python没有解决这个问题的方法。与其在类创建时得到错误,不如在对象创建时即mainClass()得到错误。 - Dunes
4个回答

15
在Python中,每个类只能有一个元类,不能有多个。但是,可以通过混合这些元类的功能来实现类似于具有多个元类的行为。让我们从简单的开始。我们自己的元类只需向类添加新属性:
class SampleMetaClass(type):
  """Sample metaclass: adds `sample` attribute to the class"""
  def __new__(cls, clsname, bases, dct):
    dct['sample'] = 'this a sample class attribute'
    return super(SampleMetaClass, cls).__new__(cls, clsname, bases, dct)

class MyClass(object):
  __metaclass__ = SampleMetaClass

print("SampleMetaClass was mixed in!" if 'sample' in MyClass.__dict__ else "We've had a problem here")

这将打印出“SampleMetaClass was mixed in! ”,因此我们知道我们的基本元类工作正常。

现在,另一方面,我们想要一个抽象类,在其最简单的形式中,它应该是:

from abc import ABCMeta, abstractmethod

class AbstractClass(object):
  __metaclass__ = ABCMeta
  @abstractmethod
  def implement_me(self):
    pass

class IncompleteImplementor(AbstractClass):
  pass

class MainClass(AbstractClass):
  def implement_me(self):
    return "correct implementation in `MainClass`"

try:
  IncompleteImplementor()
except TypeError as terr:
  print("missing implementation in `IncompleteImplementor`")

MainClass().implement_me()

这会打印出“IncompleteImplementor中缺少的实现”,然后是“MainClass中正确的实现”。所以,这个抽象类也能正常工作。

现在,我们有两个简单的实现,需要将这两个元类的行为混合在一起。这里有多种选项。

选项1 - 子类化

可以将一个SampleMetaClass实现为ABCMeta的子类 - 元类本身也是类,因此可以继承它们!

class SampleMetaABC(ABCMeta):
  """Same as SampleMetaClass, but also inherits ABCMeta behaviour"""
  def __new__(cls, clsname, bases, dct):
    dct['sample'] = 'this a sample class attribute'
    return super(SampleMetaABC, cls).__new__(cls, clsname, bases, dct)

现在,我们在AbstractClass的定义中更改元类:

class AbstractClass(object):
  __metaclass__ = SampleMetaABC
  @abstractmethod
  def implement_me(self):
    pass

# IncompleteImplementor and MainClass implementation is the same, but make sure to redeclare them if you use same interpreter from the previous test

然后再次运行我们的测试:

try:
  IncompleteImplementor()
except TypeError as terr:
  print("missing implementation in `IncompleteImplementor`")

MainClass().implement_me()

print("sample was added!" if 'sample' in IncompleteImplementor.__dict__ else "We've had a problem here")
print("sample was added!" if 'sample' in MainClass.__dict__ else "We've had a problem here")

这仍然会打印出IncompleteImplementor没有正确实现,MainClass已经实现,而且现在两者都有添加了sample类级别属性的 Sample 元类。值得注意的是,Sample 元类的一部分也成功地应用于 IncompleteImplementor 上(嗯,没有理由不应用)。

如预期那样,isinstanceissubclass 仍然按照预期工作:

print(issubclass(MainClass, AbstractClass)) # True, inheriting from AbtractClass
print(isinstance(MainClass, AbstractClass)) # False as expected - AbstractClass is a base class, not a metaclass
print(isinstance(MainClass(), AbstractClass)) # True, now created an instance here

选项2-组合元类

事实上,在问题本身中就有这个选项,只需要进行小修补即可。声明新的元类作为若干简单元类的组合以混合它们的行为:

SampleMetaWithAbcMixin = type('SampleMetaWithAbcMixin', (ABCMeta, SampleMetaClass), {})

像以前一样,更改AbstractClass的元类(而且同样,IncompleteImplementorMainClass不会改变,但如果在同一个解释器中,请重新声明它们):

class AbstractClass(object):
  __metaclass__ = SampleMetaWithAbcMixin
  @abstractmethod
  def implement_me(self):
    pass

从这里开始,运行相同的测试应该会产生相同的结果:ABCMeta仍然有效,并确保实现了@abstractmethodSampleMetaClass添加了一个sample属性。

我个人更喜欢后一种选择,原因与我通常更喜欢组合而不是继承的原因相同:随着多个(元)类之间需要的组合越来越多,使用组合会更加简单。

关于元类的更多信息

最后,我读过的有关元类的最好解释是这个SO答案:What is a metaclass in Python?


我认为这些解决方案不起作用,因为抽象方法在类创建时被检查。 - momokjaaaaa
这是一个单独的问题——实现仅在实例化时进行验证是ABCMeta的实现方式。否则,您需要在类声明上设置某种“钩子”——我不确定是否存在这种情况,但如果我在这里错了,请有人纠正我。如果您真的想强制执行此检查,可以尝试在声明后立即创建类实例,但这仍然是编码人员的责任。另一方面,为什么确切地需要这个?也许有其他方式来满足相同的需求? - Tim

13
默认情况下,Python仅在尝试实例化类时才会抱怨该类具有抽象方法,而不是在创建类时。这是因为该类的元类仍然是ABCMeta(或其子类型),因此允许具有抽象方法。
要获得所需的结果,您需要编写自己的元类,当它发现__abstractmethods__不为空时引发错误。这样,您必须明确声明类不再允许有抽象方法。
from abc import ABCMeta, abstractmethod

class YourMeta(type):
    def __init__(self, *args, **kwargs):
        super(YourMeta, self).__init__(*args, **kwargs)
        print "YourMeta.__init__"
    def __new__(cls, *args, **kwargs):
        newcls = super(YourMeta, cls).__new__(cls, *args, **kwargs)
        print "YourMeta.__new__"
        return newcls

class ConcreteClassMeta(ABCMeta):
    def __init__(self, *args, **kwargs):
        super(ConcreteClassMeta, self).__init__(*args, **kwargs)
        if self.__abstractmethods__:
            raise TypeError("{} has not implemented abstract methods {}".format(
                self.__name__, ", ".join(self.__abstractmethods__)))

class CombinedMeta(ConcreteClassMeta, YourMeta):
    pass

class AbstractBase(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def f(self):
        raise NotImplemented

try:
    class ConcreteClass(AbstractBase):
        __metaclass__ = CombinedMeta

except TypeError as e:
    print "Couldn't create class --", e

class ConcreteClass(AbstractBase):
    __metaclass__ = CombinedMeta
    def f(self):
        print "ConcreteClass.f"

assert hasattr(ConcreteClass, "__abstractmethods__")
c = ConcreteClass()
c.f()

输出结果为:

YourMeta.__new__
YourMeta.__init__
Couldn't create class -- ConcreteClass has not implemented abstract methods f
YourMeta.__new__
YourMeta.__init__
ConcreteClass.f

“ConcreteClassMeta” 是不是多余的?使用 “ABCMeta.__init__” 不就可以检查吗?也就是说,使用 “class CombinedMeta(ABCMeta, YourMeta)” 就足够了吧? - Андрей Беньковский

0

不需要设置两个元类: Meta1 应该继承自 abc.ABCMeta


0
在您编辑过的代码(1和2)中,您已经接近完成了。唯一错误的是您如何使用isinstance。您想要检查一个类实例(在这种情况下是self)是否是给定类(class1Abstract)的实例。例如:
class mainClass(class1Abstract):
def do_shared_stuff(self):
    print issubclass(mainClass, class1Abstract) # True
    print isinstance(self, class1Abstract) # True

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