Python abc模块:扩展抽象基类和异常派生类会导致令人惊讶的行为 [英] Python abc module: Extending both an abstract base class and an exception-derived class leads to surprising behavior
问题描述
扩展抽象基类和从对象"派生的类都可以按预期工作:如果您尚未实现所有抽象方法和属性,则会出现错误.
Extending both an abstract base class and a class derived from "object" works as you would expect: if you you haven't implemented all abstract methods and properties, you get an error.
奇怪的是,将对象派生的类替换为扩展了"Exception"的类,使您可以创建未实现所有必需的抽象方法和属性的类的实例.
Strangely, replacing the object-derived class with an class that extends "Exception" allows you to create instances of classes which do not implement all the required abstract methods and properties.
例如:
import abc
# The superclasses
class myABC( object ):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def foo(self):
pass
class myCustomException( Exception ):
pass
class myObjectDerivedClass( object ):
pass
# Mix them in different ways
class myConcreteClass_1(myCustomException, myABC):
pass
class myConcreteClass_2(myObjectDerivedClass, myABC):
pass
# Get surprising results
if __name__=='__main__':
a = myConcreteClass_1()
print "First instantiation done. We shouldn't get this far, but we do."
b = myConcreteClass_2()
print "Second instantiation done. We never reach here, which is good."
...产量...
First instantiation done. We shouldn't get this far, but we do.
Traceback (most recent call last):
File "C:/Users/grahamf/PycharmProjects/mss/Modules/mssdevice/sutter/sutter/test.py", line 28, in <module>
b = myConcreteClass_2()
TypeError: Can't instantiate abstract class myConcreteClass_2 with abstract methods foo
我知道"Exception"以及因此的"myCustomException"没有属性"foo",那么为什么要实例化"myCustomException"呢?
I know that "Exception" and therefore "myCustomException" have no attribute "foo", so why am I getting away with instantiating "myCustomException"?
编辑:记录下来,这是我最后遇到的骇人解决方法.不是真正的等价物,但是可以满足我的目的.
For the record, this is the hackish workaround I ended up going with. Not truly equivalent, but works for my purposes.
# "abstract" base class
class MyBaseClass( Exception ):
def __init__(self):
if not hasattr(self, 'foo'):
raise NotImplementedError("Please implement abstract property foo")
class MyConcreteClass( MyBaseClass ):
pass
if __name__=='__main__':
a = MyConcreteClass()
print "We never reach here, which is good."
推荐答案
它看起来像是因为BaseException
的__new__
方法并不关心抽象方法/属性.
It looks like this is because the __new__
method for BaseException
doesn't care about abstract methods/properties.
当您尝试实例化myConcreteClass_1
时,它最终从Exception
类调用__new__
.当要实例化myConcreteClass_2
时,它将从object
调用__new__
:
When you try to instantiate myConcreteClass_1
, it ends up calling __new__
from the Exception
class. When want to instantiate myConcreteClass_2
, it calls the __new__
from object
:
>>> what.myConcreteClass_1.__new__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: exceptions.Exception.__new__(): not enough arguments
>>> what.myConcreteClass_2.__new__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object.__new__(): not enough arguments
The Exception
class doesn't provide a __new__
method, but it's parent, BaseException
, does:
static PyObject *
BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyBaseExceptionObject *self;
self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);
if (!self)
return NULL;
/* the dict is created on the fly in PyObject_GenericSetAttr */
self->dict = NULL;
self->traceback = self->cause = self->context = NULL;
self->suppress_context = 0;
if (args) {
self->args = args;
Py_INCREF(args);
return (PyObject *)self;
}
self->args = PyTuple_New(0);
if (!self->args) {
Py_DECREF(self);
return NULL;
}
return (PyObject *)self;
}
将此与object
的 __new__
实现进行比较:
Compare this to the __new__
implementation for object
:
static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
if (excess_args(args, kwds) &&
(type->tp_init == object_init || type->tp_new != object_new)) {
PyErr_SetString(PyExc_TypeError, "object() takes no parameters");
return NULL;
}
if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
PyObject *abstract_methods = NULL;
PyObject *builtins;
PyObject *sorted;
PyObject *sorted_methods = NULL;
PyObject *joined = NULL;
PyObject *comma;
_Py_static_string(comma_id, ", ");
_Py_IDENTIFIER(sorted);
/* Compute ", ".join(sorted(type.__abstractmethods__))
into joined. */
abstract_methods = type_abstractmethods(type, NULL);
if (abstract_methods == NULL)
goto error;
builtins = PyEval_GetBuiltins();
if (builtins == NULL)
goto error;
sorted = _PyDict_GetItemId(builtins, &PyId_sorted);
if (sorted == NULL)
goto error;
sorted_methods = PyObject_CallFunctionObjArgs(sorted,
abstract_methods,
NULL);
if (sorted_methods == NULL)
goto error;
comma = _PyUnicode_FromId(&comma_id);
if (comma == NULL)
goto error;
joined = PyUnicode_Join(comma, sorted_methods);
if (joined == NULL)
goto error;
PyErr_Format(PyExc_TypeError,
"Can't instantiate abstract class %s "
"with abstract methods %U",
type->tp_name,
joined);
error:
Py_XDECREF(joined);
Py_XDECREF(sorted_methods);
Py_XDECREF(abstract_methods);
return NULL;
}
return type->tp_alloc(type, 0);
}
如您所见,object.__new__
有一些代码可以在存在一些未重写的抽象方法时抛出错误,而BaseException.__new__
则不会.
As you can see object.__new__
has code to throw an error when there are abstract methods that aren't overridden, but BaseException.__new__
does not.
这篇关于Python abc模块:扩展抽象基类和异常派生类会导致令人惊讶的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!