web-dev-qa-db-fra.com

Décorateur général à essayer sauf en python?

J'interagissais avec un grand nombre de fichiers json profondément imbriqués que je n'avais pas écrits et j'aimerais rendre mon script python plus "tolérant" en entrée non valide. Je me trouve impliqué dans l'écriture d'essai, sauf les blocs, et je préférerais simplement envelopper la fonction douteuse.

Je comprends que c’est une mauvaise politique d’avaler les exceptions, mais je préférerais qu’elles soient imprimées et analysées plus tard que d’arrêter réellement l’exécution. Dans mon cas d'utilisation, il est plus utile de continuer à exécuter en boucle que d'obtenir toutes les clés.

Voici ce que je fais maintenant:

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

Voici ce que je voudrais, (1):

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

ou (2):

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

... où je peux envelopper soit la donnée unique (1), soit une fonction maître (2), dans une fonction qui transforme les exceptions bloquant l'exécution en champs vides, imprimés sur la sortie standard. Le premier serait en quelque sorte un saut d’objet - lorsque cette clé n’est pas disponible, il enregistre des blancs et passe à autre chose - le dernier est un saut de ligne, où si l’un des champs ne fonctionne pas, l’enregistrement complet sauté.

D'après ce que je comprends, une sorte de wrapper devrait pouvoir résoudre ce problème. Voici ce que j'ai essayé, avec un wrapper:

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

Voici pourquoi cela ne fonctionne pas. Appelez une fonction qui n'existe pas, elle n'essaie pas de l'attraper:

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

Avant même d’ajouter une valeur de retour vide, je voudrais l’obtenir correctement. Si la fonction avait fonctionné, cela aurait imprimé "Erreur", non? 

Est-ce qu'une fonction d'emballage est la bonne approche ici?

METTRE &AGRAVE; JOUR

J'ai eu beaucoup de réponses utiles et utiles ci-dessous, et merci pour elles --- mais j'ai modifié les exemples que j'ai utilisés ci-dessus pour illustrer le fait que j'essaie d'attraper plus que des erreurs clés imbriquées, que je ' m cherche spécifiquement une fonction qui enveloppe un try-catch pour ...

  1. Quand une méthode n'existe pas. 
  2. Lorsqu'un objet n'existe pas et qu'une méthode est appelée dessus. 
  3. Lorsqu'un objet qui n'existe pas est appelé en tant qu'argument d'une fonction. 
  4. N'importe quelle combinaison de ces choses. 
  5. Bonus, lorsqu'une fonction n'existe pas.
33
Mittenchops

Vous pouvez utiliser une approche defaultdict et le gestionnaire de contexte décrit dans la présentation PyCon 2013 de Raymond Hettinger

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

Il y a beaucoup de bonnes réponses ici, mais je n'en ai pas vu qui aborde la question de savoir si vous pouvez accomplir cela via des décorateurs.

La réponse courte est "non", du moins pas sans modifications structurelles de votre code. Les décorateurs opèrent au niveau de la fonction, pas sur des déclarations individuelles. Par conséquent, pour utiliser des décorateurs, vous devez déplacer chaque déclaration à décorer dans sa propre fonction.

Mais notez que vous ne pouvez pas simplement placer la tâche dans la fonction décorée. Vous devez renvoyer l'expression rhs (la valeur à affecter) à partir de la fonction décorée, puis effectuer l'affectation à l'extérieur.

Pour mettre cela en termes de code d'exemple, on pourrait écrire du code avec le motif suivant:

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

item["a"] = computeA()

return_on_failure pourrait être quelque chose comme:

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

Il est très facile à réaliser en utilisant un décorateur configurable.

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)

Il suffit de passer aux tuples get_decorator avec les types d’erreur que vous voulez couper et la valeur par défaut à renvoyer .

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

Edit: / Grâce à martineau, j’ai modifié la valeur par défaut des erreurs en tuples avec exception de base pour éviter les erreurs.

16
Sergeev Andrew

dans votre cas, vous évaluez d’abord la valeur de l’appel miaou (qui n’existe pas), puis vous l’enveloppez dans le décorateur. ça ne marche pas comme ça.

l’exception est d’abord levée avant l’emballage, puis l’emballage est indenté à tort (silenceit ne doit pas se retourner lui-même). Vous voudrez peut-être faire quelque chose comme:

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

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

softfail =f(hardfail)

sortie:

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

de toute façon dans votre cas, je ne comprends pas pourquoi vous n'utilisez pas une méthode simple telle que

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

et dans le code:

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

Édité:

Si vous voulez quelque chose qui fonctionnera à n'importe quelle profondeur. Vous pouvez faire quelque chose comme:

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

Que tu appellerais:

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

Et en utilisant votre code

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

En passant, personnellement, j'aime aussi la solution @cravoori en utilisant contextmanager. Mais cela signifierait avoir trois lignes de code à chaque fois:

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

Cela dépend de quelles exceptions vous vous attendez.

Si votre seul cas d'utilisation est get(), vous pouvez le faire.

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

Dans les autres cas, votre approche de décorateur pourrait être utile, mais pas dans la manière dont vous le faites.

Je vais essayer de vous montrer:

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

Néanmoins, f(some_undefined_function()) ne fonctionnera pas, car

a) f() n'est pas encore actif au moment de l'exécution et

b) il est mal utilisé. La bonne façon serait d'encapsuler la fonction et de l'appeler ensuite: f(function_to_wrap)().

Une "couche de lambda" aiderait ici:

wrapped_f = f(lambda: my_function())

enveloppe une fonction lambda qui appelle à son tour une fonction non existante. L'appel de wrapped_f() conduit à appeler le wrapper qui appelle le lambda qui tente d'appeler my_function(). Si cela n'existe pas, le lambda déclenche une exception qui est interceptée par le wrapper.

Cela fonctionne car le nom my_function n'est pas exécuté au moment où le lambda est défini, mais au moment où il est exécuté. Et cette exécution est alors protégée et encapsulée par la fonction f(). Ainsi, l'exception se produit à l'intérieur du lambda et est propagée à la fonction d'emballage fournie par le décorateur, qui la gère normalement.

Ce mouvement vers la fonction lambda ne fonctionne pas si vous essayez de remplacer la fonction lambda par un wrapper comme

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

suivi d'un

f(g(my_function))(arguments)

parce qu'ici la résolution de nom est "de retour à la surface": my_function ne peut pas être résolu et cela se produit avant l'appel de g() ou même f(). Donc ça ne marche pas.

Et si vous essayez de faire quelque chose comme

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

cela ne peut pas fonctionner aussi bien si vous n'avez pas x, car g() protège print, pas x.

Si vous voulez protéger x ici, vous devrez faire

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

parce que le wrapper fourni par f() appelle cette fonction lambda qui déclenche une exception qui est ensuite désactivée.

8
glglgl

Étant donné que vous avez beaucoup de codes cassés, il peut être excusable d’utiliser eval dans ce cas.

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

Enveloppez ensuite toutes vos déclarations potentiellement brisées:

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

Pourquoi ne pas simplement utiliser le cycle?

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

Ou si vous souhaitez écrire un petit assistant:

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

Vous pouvez également combiner les deux solutions si vous avez quelques endroits où vous devez obtenir de la valeur et que la fonction d'assistance serait plus raisonnable.

Pas sûr que vous ayez réellement besoin d'un décorateur pour résoudre votre problème.

2
simplylizz

Extension de @iruvar answer - à partir de Python 3.4, il existe un gestionnaire de contexte existant dans la bibliothèque standard Python: 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