Как стать автором
Обновить

Комментарии 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

Можно было бы:


  1. Обойтись без декоратора, внедрять From/To в каждый из методов. Их там штук пять, не так уж сложно, но внешне загромождает вид. И если вдруг формат входящих значения опять поменяют, менять придётся везде.
  2. Обойтись без изменения сигнатуры и не вводить второй класс BCFVector, но тогда мы рискуем рано или поздно передать в функцию вектор не в той системе координат и получить трудноотлавливаемую багу.
  3. Обойтись без декоратора, использовать враппер в явном виде:

def TransformPoint(self, src: Vector) -> Vector:
    return FromBCF(self._TransformBCFPoint(ToBCF(src)))

def _TransformBCFPoint(self, src: BCFVector) -> BCFVector:
    ...

И такая однотипная ерунда для каждой функции, что просто захламляет файлик. Ещё у пользователя "библиотечки" будет болтаться второй комплект методов в автодополнении, хоть мы и сделали их "приватными".


В общем, мне показалось использование такого декоратора оптимальным выходом с наименьшей писаниной. Ещё бы засунуть его внутрь класса, в котором он используется (сейчас он висит отдельной функцией), чтобы упростить передачу UnitTransform через self, но у меня с наскока чё-то не получилось.(

Можно же ещё определить в сигнатуре, что может быть одни из двух типов, благо Union есть и там уже разруливать)

Это тоже вариант, но не люблю заморачиваться с приведениями типов. Плюс, опять же, "внешнему" пользователю не нужен второй тип (тот, что используется внутри PointConvert (который во втором комментарии случайно превратился в TransformPoint, ну да ладно)), он использует только первый.


Так что смысл был как раз запретить передавать второй тип там, где правильно передавать первый; вопрос превращается в — как разрулить различие внешнего и внутреннего API при прочих плюсах декоратора. Наверное, стоило декоратор как-то повнятнее назвать, вроде @convert_vector_to_bcf, потому что @bcf можно и не заметить. Но честно говоря, больше в голову ничего не приходит.

у меня в проекте есть декоратор, который выносит функцию в отдельный поток, код не приведу, слишком много его
go-рутина?
Я не python программист, поэтому закономерно что я в первой и пока единственной своей python программе уже не маленького размера ощутил проблемы отладки. И написал такой враппер:
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
Возможно есть другие способы получше. Но этого мне хватило на много.
НЛО прилетело и опубликовало эту надпись здесь
Если так то можете по подробней? Я от logging ошибочно отказался потому что что первое что нагуглилось писало исключительно в файл, а так логи было собирать не удобно. (Сейчас прочитал что это не так, класс, буду использовать) Но во всяком случае logging это же только расширенная замена print, а не этому декоратору. Или я упустил что-то еще?
P.S. Можно (но не нужно) написать декоратор на print добавляющий debug level :) раз мы про декораторы говорим.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

а при помощи чего порекомендуете писать бенчмарки в простом скрипте на питоне?

НЛО прилетело и опубликовало эту надпись здесь
А нормальные профайлеры в Питон так и не завезли?
Про это автор напишет следующую статью, разумеется когда их найдет)
а где мораль сказки? у кого там по усам текло, а в рот не попало?
Поясните пожалуйста! У меня складывается впечатление, что в последнем примере внутри wrapper() функция func() вызывается дважды (в теле и в return). Если это правильно, объясните почему?
НЛО прилетело и опубликовало эту надпись здесь
Спасибо!
Подправил
у меня одного от такого кода глаз дергается?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации