1个类继承了2个不同的元类(abcmeta和用户定义的元)

mom*_*aaa 19 python metaprogramming metaclass

我有一个class1需要继承2个不同的元类,即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)
Run Code Online (Sandbox Code Playgroud)

class1Abstract的实现

class class1Abstract(object):
    __metaclass__ = Meta1
    __metaclass__ = abc.ABCMeta
Run Code Online (Sandbox Code Playgroud)

mainclass的实现

class mainClass(class1Abstract):
    # do abstract method stuff
Run Code Online (Sandbox Code Playgroud)

我知道两次实现两个不同的元是错误的.

我改变了加载metclass的方式(几次尝试),我得到了这个TypeError:调用元类库时出错

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

我的想法用完了......


EDITED 1

我尝试了这个解决方案,但是mainClass不是class1Abstract的一个实例

print issubclass(mainClass, class1Abstract) # true
print isinstance(mainClass, class1Abstract) # false
Run Code Online (Sandbox Code Playgroud)

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 '''
Run Code Online (Sandbox Code Playgroud)

mainClass的实现

class mainClass(class1Abstract):
    def do_shared_stuff(self):
        print issubclass(mainClass, class1Abstract) # True
        print isinstance(mainClass, class1Abstract) # False
Run Code Online (Sandbox Code Playgroud)

由于mainClass继承自抽象类python,因此应该抱怨test_method没有在mainClass中实现.但它没有抱怨任何东西,因为print isinstance(mainClass,class1Abstract)#False

dir(mainClass) 没有

['__abstractmethods__', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry']
Run Code Online (Sandbox Code Playgroud)

救命!


EDITED 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 '''
Run Code Online (Sandbox Code Playgroud)

mainClass的实现

class mainClass(class1Abstract):
    __metaclass__ = CombineMeta
    def do_shared_stuff(self):
        print issubclass(mainClass, class1Abstract) # True
        print isinstance(mainClass, class1Abstract) # False
Run Code Online (Sandbox Code Playgroud)

dir(mainClass) 现在有抽象方法的魔法

['__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']
Run Code Online (Sandbox Code Playgroud)

但是python没有警告test_method没有被实例化

救命!

Tim*_*mur 13

在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")
Run Code Online (Sandbox Code Playgroud)

这打印"SampleMetaClass混入!",所以我们知道我们的基本元类工作正常.

现在,另一方面,我们想要一个抽象类,最简单的是:

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()
Run Code Online (Sandbox Code Playgroud)

这将打印"缺少实现IncompleteImplementor",后跟"正确实现MainClass".所以,抽象类也可以正常工作.

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

选项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)
Run Code Online (Sandbox Code Playgroud)

现在,我们在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
Run Code Online (Sandbox Code Playgroud)

并再次运行我们的测试:

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")
Run Code Online (Sandbox Code Playgroud)

这仍然会打印出IncompleteImplementor未正确实现的内容,即MainClass现在两者都sample添加了类级别属性.这里要注意的Sample是,元类的一部分也被成功应用到了IncompleteImplementor(好吧,没有理由不这样做).

正如所预期的,isinstance并且issubclass仍然工作的应该是:

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
Run Code Online (Sandbox Code Playgroud)

选项2 - 组成元类

事实上,问题本身就有这个选项,它只需要一个小修复.将新的元类声明为几个更简单的元类的组合,以混合它们的行为:

SampleMetaWithAbcMixin = type('SampleMetaWithAbcMixin', (ABCMeta, SampleMetaClass), {})
Run Code Online (Sandbox Code Playgroud)

如前所述,更改的元类AbstractClass(又一遍,IncompleteImplementorMainClass没有改变,但如果在同一个解释器重新声明它们):

class AbstractClass(object):
  __metaclass__ = SampleMetaWithAbcMixin
  @abstractmethod
  def implement_me(self):
    pass
Run Code Online (Sandbox Code Playgroud)

从这里开始,运行相同的测试应该产生相同的结果:ABCMeta仍然有效并确保实现@abstractmethod-s,SampleMetaClass添加sample属性.

我个人更喜欢后一个选项,原因与我通常喜欢组合到继承的原因相同:多个(元)类之间最终需要的组合越多 - 组合就越简单.

更多关于元类的信息

最后,我读过的元类的最佳解释是这样的答案: Python中的元类是什么?


Dun*_*nes 9

默认情况下,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()
Run Code Online (Sandbox Code Playgroud)

哪个输出:

YourMeta.__new__
YourMeta.__init__
Couldn't create class -- ConcreteClass has not implemented abstract methods f
YourMeta.__new__
YourMeta.__init__
ConcreteClass.f
Run Code Online (Sandbox Code Playgroud)