В моей работе мы часто пишем функции, которые производят вычисления и записывают результат в базу данных. Иногда допускаются ошибки, и в конечном итоге сохраняется неправильный материал, поэтому мы также хотим предоставить интерфейс для очистки после функции. Это попытка решить эту проблему.
from functools import wraps
def revert(func):
if not hasattr(func,'_revert'):
raise NotImplementedError(func.__name__ + " does not have a revert registered")
@wraps(func._revert)
def wrapper(*args,**kwargs):
return func._revert(*args,**kwargs)
return wrapper
def register_revert(revert):
def revert_added(func):
func._revert = revert
@wraps(func)
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
return wrapper
return revert_added
def _is_it_a_str(maybe_str):
if not isinstance(maybe_str,str):
raise ValueError("I need a string")
def _revert_stuff(a_str):
_is_it_a_str(a_str)
print('revert stuff with ' + a_str)
@register_revert(_revert_stuff)
def do_stuff(a_str):
_is_it_a_str(a_str)
print('doing stuff with ' + a_str)
do_stuff('something')
# Oops:
revert(do_stuff)('something')
Идея состоит в том, чтобы написать вашу функцию, выполняющую вашу работу — в данном случае do_something. А затем, когда вы будете готовы перейти на следующий уровень, вы напишете его откат, зарегистрируете, и все готово.
Мне нравится, что
- решение не требует ничего менять в основном функционале.
- вы можете использовать один и тот же идентичный интерфейс для многих функций
- синтаксис читается относительно интуитивно
Я считаю неудобным то, что
- Мне нужно поддерживать два идентичных интерфейса — один на do_stuff и _revert_stuff должен быть идентичным (или, по крайней мере, _revert_stuff должен работать, получая тот из do_stuff).
- иногда я хочу перепроверить аргументы, которые я затем должен повторить (пример здесь с _is_it_a_str). Я думал о том, чтобы передать это другому декоратору, но, боюсь, это слишком запутывает.
Изменить: мы используем новейшую стабильную версию Python
ps Мой первый вопрос старался изо всех сил. Критика приветствуется!
1 ответ
def revert(func):
@wraps(func._revert)
def wrapper(*args,**kwargs):
return func._revert(*args,**kwargs)
return wrapper
Вы возвращаете оболочку, которая принимает аргументы, вызывает функцию и возвращает результаты …
Я почти уверен, что это просто длинный способ написать:
def revert(func):
return func._revert
Возникает вопрос: зачем хранить функцию возврата как func._revert
, когда вы могли сохранить его как func.revert
? Тогда вместо:
revert(do_stuff)('something')
можно было написать:
do_stuff.revert('something')
что на один символ короче. Еще лучше: автозаполнение может сказать вам, есть ли .revert
член для do_stuff
, что невозможно с помощью revert(...)
.