it-swarm.com.de

Allgemeiner Dekorateur zum Einwickeln, außer in Python?

Ich habe mit einer Menge tief verschachtelter Json interagiert, die ich nicht geschrieben habe, und ich möchte mein Python-Skript für ungültiger Eingabe "verzeihen". Ich schreibe mit Try-Exceptor-Blöcken und möchte die zweifelhafte Funktion nur noch zusammenfassen.

Ich verstehe, dass es eine schlechte Politik ist, Ausnahmen zu schlucken, aber ich möchte lieber, dass sie später gedruckt und analysiert werden, als die Ausführung tatsächlich zu stoppen. In meinem Anwendungsfall ist es wertvoller, die Ausführung über die Schleife hinweg fortzusetzen, als alle Schlüssel abzurufen.

Folgendes mache ich jetzt:

try:
    item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST()
except:
    item['a'] = ''
try:
    item['b'] = OBJECT_THAT_DOESNT_EXIST.get('key2')
except:
    item['b'] = ''
try:
    item['c'] = func1(ARGUMENT_THAT_DOESNT_EXIST)
except:
    item['c'] = ''
...
try:
    item['z'] = FUNCTION_THAT_DOESNT_EXIST(myobject.method())
except:
    item['z'] = ''

Folgendes würde ich gerne tun (1):

item['a'] = f(myobject.get('key').get('subkey'))
item['b'] = f(myobject.get('key2'))
item['c'] = f(func1(myobject)
...

oder (2):

@f
def get_stuff():
   item={}
   item['a'] = myobject.get('key').get('subkey')
   item['b'] = myobject.get('key2')
   item['c'] = func1(myobject)
   ...
   return(item)

... wo ich entweder das einzelne Datenelement (1) oder eine Master-Funktion (2) in einer Funktion einpacken kann, die ausnahmebedingte Ausnahmen in leere Felder umwandelt, die in stdout gedruckt werden. Ersteres wäre so etwas wie ein artikelweises Überspringen - wenn der Schlüssel nicht verfügbar ist, wird er leer protokolliert und weitergeschoben -, letzteres ist ein Überspringen von Zeilen, wobei, wenn eines der Felder nicht funktioniert, der gesamte Datensatz ist übersprungen.

Meines Wissens ist es so, dass eine Art Wrapper das beheben kann. Ich habe es mit einem Wrapper versucht:

def f(func):
   def silenceit():
      try:
         func(*args,**kwargs)
      except:
         print('Error')
      return(silenceit)

Deshalb funktioniert es nicht. Rufen Sie eine Funktion auf, die nicht existiert.

>>> f(meow())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'meow' is not defined

Bevor ich überhaupt einen leeren Rückgabewert hinzufüge, möchte ich, dass der Try-Catch korrekt ausgeführt wird. Wenn die Funktion funktioniert hätte, hätte dies "Fehler" ausgegeben, oder? 

Ist eine Wrapper-Funktion hier der richtige Ansatz?

UPDATE

Ich habe hier eine Menge wirklich nützlicher und hilfreicher Antworten erhalten, und ich danke Ihnen dafür. Aber ich habe die oben beschriebenen Beispiele bearbeitet, um zu zeigen, dass ich versuche, mehr als verschachtelte Schlüsselfehler zu finden, dass ich Ich suche speziell nach einer Funktion, die einen Try-Catch für ...

  1. Wenn es keine Methode gibt 
  2. Wenn ein Objekt nicht existiert und eine Methode dafür abgerufen wird. 
  3. Wenn ein Objekt, das nicht existiert, als Argument einer Funktion aufgerufen wird. 
  4. Jede Kombination dieser Dinge. 
  5. Bonus, wenn eine Funktion nicht existiert.
33
Mittenchops

Sie können ein Defaultdict und den Kontext-Manager-Ansatz verwenden, wie in der PyCon 2013-Präsentation von Raymond Hettinger beschrieben

from collections import defaultdict
from contextlib import contextmanager

@contextmanager
def ignored(*exceptions):
  try:
    yield
  except exceptions:
    pass 

item = defaultdict(str)

obj = dict()
with ignored(Exception):
  item['a'] = obj.get(2).get(3) 

print item['a']

obj[2] = dict()
obj[2][3] = 4

with ignored(Exception):
  item['a'] = obj.get(2).get(3) 

print item['a']
30
iruvar

Hier gibt es viele gute Antworten, aber ich habe nicht die Antwort darauf gesehen, ob Sie dies über Dekorateure erreichen können.

Die kurze Antwort lautet "Nein", zumindest nicht ohne strukturelle Änderungen an Ihrem Code. Dekorateure arbeiten auf der Funktionsebene und nicht auf einzelnen Anweisungen. Um Dekoratoren verwenden zu können, müssen Sie daher jede der zu dekorierenden Anweisungen in ihre eigene Funktion verschieben.

Beachten Sie jedoch, dass Sie die Zuweisung selbst nicht einfach in die dekorierte Funktion einfügen können. Sie müssen den rhs-Ausdruck (den zuzuweisenden Wert) von der dekorierten Funktion zurückgeben und dann die Zuweisung außerhalb vornehmen.

Um dies in Bezug auf Ihren Beispielcode zu beschreiben, könnte man Code mit dem folgenden Muster schreiben:

@return_on_failure('')
def computeA():
    item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST()

item["a"] = computeA()

return_on_failure könnte so aussehen:

def return_on_failure(value):
  def decorate(f):
    def applicator(*args, **kwargs):
      try:
         f(*args,**kwargs)
      except:
         print('Error')

    return applicator

  return decorate
19
Nathan Davis

Mit dem konfigurierbaren Dekorator ist dies sehr einfach zu erreichen.

def get_decorator(errors=(Exception, ), default_value=''):

    def decorator(func):

        def new_func(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except errors, e:
                print "Got error! ", repr(e)
                return default_value

        return new_func

    return decorator

f = get_decorator((KeyError, NameError), default_value='default')
a = {}

@f
def example1(a):
    return a['b']

@f
def example2(a):
    return doesnt_exist()

print example1(a)
print example2(a)

Übergeben Sie einfach an get_decorator-Tupel mit Fehlertypen, die Sie stummschalten möchten, und geben Sie den Standardwert an ..__ zurück. Die Ausgabe erfolgt

Got error!  KeyError('b',)
default
Got error!  NameError("global name 'doesnt_exist' is not defined",)
default

Edit: Dank martineau habe ich den voreingestellten Fehler in Tupel mit der grundlegenden Ausnahme geändert, um Fehler zu vermeiden.

16
Sergeev Andrew

in Ihrem Fall bewerten Sie zuerst den Wert des Meow-Aufrufs (der nicht existiert) und wickeln ihn dann in den Dekorateur ein. das funktioniert nicht so.

zuerst wird die Ausnahme ausgelöst, bevor sie verpackt wurde, dann wird der Wrapper falsch eingerückt (silenceit sollte sich nicht zurückgeben). Möglicherweise möchten Sie Folgendes tun:

def hardfail():
  return meow() # meow doesn't exist

def f(func):
  def wrapper():
    try:
      func()
    except:
      print 'error'
  return wrapper

softfail =f(hardfail)

ausgabe:

>>> softfail()
error

>>> hardfail()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in hardfail
NameError: global name 'meow' is not defined

ich verstehe sowieso in Ihrem Fall nicht, warum Sie keine einfache Methode wie verwenden

def get_subkey(obj, key, subkey):
  try:
    return obj.get(key).get(subkey, '')
  except AttributeError:
    return ''

und im Code:

 item['a'] = get_subkey(myobject, 'key', 'subkey')

bearbeitet:

Wenn Sie etwas wollen, das in jeder Tiefe funktioniert. Sie können so etwas tun:

def get_from_object(obj, *keys):
  try:
    value = obj
    for k in keys:
        value = value.get(k)
    return value
  except AttributeError:
    return ''

Das würdest du anrufen:

>>> d = {1:{2:{3:{4:5}}}}
>>> get_from_object(d, 1, 2, 3, 4)
5
>>> get_from_object(d, 1, 2, 7)
''
>>> get_from_object(d, 1, 2, 3, 4, 5, 6, 7)
''
>>> get_from_object(d, 1, 2, 3)
{4: 5}

Und mit deinem Code

item['a'] = get_from_object(obj, 2, 3) 

Ich persönlich mag die @cravoori-Lösung auch aus persönlicher Sicht mithilfe von contextmanager. Dies würde jedoch bedeuten, jedes Mal drei Codezeilen zu haben:

item['a'] = ''
with ignored(AttributeError):
  item['a'] = obj.get(2).get(3) 
8
astreal

Es hängt davon ab, welche Ausnahmen Sie erwarten.

Wenn Ihr einziger Anwendungsfall get() ist, können Sie dies tun

item['b'] = myobject.get('key2', '')

In den anderen Fällen kann Ihr Dekorateuransatz nützlich sein, jedoch nicht in der Art und Weise, wie Sie dies tun.

Ich versuche es dir zu zeigen:

def f(func):
   def silenceit(*args, **kwargs): # takes all kinds of arguments
      try:
         return func(*args, **kwargs) # returns func's result
      except Exeption, e:
         print('Error:', e)
         return e # not the best way, maybe we'd better return None
                  # or a wrapper object containing e.
  return silenceit # on the correct level

Trotzdem funktioniert f(some_undefined_function()) nicht, weil

a) f() ist zur Ausführungszeit noch nicht aktiv und

b) es wird falsch verwendet. Der richtige Weg wäre, die Funktion einzuhüllen und dann aufzurufen: f(function_to_wrap)().

Eine "Lambda-Schicht" würde hier helfen:

wrapped_f = f(lambda: my_function())

umschließt eine Lambda-Funktion, die wiederum eine nicht vorhandene Funktion aufruft. Das Aufrufen von wrapped_f() führt zum Aufruf des Wrappers, der das Lambda aufruft, das versucht, my_function() aufzurufen. Wenn dies nicht existiert, löst das Lambda eine Ausnahme aus, die vom Wrapper abgefangen wird.

Dies funktioniert, weil der Name my_function nicht zum Zeitpunkt der Definition des Lambda ausgeführt wird, sondern wenn es ausgeführt wird. Diese Ausführung wird dann durch die Funktion f() geschützt und eingeschlossen. Die Ausnahme tritt also innerhalb des Lambdas auf und wird an die vom Dekorateur bereitgestellte Wrapping-Funktion weitergegeben, die es elegant behandelt.

Diese Bewegung in Richtung der Lambda-Funktion funktioniert nicht, wenn Sie versuchen, die Lambda-Funktion durch einen Wrapper wie zu ersetzen

g = lambda function: lambda *a, **k: function(*a, **k)

gefolgt von einem

f(g(my_function))(arguments)

denn hier ist die Namensauflösung "zurück an der Oberfläche": my_function kann nicht aufgelöst werden, und dies geschieht, bevor g() oder sogar f() aufgerufen werden. Also klappt es nicht.

Und wenn Sie versuchen, so etwas zu tun

g(print)(x.get('fail'))

es kann auch nicht funktionieren, wenn Sie keine x haben, da g()print schützt, nicht x.

Wenn Sie x hier schützen möchten, müssen Sie dies tun

value = f(lambda: x.get('fail'))

weil der von f() bereitgestellte Wrapper diese Lambda-Funktion aufruft, die eine Ausnahme auslöst, die dann stummgeschaltet wird.

8
glglgl

Da es sich um eine Menge defekten Codes handelt, kann in diesem Fall eval verwendet werden.

def my_eval(code):
  try:
    return eval(code)
  except:  # Can catch more specific exceptions here.
    return ''

Wickeln Sie dann alle möglicherweise defekten Anweisungen ein:

item['a'] = my_eval("""myobject.get('key').get('subkey')""")
item['b'] = my_eval("""myobject.get('key2')""")
item['c'] = my_eval("""func1(myobject)""")
5
Addison

Warum nicht einfach den Zyklus benutzen?

for dst_key, src_key in (('a', 'key'), ('b', 'key2')):
    try:
        item[dst_key] = myobject.get(src_key).get('subkey')
    except Exception:  # or KeyError?
        item[dst_key] = ''

Oder wenn Sie möchten, schreiben Sie einen kleinen Helfer:

def get_value(obj, key):
    try:
        return obj.get(key).get('subkey')
    except Exception:
        return ''

Sie können auch beide Lösungen kombinieren, wenn Sie ein paar Stellen haben, an denen Sie Wert schaffen müssen, und die Helferfunktion sinnvoller wäre.

Sie sind sich nicht sicher, ob Sie tatsächlich einen Dekorateur für Ihr Problem benötigen.

2
simplylizz

Erweiterung der @iruvar-Antwort - ab Python 3.4 gibt es in Python-Standardlib einen vorhandenen Kontextmanager: https://docs.python.org/3/library/contextlib.html#contextlib.suppress

from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

with suppress(FileNotFoundError):
    os.remove('someotherfile.tmp')
0
Pax0r