Pull to refresh

Comments 35

Интересно,
А если вместо
def mydecorator(fn) :
    # здесь может быть какая-то полезная работа...
    def wrapper(*args, **kwargs)
        # ... или здесь что-то полезное ...
        return fn(*args, **kwargs)
    return wrapper

# зарегистрируем наш декоратор
mydecorator = dreg.decorator(mydecorator)


написать
@dreg.decorator
def mydecorator( fn) :
    # здесь может быть какая-то полезная работа...
    def wrapper( *args, **kwargs)
        # ... или здесь что-то полезное ...
        return fn( *args, **kwargs)
    return wrapper


— не выйдет ли проще для понимания?
Как это может не сработать, если это и есть определение декоратора?
Виноват, засыпаю. конечно же будет работать и так
> возникла необходимость внутри функции-декоратора проверить задекорирован
> ли декорируемый метод другим декоратором
Зачем?
Вот я ожидал этого вопроса и даже начал описывать кейс, но потом передумал. Вы готовы поверить на слово, что на то были объективные причины?
Может быть оно и не самое хорошее но наиболее удобное, так как позволяет не править сторонюю библиотеку и иметь впоследствии проблемы с ее обновлением. К сожалению описание деталей тянет на отдельную статью… Ваше право считать так как вы считаете.
> наиболее удобное, так как позволяет не править сторонюю библиотеку
Вы заранее считаете, что любое другое решение потребует правки. Возможно, это не так.
Я считаю не заранее, а по факту.
Это все очень странно выглядит и больше похоже на статью из топика «Ненормальное программирование»… может всё-таки набросаете жизненный пример для общего понимания?
Вот жеж вы зануды.
Хорошо, пусть будет такой пример. Начнем с того, что есть сторонняя библиотека «один» поставляющая некий «мега-функционал», который «включается» декоратором «one». Есть вторая, такая же важначя и нужная и поставляет декоратор «two».

Есть некий наш класс:

class MyClass(object):

    def my_method(self):
        pass


Если вы применяете декораторы из двух библиотек по одному на ваших методах — все работает. Так работает:

class MyClass(object):

    @one
    def my_method(self):
        pass


и так работает:

class MyClass(object):

    @two
    def my_method(self):
        pass


А так не работает:

class MyClass(object):

    @one
    @two
    def my_method(self):
        pass


и так не работает:

class MyClass(object):

    @two
    @one
    def my_method(self):
        pass


Не работает потому, что сами библиотеки внутри возможно написаны не очень хорошо (такое бывает?) и каждая из них делает нечно, для чего ожидает что переданный в ее декоратор метод — это метод класса, патается каким-то образом отрефлектить этот метод, и что-то там сделать — не важно. Поэтому в таком варианте не работает по той причине что верхний декоратор декорирует уже задекорированый метод, а не исходный. В качестве решения я и воспользовался таким подходом — регистрирую оба декоратора в реестре и один из них переопределяю, передавая реальную функцию, когда она не доступна. Остальной функционал по рефлексии получился в качестве дополнительного инструментария, что иногда полезно, как минимум в дебаге. Сторонние же библиотеки остались нетронутыми, и не будет проблем с их обновлением. Может это и не очень правильно, как вы говорите, но зато удобно.
> сами библиотеки внутри возможно написаны не очень хорошо (такое бывает?)
И проще написать еще одну, лишь бы не исправлять эти? :)

> не будет проблем с их обновлением
Просто исправьте их и судя по вашему рассказу не будет вообще никаких проблем ни с чем.
> И проще написать еще одну, лишь бы не исправлять эти? :)
Ну вам уже не придется так напрягаться — можете воспользоваться моей :)

> Просто исправьте их и судя по вашему рассказу не будет вообще никаких проблем ни с чем.
Да и так нет проблем ни с чем
Вообще-то, если по-честному, я немного лукавлю, говоря, что библиотеки написаны не очень хорошо. Просто сам Python, на мой взгляд устроен не совсем хорошо. Сам факт того, что при декорировании, метод переданый в декоратор становиться unbound функцией и зарефлектить, к какому классу принадлежит данный метод, не представляется возможным без таких вещей, как f.__code__.co_filename, f.__code__.co_firstlineno, и с последующим парсингом исходника с целью найти какому классу принадлежит данный метод. Если же декораторов было несколько, то вы вообще теряете связь с самим исходным методом! Вам опять может стать непонятным для чего это нужно, но ведь, в целом, рефлексии позволяют создавать некоторые абстракции, что может быть полезным при проектировании некоторых библиотек общего назначения. Все было бы гораздо проще, если бы на уровне языка рефлексии, да и реализация декораторов, были бы немного продуманнее.
Если честно, вы говорите на непонятном языке :) Нет никаких рефлексий, вся информация доступная интерпретатору доступна в рантайме и вам.

> при декорировании, метод переданый в декоратор становиться unbound функцией
Он и до декорирования обычная функция. Он привязывается к классу строго в тот момент, когда вы получаете его как атрибут, не раньше. Это и позволяет вам сделать декоратор для метода: все что вы вернете из декоратора тоже станет «методом». Взгляните на пример:

def method(self):
  return id(self)

class A(object):
  id = method

class B(object):
  id = method

A().id()
B().id()

Какому классу «принадлежит» метод method по вашему мнению?

Я думаю вам нужно больше узнать про дескрипторы, благодаря которым все это и работает. А так вы со своим уставом ищете правды. И пожалуйста, не пишите библиотеку для парсинга исходников :)
Дорогой КО, зачем вы мне это пишете?
Я говорю именно о том, что подобный подход мне не нравится в Python.

Ваш пример:

>>> def m():
...     pass
... 
>>> def m(self):
...     pass
... 
>>> class A(object):
...     f = m
... 
>>> class B(object):
...     f = m
... 
>>> A.f.im_class
<class '__main__.A'>
>>> B.f.im_class
<class '__main__.B'>
>>> m.im_class
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'im_class'


Теперь объявляем класс с таким задекорированым методом:

>>> def d( fn):
...     print fn.im_class
...     def w(*a, **k):
...             return fn(*a, **k)
...     return w
... 
>>> class X(object):
...     @d
...     def f(self):
...             pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in X
  File "<stdin>", line 2, in d
AttributeError: 'function' object has no attribute 'im_class'


Все вы даже определить класс не можете! Чтобы это все таки работало — будете парсить код. Я не вижу другого решения. Вы видите?

И еще раз — я не пишу библиотеку «для парсинга исходников». Я просто использую библиотеку, у которой это внутри.
Э… а functools.wraps, случайно, не спасёт отца русской демократии ©? ;)
С функциями — спасает, с методами сам не пробовал…
Э… как бы… совсем о другом будет, не?
Не знаю — честно спросил :).

Попробовал. Не спасает.
И декоратор-класс тоже не спасает.

В декоратор уже попадает функция…
Продолжу…

Итак, мы выяснили, что для решения такой ситуации придется парсить исходник (что и делают эти мои сторонние библиотеки). Такой код выполниться только один раз при запуске (что нам собственно и нужно, и поэтому не критично, т.е. можем с этим мириться).

Кстати, если кто-то может ткнуть меня мордой в то, как обойти парсинг исходников в такой ситуации — я буду просто благодарен и наконец-то стану спать спокойно.

Усложняем модель.

Вводим второй декоратор, который делает в какой-то мере то же самое (опять парсит, опять единожды при запуске, но делает что-то уже другое, но нам также полезное). Но так как второй декоратор будет декорировать не метод класса (а по написанному коду — это метод, объявленный внутри класса — так код написан!), а декоратор этого метода, то выходит, что будем парсить далеко не тот кусок кода, который ожидаем. (напоминаю — это не мое, чужое, но мне с ним жить)

Вот тут и поможет моя библиотека. Она позволяет получить get_real_function(). Но это будет доступно только если оба декоратора «зарегистрированы» перед определением класса. При этом декоратор, который используется сверху, переопределяем, подсовывая нативному реальную функцию. И все работает. И овцы целы, и волки сыты. Кстати можно переопределить таким способом оба и тогда можно вообще забить на порядок их следования при написании кода классов.

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

Я специально не хотел описывать этот кейс (он очень специфичный), а подать статью немного в другом ракурсе, как бы «вот библиотека, вот это она умеет». Но не фартануло… :)
> зарефлектить, к какому классу принадлежит данный метод, не представляется возможным
Я правда не понимаю, в чем тут проблема. В каком порядке не применяй декораторы, все равно декоратор метода легко получает класса.

def regular_dec(fn):
    def wrapped(*args, **kwargs):
        print 'regular decorator'
        return fn(*args, **kwargs)
    return wrapped

def method_dec(fn):
    def wrapped(self, *args, **kwargs):
        print 'method decorator. class is', type(self)
        return fn(self, *args, **kwargs)
    return wrapped

class A(object):
    @regular_dec
    @method_dec
    def method1(self, some_arg=None):
        print '>>> method1'

    @method_dec
    @regular_dec
    def method2(self, some_arg=None):
        print '>>> method2'

A().method1()
A().method2()


regular decorator
method decorator. class is <class '__main__.A'>
>>> method1
method decorator. class is <class '__main__.A'>
regular decorator
>>> method2


Другое дело, что вы не получите этой информации извне. Но вы этого не узнаете и для любого другого метода, безотносительно декораторов. И ваша библиотека тут вроде бы никак не поможет.
Смотрите мой комент выше
Приведите пожалуйста реальный пример с конкретными библиотеками и конкретными декораторами из этих библиотек.
Я не автор, но с _похожей_ бедой столкнулся при играх с bottle.py — у них декораторы @route() корёжат переданные аргументы (*args, **kwargs) и что-то там ещё так, что «внешний» декоратор вообще не вызывается. Например такой код:
def my_deco(fn):
  def wrapped(*args, **kwargs):
    print "HI"
  
  #....

@my_deco
@route("/")
def index():
  #...

Никогда не напечатает «HI».
Есть штатный обходной путь, который приходится использовать с их же декоратором:
@route("/", apply=[my_deco])
def index():
  #...

Первым желанием было сделать что-то наподобие сделанного автором топика, но потом — «отпустило», когда нагуглил таки про «apply». Отнюдь не в родной документации :)
Мдя… Спасибо большое за пример! Хорошо хоть в bottle.py штатный путь решения этой проблемы есть. Только вот очень уж не нравится мне такой подход авторов библиотек.
Не за что!
Мне тоже такой подход не понравился, но другой «лёгкой» альтернативы пока не придумалось.
Самое неприятное, что этот метод нагуглился на каком-то форуме, чуть ли не в багтрекере :(

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

Замечу ещё, что порядок вызова декораторов из apply — LIFO:
@route("/", apply=[view("my_index_template"), my_beaker_cache])
def index():
  return {
     'data': some_data()
  }

Сначала сработает my_beaker_cache, закешировав отданный нами dict, а только потом вызовется шаблонизатор (и его никто кешировать не станет). Это может вызвать ошибки при отличии типа кеша от «memory» — в beaker сериализация идёт через json, который поддерживает ограниченное количество типов данных.
Конечно, не вызывается. Чтобы он вызвался, он должен идти «под» route, потому что route добавляет в dispatch нижележащего фреймворка функцию, которая передана ему в качестве параметра.
Угу… Вот это и есть «нехорошее» т.е. не очевидное поведение.
И, кстати, вот такое не работает по причине не соответствия параметров функции:
@route("/abc")
@view("abcview")
def abc(db): # db - подключённая через Plugin база данных
  # ...

но прекрасно работает, когда вызовом занимается route.
В документации описания подобного поведения не нашёл. А нашёл слова, что, подключая plugin, функции, использующие параметр с указанным именем, автоматически его получают из плагина…

Возможно — плохо искал :)
Понятия не имею про плагины bottle.py. Краткий обзор и интуиция подсказывают мне, что у них что-то фатально сломано. То ли плагины, то ли view, то ли route.

Сами же декораторы работают вполне логично. Порядок их применения очевиден. С «регистрирующими» тоже все довольно очевидно — неявный стейт, все дела.
Тот же самый вопрос возник.
Для того чтобы внутри декоратора проверить не задекорирована ли декорируемая функции другим декоратором вы написали библиотеку декораторов, декорирующую декораторы вспомогательными декораторами.

Я прав?
Правы. Вас это смущает? В конце концов я не агитирую использовать данную библиотеку именно в таком контексте. Можно на это, в конце-концов, посмотреть под другим углом. К примеру, вы ищете решение такой задачи — узнать какие методы в заданном классе задекорированы заданным декоратором. Данная библиотека позволяет задачу решить без парсинга исходного кода. Сам язык такого инструментария не дает. Это что, что-то совершенно бесполезное?
Но для этого нужно задекорировать ваши(или не ваши) декораторы вспомогательным декоратором из вашей библиотеки декораторов. А если в исходной сторонней библиотеке считается, что декоратор не задеркорирован (в конце-то концов, он же в той библиотеке, никто и не думал, что его возьмут, да извне задекорируют), что вы на это имеете сказать?
Я имею на это сказать. Почему по вашему он не ожидает, что он не задекорирован? Как он об этом знает? Вед декоратор только знает о функции, которую он декорирует. Я вед именно эту задачу и решаю — позволить себе знать не только вниз но и вверх. Если он об этом знает, значит он использует мою библиотеку? :)
Sign up to leave a comment.

Articles