“不能在类上实例化抽象类......使用抽象方法”,不应该有任何抽象方法

时间:2011-11-10 21:49:41

标签: python abstract-class

采用以下最小例子:

import abc

class FooClass(object):
  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def FooMethod(self):
    raise NotImplementedError()


def main():
  derived_type = type('Derived', (FooClass,), {})

  def BarOverride(self):
    print 'Hello, world!'
  derived_type.FooMethod = BarOverride

  instance = derived_type()

正在运行main()

TypeError: Can't instantiate abstract class Derived with abstract methods FooMethod

(例外情况发生在instance = derived_type()行。)

FooMethod不应该是抽象的:我用BarOverride覆盖了它。那么,为什么这会引发例外呢?

免责声明:是的,我可以使用显式class语法,并完成完全相同的事情。 (甚至更好,我可以使它工作!)但这是一个最小的测试用例,更大的例子是动态创建类。 :-)而且我很好奇为什么这不起作用。

编辑:并防止其他明显的非答案:我不想将第三个参数中的BarOverride传递给type:在实际示例中, BarOverride需要derived_type绑定它。如果我可以在创建BarOverride后定义derived_type,则更容易执行此操作。 (如果我不能这样做,为什么呢?)

4 个答案:

答案 0 :(得分:5)

Because the docs say so:

  

动态地将抽象方法添加到类或尝试   修改方法或类创建后的抽象状态,   不受支持。 abstractmethod()只影响子类   使用常规继承派生; “虚拟子类”已注册   使用ABC的register()方法不受影响。

只有在定义类时才会调用元类。当abstractmethod将类标记为抽象时,状态将不会在以后更改。

答案 1 :(得分:3)

乔克是对的;抽象方法是在类创建时设置的,我不会因为你重新分配一个属性而修改它。

您可以通过执行

手动将其从抽象方法列表中删除
DerivedType.__abstractmethods__ = frozenset()

DerivedType.__abstractmethods__ = frozenset(
        elem for elem in DerivedType.__abstractmethods__ if elem != 'FooMethod')

以及setattr,因此它仍然认为FooMethod不是抽象的。

答案 2 :(得分:3)

我知道这个话题真的很老但是......这真是一个很好的问题。

它不起作用,因为abc只能在类型的实例化期间检查抽象方法,也就是说,type('Derived', (FooClass,), {})正在运行时。之后完成的任何setattr都无法从abc访问。

所以,setattr不会工作,buuut ...... 解决以前未声明或定义的类名称的问题看起来是可解决的:

我写了一个小元类,它允许你使用占位符“clazz”来访问任何类,这些类最终会得到你在类定义之外编写的方法。

这样你就不会再从abc中获取TypeError,因为你现在可以在实例化你的类型之前定义你的方法,然后将它传递给dict参数的类型。然后abc会将其视为正确的方法覆盖。

Aaand,使用新的元类,您可以在该方法中引用类对象。 这是超级的,因为现在你可以使用超级! = P 我猜你也担心这个......

看看:

import abc
import inspect

clazz = type('clazz', (object,), {})()

def clazzRef(func_obj):
    func_obj.__hasclazzref__ = True
    return func_obj

class MetaClazzRef(type):
    """Makes the clazz placeholder work.

    Checks which of your functions or methods use the decorator clazzRef
    and swaps its global reference so that "clazz" resolves to the
    desired class, that is, the one where the method is set or defined.

    """
    methods = {}
    def __new__(mcs, name, bases, dict):
        ret = super(MetaClazzRef, mcs).__new__(mcs, name, bases, dict)
        for (k,f) in dict.items():
            if getattr(f, '__hasclazzref__', False):
                if inspect.ismethod(f):
                    f = f.im_func
                if inspect.isfunction(f):
                    for (var,value) in f.func_globals.items():
                        if value is clazz:
                            f.func_globals[var] = ret
        return ret

class MetaMix(abc.ABCMeta, MetaClazzRef):
    pass

class FooClass(object):
    __metaclass__ = MetaMix
    @abc.abstractmethod
    def FooMethod(self):
        print 'Ooops...'
        #raise NotImplementedError()


def main():
    @clazzRef
    def BarOverride(self):
        print "Hello, world! I'm a %s but this method is from class %s!" % (type(self), clazz)
        super(clazz, self).FooMethod() # Now I have SUPER!!!

    derived_type = type('Derived', (FooClass,), {'FooMethod': BarOverride})

    instance = derived_type()
    instance.FooMethod()

    class derivedDerived(derived_type):
        def FooMethod(self):
            print 'I inherit from derived.'
            super(derivedDerived,self).FooMethod()

    instance = derivedDerived()
    instance.FooMethod()

main()

输出结果为:

Hello, world! I'm a <class 'clazz.Derived'> but this method is from class <class 'clazz.Derived'>!
Ooops...
I inherit from derived.
Hello, world! I'm a <class 'clazz.derivedDerived'> but this method is from class <class 'clazz.Derived'>!
Ooops...

答案 3 :(得分:2)

好吧,如果你必须这样做,那么你可以传递一个虚拟dict {'FooMethod':None}作为第三个参数来输入。这允许derived_type满足ABCMeta要求覆盖所有抽象方法的要求。稍后您可以提供真实的FooMethod

def main():
  derived_type = type('Derived', (FooClass,), {'FooMethod':None})
  def BarOverride(self):
    print 'Hello, world!'
  setattr(derived_type, 'FooMethod', BarOverride)
  instance = derived_type()