Comments 25
Олег обрадовался и захотел переписать весь код большого и уютного проекта, но быстро устал вносить изменения и стал грустить.
так декораторы же тоже надо расставлять по коду. Так что Олег так и остался жить грустным, а менеджеры стали радостными.
Ну вот буквально на днях запилил скриптик, который должен был применять некоторые преобразования к вектору. Однако обнаружилось, что векторы, которые поступают на вход, немного не в том формате, в котором ожидалось. Пилим два классика: Vector
(который приходит) и BCFVector
(который отличается только системой координат, нужен, чтобы mypy
не давал передавать один тип там, где ожидается другой) и к ним две функции ToBCF
и FromBCF
.
def bcf(
fn: Callable[[UnitTransform, BCFVector], BCFVector]
) -> Callable[[UnitTransform, Vector], Vector]:
"""Make automatic convert to and from BCF."""
def wrapper(ut: UnitTransform, vector: Vector) -> Vector:
return FromBCF(fn(ut, ToBCF(vector)))
return wrapper
class UnitTransform:
@bcf
def PointConvert(self, point: BCFVector) -> BCFVector:
...
src = Vector(0, 1, 2) # не BCFVector!
res = transform.PointConvert(src)
Везде удобно и при этом не надо переживать, что где-то накосячил с преобразованием из одного формата в другой.
Но ведь у вас теперь сигнатура функции отличается от того, что реально ожидает функция, нет? Теперь придётся ваш декоратор смотреть, чтоб понять что происходит.
Сигнатура отличается, да, надо помнить, что в данном случае декоратор её изменяет. Но если воспринимать этот класс как "библиотечку", то её пользователю всё равно как оно там, главное что на вход он может передавать ровно то, что хотел. Автодополнение (по крайней мере в VSCode) отрабатывает правильно, с учётом декоратора:
(method) PointConvert: (p1: Vector) -> Vector
Можно было бы:
- Обойтись без декоратора, внедрять From/To в каждый из методов. Их там штук пять, не так уж сложно, но внешне загромождает вид. И если вдруг формат входящих значения опять поменяют, менять придётся везде.
- Обойтись без изменения сигнатуры и не вводить второй класс
BCFVector
, но тогда мы рискуем рано или поздно передать в функцию вектор не в той системе координат и получить трудноотлавливаемую багу. - Обойтись без декоратора, использовать враппер в явном виде:
def TransformPoint(self, src: Vector) -> Vector:
return FromBCF(self._TransformBCFPoint(ToBCF(src)))
def _TransformBCFPoint(self, src: BCFVector) -> BCFVector:
...
И такая однотипная ерунда для каждой функции, что просто захламляет файлик. Ещё у пользователя "библиотечки" будет болтаться второй комплект методов в автодополнении, хоть мы и сделали их "приватными".
В общем, мне показалось использование такого декоратора оптимальным выходом с наименьшей писаниной. Ещё бы засунуть его внутрь класса, в котором он используется (сейчас он висит отдельной функцией), чтобы упростить передачу UnitTransform
через self
, но у меня с наскока чё-то не получилось.(
Это тоже вариант, но не люблю заморачиваться с приведениями типов. Плюс, опять же, "внешнему" пользователю не нужен второй тип (тот, что используется внутри PointConvert
(который во втором комментарии случайно превратился в TransformPoint
, ну да ладно)), он использует только первый.
Так что смысл был как раз запретить передавать второй тип там, где правильно передавать первый; вопрос превращается в — как разрулить различие внешнего и внутреннего API при прочих плюсах декоратора. Наверное, стоило декоратор как-то повнятнее назвать, вроде @convert_vector_to_bcf
, потому что @bcf
можно и не заметить. Но честно говоря, больше в голову ничего не приходит.
def log_decorator(function_to_decorate):
def the_wrapper_around_the_original_function(param1, *args, **kwargs):
try:
print(f"debug: starting function: {function_to_decorate.__name__} with *args={str(*args)} **kwargs={str(**kwargs)}")
function_to_decorate(param1, *args, **kwargs)
print(f"debug: finished function: {function_to_decorate.__name__}")
except Exception as e:
print_exception(e)
return the_wrapper_around_the_original_function
Возможно есть другие способы получше. Но этого мне хватило на много.P.S. Можно (но не нужно) написать декоратор на print добавляющий debug level :) раз мы про декораторы говорим.
Сказка про декораторы в Python