2017-07-10 2 views
1

Supposons que je crée une classe Python régulière qui possède une propriété, et dans l'implémentation de cette propriété, je fais une erreur qui mène à une erreur AttributeError. Un MVCE est la suivante:Message d'erreur trompeur lorsque AttributeError est déclenché dans la propriété de classe dérivée de QObject

class MyClass(): 

    @property 
    def myProp(self): 
     raise AttributeError("my mistake") 

def main(): 
    # Gives a 'expected-error-message' as expected 
    myObject = MyClass() 
    print("Regular object property: {}".format(myObject.myProp)) 


if __name__ == "__main__": 
    main() 

Cela donne l'erreur suivante, comme prévu:

Traceback (most recent call last): 
    File "prop_regular.py", line 14, in <module> 
    main() 
    File "prop_regular.py", line 10, in main 
    print("Regular object property: {}".format(myObject.myProp)) 
    File "prop_regular.py", line 5, in myProp 
    raise AttributeError("my mistake") 
AttributeError: my mistake 

Cependant, si je laisse le Hériter de la classe de QObject, l'erreur est source de confusion. Par exemple exécutant le code suivant

from PyQt5 import QtCore 

class MyQtClass(QtCore.QObject): 

    @property 
    def myProp(self): 
     raise AttributeError("my-mistake") 


def main(): 
    app = QtCore.QCoreApplication([]) 

    # Gives confusing error message: 'MyQtClass' object has no attribute 'myProp' 
    qc = MyQtClass() 
    print("Qt object property: {}".format(qc.myProp)) 


if __name__ == "__main__": 
    main() 

donne

Traceback (most recent call last): 
    File "prop_qt.py", line 19, in <module> 
    main() 
    File "prop_qt.py", line 15, in main 
    print("Qt object property: {}".format(qc.myProp)) 
AttributeError: 'MyQtClass' object has no attribute 'myProp' 

Mais la MyQtClass classe ne ont une propriété myProp, il contient juste un bug! Cela m'a pris un moment pour déboguer dans une application réelle. Donc, ma question est la suivante: que se passe-t-il ici? Est-ce un bug dans PyQt? Ou est-ce que je fais quelque chose de mal?

EDIT:

réponse de Ekhumoro m'a incité à regarder dans la source PyQt (5,6). Il semble que l'erreur provient de QtCore/qpycore_qobject_getattr.cpp, qui définit la fonction suivante

// See if we can find an attribute in the Qt meta-type system. This is 
// primarily to support access to JavaScript (e.g. QQuickItem) so we don't 
// support overloads. 
PyObject *qpycore_qobject_getattr(const QObject *qobj, PyObject *py_qobj, 
     const char *name) 
{ 
    const QMetaObject *mo = qobj->metaObject(); 

    // Try and find a method with the name. 
    QMetaMethod method; 
    int method_index = -1; 

    // Count down to allow overrides (assuming they are possible). 
    for (int m = mo->methodCount() - 1; m >= 0; --m) 
    { 
     method = mo->method(m); 

     if (method.methodType() == QMetaMethod::Constructor) 
      continue; 

     // Get the method name. 
     QByteArray mname(method.methodSignature()); 
     int idx = mname.indexOf('('); 

     if (idx >= 0) 
      mname.truncate(idx); 

     if (mname == name) 
     { 
      method_index = m; 
      break; 
     } 
    } 

    if (method_index >= 0) 
    { 
     // Get the value to return. Note that this is recreated each time. We 
     // could put a descriptor in the type dictionary to satisfy the request 
     // in future but the typical use case is getting a value from a C++ 
     // proxy (e.g. QDeclarativeItem) and we can't assume that what is being 
     // proxied is the same each time. 
     if (method.methodType() == QMetaMethod::Signal) 
     { 
      // We need to keep explicit references to the unbound signals 
      // (because we don't use the type dictionary to do so) because they 
      // own the parsed signature which may be needed by a PyQtSlotProxy 
      // at some point. 
      typedef QHash<QByteArray, PyObject *> SignalHash; 

      static SignalHash *sig_hash = 0; 

      // For crappy compilers. 
      if (!sig_hash) 
       sig_hash = new SignalHash; 

      PyObject *sig_obj; 

      QByteArray sig_str = method.methodSignature(); 

      SignalHash::const_iterator it = sig_hash->find(sig_str); 

      if (it == sig_hash->end()) 
      { 
       sig_obj = (PyObject *)qpycore_pyqtSignal_New(
         sig_str.constData()); 

       if (!sig_obj) 
        return 0; 

       sig_hash->insert(sig_str, sig_obj); 
      } 
      else 
      { 
       sig_obj = it.value(); 
      } 

      return qpycore_pyqtBoundSignal_New((qpycore_pyqtSignal *)sig_obj, 
        py_qobj, const_cast<QObject *>(qobj)); 
     } 

     // Respect the 'private' nature of __ names. 
     if (name[0] != '_' || name[1] != '_') 
     { 
      QByteArray py_name(Py_TYPE(py_qobj)->tp_name); 
      py_name.append('.'); 
      py_name.append(name); 

      return qpycore_pyqtMethodProxy_New(const_cast<QObject *>(qobj), 
        method_index, py_name); 
     } 
    } 

    // Replicate the standard Python exception. 
    PyErr_Format(PyExc_AttributeError, "'%s' object has no attribute '%s'", 
      Py_TYPE(py_qobj)->tp_name, name); 

    return 0; 
} 

Si aucune méthode de ce nom se trouve dans le système de méta type Qt, ce message d'erreur est soulevée. Je suppose qu'il serait difficile de faire autre chose en effet.

+0

Je suis curieux de savoir pourquoi vous n'avez pas répondu à la réponse que j'ai donnée. Êtes-vous en désaccord avec le fait que cela touche tous les points de votre question? – ekhumoro

+0

Ouais. J'étais occupé et pouvais continuer mon travail sans connaître la réponse, alors ça m'a échappé. Merci d'avoir suivi. – titusjan

Répondre

1

Ceci est normal python behaviour lorsqu'une classe définit __getattr__, car il doit être appelé à chaque fois qu'un AttributeError est soulevé:

>>> class MyClass(): 
...  @property 
...  def myProp(self): 
...   raise AttributeError("my mistake") 
...  def __getattr__(self, name): 
...   raise AttributeError("no attribute %r" % name) 
... 
>>> x = MyClass() 
>>> x.myProp 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 6, in __getattr__ 
AttributeError: no attribute 'myProp' 

L'exception d'origine se propage s'il n'y a pas __getattr__ défini; sinon, l'original est avalé et l'exception déclenchée par __getattr__ est propagée à la place.

Ce qui implique que toutes les classes PyQt dérivées de QObject doivent définir __getattr__.

+1

Je pense que vous avez raison, 'QObject .__ getattr__' semble être défini dans la source PyQt. Voir ma modification. – titusjan