2013-03-22 12 views
27

Interagirei con un sacco di json profondamente annidato che non ho scritto e vorrei rendere il mio script python più "permissivo" all'input non valido. Mi trovo a scrivere blocchi provati, tranne i blocchi, e preferisco semplicemente avvolgere la discutibile funzione.Decoratore generale per avvolgere prova tranne in python?

Capisco che sia una cattiva politica ingoiare le eccezioni, ma preferirei che fossero stampate e analizzate in seguito, piuttosto che interrompere l'esecuzione. È più utile, nel mio caso d'uso, continuare ad eseguire il ciclo piuttosto che ottenere tutte le chiavi.

Ecco quello che sto facendo ora:

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'] = '' 

Ecco cosa mi piacerebbe, (1):

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

o (2):

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

. ..dove posso racchiudere il singolo elemento dati (1), o una funzione master (2), in una funzione che trasforma le eccezioni di esecuzione-arresto in campi vuoti, stampati su stdo ut. Il primo sarebbe una sorta di salto in base all'elemento - dove quella chiave non è disponibile, si registra in bianco e si sposta - quest'ultimo è un salto di riga, dove se uno qualsiasi dei campi non funziona, l'intero record è saltato.

La mia comprensione è che una specie di wrapper dovrebbe essere in grado di risolvere questo problema. Ecco cosa ho provato, con un wrapper:

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

Ecco perché non funziona. Chiamare una funzione che non esiste, non try-catch via:

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

Prima ancora di aggiungere un valore di ritorno in bianco, mi piacerebbe farlo try-catch in modo corretto. Se la funzione avesse funzionato, questo avrebbe stampato "Errore", giusto?

Un wrapper funziona correttamente qui?

UPDATE

Ho avuto un sacco di veramente utile, risposte utili di seguito, e vi ringrazio per loro --- ma ho modificato gli esempi che ho usato in precedenza per illustrare che sto cercando per catturare più di errori di chiave annidati, che sto cercando specificamente per una funzione che avvolge un try-catch per ...

  1. Quando un metodo non esiste.
  2. Quando un oggetto non esiste e sta ricevendo un metodo chiamato su di esso.
  3. Quando un oggetto che non esiste viene chiamato come argomento per una funzione.
  4. Qualsiasi combinazione di una di queste cose.
  5. Bonus, quando una funzione non esiste.
+0

Per accedere JSON nidificato nello specifico, si potrebbe vuoi guardare [safeJSON] (https://github.com/NYTimes/safejson). Questo funziona avvolgendo efficacemente l'oggetto 'mioobject'. – BrenBarn

risposta

6

Dipende dalle eccezioni previste.

Se il caso d'uso è solo get(), si potrebbe fare

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

Per gli altri casi, il tuo approccio decoratore potrebbe essere utile, ma non nel modo in cui lo fai.

cercherò di mostrarvi:

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 

Tuttavia, f(some_undefined_function()) non funzionerà, perché

a) f() non è ancora attivo al momento executon e

b) è sbagliato. Il modo corretto sarebbe quello di avvolgere la funzione e quindi chiamarla: f(function_to_wrap)().

A "strato di lambda" aiuterebbe qui:

wrapped_f = f(lambda: my_function()) 

avvolge una funzione lambda che a sua volta definisce una funzione non esistente. Chiamare wrapped_f() porta a chiamare il wrapper che chiama il lambda che tenta di chiamare my_function(). Se questo non esiste, il lambda solleva un'eccezione che viene catturata dall'involucro.

Questo funziona perché il nome my_function non viene eseguito al momento della definizione del lambda, ma quando viene eseguito. E questa esecuzione è protetta e avvolta dalla funzione f() quindi. Quindi l'eccezione si verifica all'interno della lambda e viene propagata alla funzione di avvolgimento fornita dal decoratore, che la gestisce con garbo.

Questa mossa verso l'interno la funzione lambda non funziona se si tenta di sostituire la funzione lambda con un wrapper come

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

seguito da un

f(g(my_function))(arguments) 

perché qui la risoluzione del nome è "di nuovo in superficie": my_function non può essere risolto e ciò accade prima che vengano chiamati g() o f(). Quindi non funziona.

E se si tenta di fare qualcosa di simile

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

non può funzionare così se non avete x, perché g() protegge print, non x.

Se si desidera proteggere x qui, dovrete fare

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

perché l'involucro fornito da f() chiama quella funzione lambda che solleva un'eccezione che viene poi messo a tacere.

+0

Questo è stato utile, ma ci sto ancora lavorando. (Eccezione è errata sopra, ma ce l'ho fatta.) Prendendo questo un po 'più lontano, c'è un modo che, invece di avvolgere con lambda, potrei fare un'altra funzione, g() che fa quel lambda avvolgere per me su una funzione generica, inclusi * args e ** kwargs? In modo che potrei chiamare g (anyfunction()) e avrebbe l'effetto completo che sto cercando? – Mittenchops

+0

Quando l'ho provato, ho fatto '>>> wrapped_p = f (lambda * z, ** z: func (* z, ** z))' e ottenuto 'File" ", riga 1 Sintassi Errore: argomento duplicato 'z' nella definizione della funzione ' – Mittenchops

+0

Naturalmente; si potrebbe fare una funzione 'g = lambda: lambda * a, ** k: function (* a, ** k)' che ti permette di fare 'g (anyfunction)()'. (Solo in questo modo: non vuoi racchiudere il risultato della funzione, ma la funzione stessa.) – glglgl

8

nel tuo caso per prima cosa valuti il ​​valore della chiamata di miagolio (che non esiste) e poi avvolgilo nel decoratore. questo non funziona in questo modo.

prima viene sollevata l'eccezione prima che venisse spostata, quindi il wrapper è rientrato in modo errato (silenceit non deve restituire se stesso). Si potrebbe desiderare di fare qualcosa di simile:

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

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

softfail =f(hardfail) 

uscita:

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

comunque nel tuo caso non capisco il motivo per cui non si utilizza un metodo semplice, come

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

e nel codice:

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

Modificato:

Nel caso in cui si desideri qualcosa che funzioni a qualsiasi profondità. Si può fare qualcosa di simile:

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

Quello che si dice:

>>> 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} 

E utilizzando il codice

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

Tra l'altro, su un punto di vista personale mi piace anche la soluzione @cravoori con contextmanager. Ma questo significherebbe avere tre righe di codice ogni volta:

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

Grazie! Il ritorno nel posto sbagliato risolve quello che pensavo stavo facendo, ma non lo era. Ma sono ancora incerto su come l'eccezione interna di meow() venga chiamata prima dell'involucro. Il motivo per cui non posso usare il metodo semplice è che sto chiamando a diverse profondità, usando diversi oggetti con attributi diversi. Scriverò una funzione per incarico, che sarebbe ingombrante quanto cercare di afferrare. Quindi, sto cercando qualcosa che possa intercettare genericamente funzioni fallite e restituire '', stampando un errore su stdout. – Mittenchops

+0

ha apportato modifiche per mostrare una versione più generica – astreal

2

Perché non usare solo il ciclo?

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] = '' 

o se si desidera scrivere un piccolo aiutante:

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

Inoltre è possibile combinare entrambe le soluzioni, se si dispone di pochi posti in cui è necessario ottenere il valore e la funzione di supporto sarebbe più ragionevole.

Non sono sicuro che sia effettivamente necessario un decoratore per il tuo problema.

+0

Sto cercando di renderlo più generico, non solo per le chiavi mancanti profonde a un livello, ma con molte funzioni che possono non riuscire ad assegnare i dati per molte ragioni. – Mittenchops

27

È possibile utilizzare un defaultdict e the context manager approach as outlined in Raymond Hettinger's PyCon 2013 presentation

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'] 
+0

Hmm, è pulito. Ho intenzione di indagare su questo. Grazie. Il collegamento – Mittenchops

+0

non funziona. qualche idea dove trovarlo? – jdennison

+0

@jdennison, corretto il link – iruvar

14

E 'molto facile da raggiungere usando decoratore configurabile.

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) 

semplicemente passare a get_decorator tuple con i tipi di errore che si desidera mettere a tacere e il valore di default per tornare. uscita sarà

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

Edit: Grazie a Martineau ho cambiato il valore predefinito di errori di tuple con l'eccezione di base per previene errori.

+0

Perdonami per la mancanza di conoscenza qui. Sto tentando di usarlo nella mia sceneggiatura, ma non mi sta dando il risultato che sto cercando. Il mio errore è 'AttributeError:' NoneType 'oggetto non ha il testo dell'attributo' risultante da f (soup.find ("span", class _ = 'xxx'). Text). Sto definendo il decoratore come 'f = get_decorator (errors = (AttributeError,), default_value = "# NA")'. Cosa sto facendo di sbagliato qui? – MattV

+0

@MVersteeg: è necessario applicare '@ f' a una funzione che restituisce il valore della espressione' soup.find ("span", class _ = 'xxx'). Text' che sta generando l'eccezione - come mostrato nella esempi nella risposta. – martineau

+0

In suvated, anche se cambierei la firma di 'get_decorator()' a 'def get_decorator (default_value = '', * errors)' per semplificare la chiamata leggermente. – martineau

5

Poiché si ha a che fare con un sacco di codice guasto, in questo caso potrebbe essere scusabile utilizzare eval.

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

poi avvolgere tutte le sue dichiarazioni potenzialmente rotti:

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

Ci sono un sacco di buone risposte qui, ma non ho visto alcuna che affronta la questione se è possibile eseguire questa via decoratori.

La risposta breve è "no", almeno non senza modifiche strutturali al codice. I decoratori operano a livello di funzione, non su singole dichiarazioni. Pertanto, al fine di utilizzare decoratori, è necessario spostare ciascuna delle affermazioni per decorare la propria funzione.

Ma si noti che non si può semplicemente mettere il compito stesso all'interno della funzione decorata. È necessario restituire l'espressione rhs (il valore da assegnare) dalla funzione decorata, quindi eseguire l'assegnazione all'esterno.

Per mettere questo in termini di vostro codice di esempio, si potrebbe scrivere il codice con il seguente schema:

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

item["a"] = computeA() 

return_on_failure potrebbe essere qualcosa di simile:

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

    return applicator 

    return decorate