Подтвердить что ты не робот

Общий декоратор для обертывания, кроме как в python?

Я бы взаимодействовал с большим количеством глубоко вложенных json, которые я не писал, и хотел бы сделать мой python script более "прощающим" для недопустимого ввода. Я нахожу, что пишу участвую в блоках try-except, и скорее всего закрою сомнительную функцию.

Я понимаю, что это плохая политика для проглатывания исключений, но я предпочел бы, чтобы они были напечатаны и проанализированы позже, чем фактически остановить выполнение. Это более ценно, в моем случае использования, чтобы продолжить выполнение над циклом, чем получить все ключи.

Вот что я делаю сейчас:

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

Вот что мне хотелось бы, (1):

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

или (2):

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

... где я могу обернуть либо единый элемент данных (1), либо главную функцию (2), в некоторой функции, которая превращает исключения завершения выполнения в пустые поля, напечатанные на стандартный вывод. Первый будет своего рода пропуском по пунктам - там, где этот ключ недоступен, он записывается пустым и перемещается - последний является пропуском строки, где, если какое-либо из полей не работает, вся запись пропущен.

Я понимаю, что какая-то обертка должна уметь это исправить. Вот что я пробовал, с оберткой:

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

Вот почему это не работает. Вызовите функцию, которая не существует, она не пытается ее уловить:

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

Прежде чем я даже добавлю пустое возвращаемое значение, я бы хотел, чтобы он правильно попытался поймать. Если бы функция работала, это напечатало бы "Ошибка", правильно?

Является ли функция-обертка правильным здесь?

UPDATE

У меня было много действительно полезных и полезных ответов ниже, и спасибо вам за них, но я отредактировал примеры, которые я использовал выше, чтобы проиллюстрировать, что я пытаюсь поймать больше, чем вложенные ключевые ошибки, что я ищу специально для функции, которая обертывает try-catch для...

  • Если метод не существует.
  • Когда объект не существует и получает вызванный метод.
  • Когда объект, который не существует, вызывается как аргумент функции.
  • Любая комбинация любой из этих вещей.
  • Бонус, когда функция не существует.
4b9b3361

Ответ 1

Вы можете использовать defaultdict и подход к контекстному менеджеру, как описано в презентации Raymond Hettinger PyCon 2013

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

Ответ 2

Это очень легко достичь с помощью настраиваемого декоратора.

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)

Просто перейдите к кортежам get_decorator с типами ошибок, которые вы хотите отключить, и значение по умолчанию для возврата. Выход будет

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

Изменить: Благодаря martineau я изменил значение по умолчанию ошибок на кортежи с базовым исключением, чтобы предотвратить ошибки.

Ответ 3

Здесь есть много хороших ответов, но я не видел ни одного такого вопроса, касающегося того, можете ли вы выполнить это через декораторы.

Короткий ответ - "нет", по крайней мере, не без структурных изменений в вашем коде. Декораторы работают на функциональном уровне, а не на отдельных заявлениях. Поэтому, чтобы использовать декораторы, вам нужно будет переместить каждое из выражений, которые будут украшены, в свою собственную функцию.

Но обратите внимание, что вы не можете просто поставить само присваивание внутри декорированной функции. Вам нужно вернуть выражение rhs (значение, которое нужно назначить) из декорированной функции, а затем выполнить назначение снаружи.

Чтобы выразить это с точки зрения кода вашего примера, можно написать код со следующим шаблоном:

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

item["a"] = computeA()

return_on_failure может быть что-то вроде:

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

    return applicator

  return decorate

Ответ 4

в вашем случае вы сначала оцените значение вызова мяу (которое не существует), а затем оберните его в декораторе. это не работает.

сначала исключение возникает до того, как оно было завернуто, тогда оболочка ошибочно отступом (silenceit не должна возвращаться сама). Возможно, вы захотите сделать что-то вроде:

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

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

softfail =f(hardfail)

выход:

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

в любом случае в вашем случае я не понимаю, почему вы не используете простой метод, например

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

и в коде:

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

Отредактировано:

Если вы хотите что-то, что будет работать на любой глубине. Вы можете сделать что-то вроде:

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

Что вы бы назвали:

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

И используя ваш код

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

Кстати, с личной точки зрения мне также нравится решение @cravoori, используя contextmanager. Но это означало бы иметь три строки кода каждый раз:

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

Ответ 5

Это зависит от того, какие исключения вы ожидаете.

Если ваш единственный вариант использования get(), вы можете сделать

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

В других случаях ваш подход к декоратору может быть полезен, но не так, как вы это делаете.

Я попробую показать вам:

def f(func):
   def silenceit(*args, **kwargs): # takes all kinds of arguments
      try:
         return func(*args, **kwargs) # returns func 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

Тем не менее, f(some_undefined_function()) не будет работать, потому что

a) f() еще не активен во время выполнения и

b) используется неправильно. Правильный способ состоял бы в том, чтобы обернуть функцию, а затем вызвать ее: f(function_to_wrap)().

"Уровень лямбда" поможет здесь:

wrapped_f = f(lambda: my_function())

обертывает лямбда-функцию, которая в свою очередь вызывает несуществующую функцию. Вызов wrapped_f() приводит к вызову оболочки, которая вызывает лямбду, которая пытается вызвать my_function(). Если этого не существует, лямбда вызывает исключение, которое попадает в оболочку.

Это работает, потому что имя my_function не выполняется во время определения лямбда, но когда оно выполняется. И это выполнение защищено и завернуто функцией f(). Таким образом, исключение происходит внутри лямбда и распространяется на функцию обертывания, предоставляемую декоратором, который обрабатывает ее изящно.

Это движение по направлению к лямбда-функции не работает, если вы попытаетесь заменить лямбда-функцию оболочкой, например

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

а затем

f(g(my_function))(arguments)

потому что здесь разрешение имен "обратно на поверхность": my_function не может быть разрешено, и это происходит до того, как вызываются g() или даже f(). Так что это не работает.

И если вы попытаетесь сделать что-то вроде

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

он не может работать, если у вас нет x, потому что g() защищает print, а не x.

Если вы хотите защитить x здесь, вам нужно будет сделать

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

поскольку оболочка, предоставленная f(), вызывает эту лямбда-функцию, которая вызывает исключение, которое затем отключается.

Ответ 6

Поскольку вы имеете дело с большим количеством сломанного кода, в этом случае может быть оправданным использование eval.

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

Затем заверните все ваши потенциально нарушенные инструкции:

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

Ответ 7

Почему бы просто не использовать цикл?

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

Или, если вы хотите написать небольшой помощник:

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

Также вы можете комбинировать оба решения, если у вас есть несколько мест, где вам нужно получить значение, и вспомогательная функция будет более разумной.

Не уверен, что вам действительно нужен декоратор для вашей проблемы.