“无法实例化包含抽象方法的抽象类”但该类不应有任何抽象方法
来看一个简单的例子:
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
。如果我能在创建 derived_type
之后定义 BarOverride
,这样做会更简单。(如果我不能这样做,那为什么呢?)
4 个回答
我知道这个话题很老旧,但这个问题真的很有意思。
之所以不工作,是因为abc只能在创建类型的时候检查抽象方法,也就是说,当你运行type('Derived', (FooClass,), {})
的时候。之后再用setattr设置的内容,abc就无法访问了。
所以,setattr是没用的,不过……
你提到的关于如何处理一个之前没有声明或定义的类名的问题,看起来是可以解决的:
我写了一个小的元类,让你可以用一个占位符“clazz”来访问任何最终会拥有你在类定义外编写的方法的类。
这样,你就不会再因为abc而出现TypeError了,因为你现在可以在创建类型之前定义你的方法,然后把它传递给type的字典参数。这样abc就会把它视为一个合适的方法重写。
而且,使用这个新的元类,你可以在那个方法中引用类对象。这太棒了,因为现在你可以使用super了!=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...
Jochen说得对;抽象方法是在类创建的时候就确定下来的,不会因为你重新给某个属性赋值而改变。
你可以通过下面的方式手动把它从抽象方法的列表中移除:
DerivedType.__abstractmethods__ = frozenset()
或者
DerivedType.__abstractmethods__ = frozenset(
elem for elem in DerivedType.__abstractmethods__ if elem != 'FooMethod')
还有使用 setattr
这个方法,这样它就不会再认为 FooMethod
是抽象的了。
动态地给一个类添加抽象方法,或者在类或方法创建后试图修改它们的抽象状态,这些都是不被支持的。
abstractmethod()
只会影响通过正常继承创建的子类;通过 ABC 的register()
方法注册的“虚拟子类”不会受到影响。
元类只在定义类的时候被调用。当 abstractmethod
把一个类标记为抽象类后,这个状态是不会再改变的。