似乎当我有一个继承自gevent.Greenlet的抽象基类(它继承自C扩展模块greenlet:https://github.com/python-greenlet/greenlet)时,实现它的类不会引发任何abc错误关于未实现的方法。
class ActorBase(gevent.Greenlet):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def foo(self):
print "foo"
class ActorBaseTest(ActorBase):
def bar(self):
print "bar"
abt = ActorBaseTest() # no errors!
如果我从object
继承,它会按预期失败:
class ActorBase(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def foo(self):
print "foo"
class ActorBaseTest(ActorBase):
def bar(self):
print "bar"
>>> abt = ActorBaseTest()
Traceback (most recent call last):
File "/home/dw/.virtualenvs/prj/local/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2827, in run_code
exec code_obj in self.user_global_ns, self.user_ns
File "<ipython-input-6-d67a142e7297>", line 1, in <module>
abt = ActorBaseTest()
TypeError: Can't instantiate abstract class ActorBaseTest with abstract methods foo
实现此功能的正确方法是什么?
答案 0 :(得分:7)
问题的原因是它是检查抽象类实例化的object.__new__
方法,在这种情况下object.__new__
没有被调用: gevent.Greenlet
继承自greenlet.greenlet
,而greenlet.greenlet
是C扩展类型,其__new__
实现在任何时候都不会调用object.__new__
(请参阅{{ 3}} greenlet
C源代码中的函数。
通过继承一些实现自己的__new__
方法的其他内置类型并且不再引用object.__new__
(float
类型,您可以看到相同的效果,例如)。但问题并不是特定于C扩展类型:您还可以使用纯Python类型复制它。请考虑以下代码:
import abc
class A(object):
def __new__(cls):
# self = object.__new__(cls)
return 42
class B(A):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def foo(self):
pass
b = B() # No exception.
类B
被正确注册为抽象类(在内部,Py_TPFLAGS_IS_ABSTRACT
位在tp_flags
字段中设置),但object.__new__
永远不会被调用,所以实例化B
时没有错误。但是,如果您取消注释self = object.__new__(cls)
中的A
方法调用,则会在实例化时看到预期的错误。
至于正确的方式&#39;实现这一点,遗憾的是我认为正确的方法是修复greenlet
类型,以便其__new__
方法调用object.__new__
。我猜您可以向__new__
添加ActorBase
方法,该方法显式调用基类__new__
和 object.__new__
(并抛弃结果后者),但我认为这是一个丑陋的解决方法,而不是正确的方式&#39;。 (编辑:最重要的是,它没有用。我从TypeError: object.__new__(ActorBase) is not safe, use greenlet.greenlet.__new__()
电话中获得object.__new__
。)我已经在greenlet跟踪器上打开了green_new
CHasTraits
类, 可以很好地与基本知识。它的__new__
方法就像这样开始(评论来自原始来源,而不是我的):
PyObject *
has_traits_new ( PyTypeObject * type, PyObject * args, PyObject * kwds ) {
// Call PyBaseObject_Type.tp_new to do the actual construction.
// This allows things like ABCMeta machinery to work correctly
// which is implemented at the C level.
has_traits_object * obj = (has_traits_object *) PyBaseObject_Type.tp_new(type, empty_tuple, empty_dict);
所以也许长期解决方案是说服greenlet
人做类似的事情。