2014-07-17 3 views
6

Die Erweiterung einer abstrakten Basisklasse und einer von "object" abgeleiteten Klasse funktioniert wie erwartet: wenn Sie nicht implementiert haben Bei allen abstrakten Methoden und Eigenschaften erhalten Sie einen Fehler.Python abc-Modul: Erweiterung einer abstrakten Basisklasse und einer ausnahmebedingten Klasse führt zu einem überraschenden Verhalten

Seltsamerweise können Sie durch das Ersetzen der vom Objekt abgeleiteten Klasse durch eine Klasse, die "Exception" erweitert, Instanzen von Klassen erstellen, die nicht alle erforderlichen abstrakten Methoden und Eigenschaften implementieren.

Zum Beispiel:

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." 

... Ausbeuten ...

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 

Ich weiß, dass "Ausnahme" und deshalb "myCustomException" haben kein Attribut "foo", also warum bin ich weg mit der Instanziierung "myCustomException"?

EDIT: Für die Aufzeichnung ist dies die hackish Workaround, die ich mit ging. Nicht wirklich gleichwertig, aber funktioniert für meine Zwecke.

# "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." 

Antwort

3

Es sieht so aus, weil die __new__ Methode für BaseException nicht über abstrakte Methoden/Eigenschaften schert.

Wenn Sie versuchen, myConcreteClass_1 instanziieren, ruft es __new__ aus der Exception Klasse. Wenn wollen myConcreteClass_2 instanziiert, ruft es die __new__ von 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 

Die Exception Klasse bietet keine __new__ Methode, aber es ist Eltern, 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; 
} 

diese Vergleiche zum __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); 
} 

Wie Sie sehen können object.__new__ hat einen Code, der einen Fehler auslöst, wenn es abstrakte Methoden gibt, die nicht überschrieben werden, aber BaseException.__new__ nicht.

+0

Danke! Das ist interessant. Ich kann mir keine Problemumgehung für das, was ich versuche, vorstellen, die nicht hack-ish ist. –

+1

Verwandte Themen für Menschen mit diesem Problem: [hier] (http://stackoverflow.com/questions/2862153/python-2-6-3-abstract-base-class-misunderstanding/2862419#2862419) und [hier] (http://stackoverflow.com/questions/20107530/can-one-declare-an-abstract-exception-in-python) –

Verwandte Themen