采用以下最小例子:
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
,则更容易执行此操作。 (如果我不能这样做,为什么呢?)
答案 0 :(得分:5)
动态地将抽象方法添加到类或尝试 修改方法或类创建后的抽象状态, 不受支持。 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()