2014-10-24 6 views
7

Angenommen, wir versuchen, ein nicht vorhandenes Attribut zuzugreifen:Wie hält das Objekt erhalten ein Attribut fehlt

>>> {'foo': 'bar'}.gte('foo') # well, I meant “get”! 

Pythons AttributeError hat nur das Attribut args mit einem String der fertige Fehlermeldung enthält: 'dict' object has no attribute 'gte'

Verwenden Sie die Module inspect und/oder traceback mit sys.last_traceback, gibt es eine Möglichkeit, das tatsächliche dict-Objekt zu erhalten?

>>> offending_object = get_attributeerror_obj(sys.last_traceback) 
>>> dir(offending_object) 
[... 
'clear', 
'copy', 
'fromkeys', 
'get',  # ah, here it is! 
'items', 
...] 

Edit: da die Katze aus dem Sack ist sowieso, werde ich meine Erkenntnisse und Code teilen (bitte nicht dieses lösen und zu PyPI, bitte unterbreiten;)

) Die AttributeError wird erstellt here, die zeigt, dass es eindeutig keinen Verweis auf das ursprüngliche Objekt gibt.

Hier ist der Code mit der gleichen Platzhalter-Funktion:

import sys 
import re 
import difflib 

AE_MSG_RE = re.compile(r"'(\w+)' object has no attribute '(\w+)'") 

def get_attributeerror_obj(tb): 
    ??? 

old_hook = sys.excepthook 

def did_you_mean_hook(type, exc, tb): 
    old_hook(type, exc, tb) 

    if type is AttributeError: 
     match = AE_MSG_RE.match(exc.args[0]) 
     sook = match.group(2) 

     raising_obj = get_attributeerror_obj(tb) 

     matches = difflib.get_close_matches(sook, dir(raising_obj)) 
     if matches: 
      print('\n\nDid you mean?', matches[0], file=sys.stderr) 

sys.excepthook = did_you_mean_hook 
+0

Willst du etwas sagen wie den Entwickler fragen "meintest du statt GTE"? – karthikr

+3

Versuchen, Ruby zu replizieren [hattest du ...] (http://www.yukinishijima.net/2014/10/21/did-you-mean-experience-in-ruby.html) Funktionalität, wie? :-) – Kevin

+0

@Kevin: das wäre ein tolles Paket auf PyPI! [DidYouMean] (https://pypi.python.org/pypi/DidYouMean/) existiert, es geht aber um Rechtschreibprüfung, nicht um Code. – ojdo

Antwort

1

Es ist nicht die Antwort, die Sie wollen, aber ich bin mir ziemlich sicher, dass Sie kann nicht ... zumindest nicht mit sys.excepthook. Dies liegt daran, dass die Referenzzählungen dekrementiert werden, wenn der Rahmen abgewickelt wird, so dass es vollkommen gültig ist, dass das Objekt als Garbage Collected erfasst wird, bevor sys.excepthook aufgerufen wird. Genau das passiert in CPython:

Das heißt, es ist nicht immer der Fall. Da die Ausnahme-Objekt auf dem Rahmen, wenn Ihr Code wie folgt aussieht:

def error(): 
    x = X() 
    x.wrong 

x kann noch nicht gesammelt werden. x gehört dem Frame und der Frame ist am Leben. Aber da ich bereits bewiesen habe, dass auf dieses Objekt nicht explizit Bezug genommen wird, ist es nicht immer offensichtlich, was zu tun ist. Zum Beispiel

def error(): 
    foo().wrong 

können oder nicht ein Objekt, das überlebt hat, und den einzig gangbaren Weg ist, um herauszufinden, foo zu laufen ... aber selbst dann haben Sie Probleme mit Nebenwirkungen.

Also nein, das ist nicht möglich. Wenn es Ihnen nichts ausmacht, irgendwelche Längen zu nehmen, werden Sie wahrscheinlich am Ende die AST beim Laden neu schreiben müssen (ähnlich wie FuckIt.py). Das willst du aber nicht tun.


Mein Vorschlag wäre, zu versuchen, einen Linter mit den Namen aller bekannten Klassen und ihre Methoden zu erhalten. Sie können dies verwenden, um die Trace-Back-Zeichenfolge rückzuentwickeln, um die Klasse und die falsche Methode abzurufen, und dann eine Fuzzy-Übereinstimmung ausführen, um den Vorschlag zu finden.

+0

Danke, das ist eine gute Antwort. Es kam mir nicht einmal in den Sinn, die Klasse mit ihrem Namen einfach aus dem Rahmen zu holen, und Sie erklären gut, warum das Objekt nicht abrufbar ist. Ich habe eine Funktion Anfrage für CPython und werde tun, was Sie vorgeschlagen –

1

Hinzufügen meiner 2 Cent, wie ich erfolgreich (bisher) versucht, etwas ähnliches für DidYouMean-Python zu tun.

Der Trick hier ist, dass es ziemlich genau der eine Fall ist, in dem die Fehlermeldung genug Informationen enthält, um zu folgern, was Sie eigentlich meinten. Tatsächlich kommt es hier darauf an, dass Sie versucht haben, gte auf einem dict Objekt aufzurufen: Sie benötigen den Typ, nicht das Objekt selbst.

Wenn Sie {'foo': 'bar'}.get('foob') geschrieben hätten, wäre die Situation viel schwieriger zu handhaben und ich würde mich freuen, wenn jemand eine Lösung hätte.

Schritt eins

Überprüfen Sie, ob Sie ein Attribute sind Handling (das erste Argument des Hakens verwendet wird).

Schritt zwei

Abrufen der relevanten Informationen aus der Nachricht (das zweite Argument verwendet wird). Ich habe das mit Regexp gemacht. Bitte beachten Sie, dass diese Ausnahme von der Version von Python mehrere Formen annehmen kann je nach dem Objekt, das Sie auf die Methode aufrufen, usw.

Bisher mein regexp ist: "^'?(\w+)'? (?:object|instance) has no attribute '(\w+)'$"

Schritt drei

Ruft das Typobjekt ab, das dem Typ entspricht ('dict' in Ihrem Fall), so dass Sie dir() darauf aufrufen können. Eine schmutzige Lösung wäre einfach eval(type), aber Sie können besser und sauberer durch die Wiederverwendung der Informationen in der Ablaufverfolgung (drittes Argument Ihres Hooks): das letzte Element der Ablaufverfolgung enthält den Rahmen, in dem die Ausnahme aufgetreten ist und in diesem Rahmen Typ wurde ordnungsgemäß definiert (entweder als lokaler Typ, als globaler Typ oder als Built-In).

Sobald Sie den Typ Objekt haben, müssen Sie nur anrufen dir() darauf und extrahieren Sie den Vorschlag, der Ihnen am besten gefällt.

Bitte lassen Sie mich wissen, wenn Sie weitere Informationen zu meiner Arbeit benötigen.

+0

das gleiche wie '{'foo': 'bar'}. Get ('foob')' wäre ein Fall, wo der Typ nicht im Traceback-Frame ist ... Ich hoffe, dass [PEP 437] (http://legacy.python.org/dev/peps/pep-0473/) implementiert wird! –

Verwandte Themen