Общий декоратор для обертывания, кроме как в 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 для...
- Если метод не существует.
- Когда объект не существует и получает вызванный метод.
- Когда объект, который не существует, вызывается как аргумент функции.
- Любая комбинация любой из этих вещей.
- Бонус, когда функция не существует.
Ответы
Ответ 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 ''
Также вы можете комбинировать оба решения, если у вас есть несколько мест, где вам нужно получить значение, и вспомогательная функция будет более разумной.
Не уверен, что вам действительно нужен декоратор для вашей проблемы.