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
— не выйдет ли проще для понимания?
+1
> возникла необходимость внутри функции-декоратора проверить задекорирован
> ли декорируемый метод другим декоратором
Зачем?
> ли декорируемый метод другим декоратором
Зачем?
+5
Вот я ожидал этого вопроса и даже начал описывать кейс, но потом передумал. Вы готовы поверить на слово, что на то были объективные причины?
0
Нет. Скорее всего вы нашли не самое хорошее решение.
+5
Это все очень странно выглядит и больше похоже на статью из топика «Ненормальное программирование»… может всё-таки набросаете жизненный пример для общего понимания?
+2
Вот жеж вы зануды.
Хорошо, пусть будет такой пример. Начнем с того, что есть сторонняя библиотека «один» поставляющая некий «мега-функционал», который «включается» декоратором «one». Есть вторая, такая же важначя и нужная и поставляет декоратор «two».
Есть некий наш класс:
Если вы применяете декораторы из двух библиотек по одному на ваших методах — все работает. Так работает:
и так работает:
А так не работает:
и так не работает:
Не работает потому, что сами библиотеки внутри возможно написаны не очень хорошо (такое бывает?) и каждая из них делает нечно, для чего ожидает что переданный в ее декоратор метод — это метод класса, патается каким-то образом отрефлектить этот метод, и что-то там сделать — не важно. Поэтому в таком варианте не работает по той причине что верхний декоратор декорирует уже задекорированый метод, а не исходный. В качестве решения я и воспользовался таким подходом — регистрирую оба декоратора в реестре и один из них переопределяю, передавая реальную функцию, когда она не доступна. Остальной функционал по рефлексии получился в качестве дополнительного инструментария, что иногда полезно, как минимум в дебаге. Сторонние же библиотеки остались нетронутыми, и не будет проблем с их обновлением. Может это и не очень правильно, как вы говорите, но зато удобно.
Хорошо, пусть будет такой пример. Начнем с того, что есть сторонняя библиотека «один» поставляющая некий «мега-функционал», который «включается» декоратором «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
Не работает потому, что сами библиотеки внутри возможно написаны не очень хорошо (такое бывает?) и каждая из них делает нечно, для чего ожидает что переданный в ее декоратор метод — это метод класса, патается каким-то образом отрефлектить этот метод, и что-то там сделать — не важно. Поэтому в таком варианте не работает по той причине что верхний декоратор декорирует уже задекорированый метод, а не исходный. В качестве решения я и воспользовался таким подходом — регистрирую оба декоратора в реестре и один из них переопределяю, передавая реальную функцию, когда она не доступна. Остальной функционал по рефлексии получился в качестве дополнительного инструментария, что иногда полезно, как минимум в дебаге. Сторонние же библиотеки остались нетронутыми, и не будет проблем с их обновлением. Может это и не очень правильно, как вы говорите, но зато удобно.
+1
> сами библиотеки внутри возможно написаны не очень хорошо (такое бывает?)
И проще написать еще одну, лишь бы не исправлять эти? :)
> не будет проблем с их обновлением
Просто исправьте их и судя по вашему рассказу не будет вообще никаких проблем ни с чем.
И проще написать еще одну, лишь бы не исправлять эти? :)
> не будет проблем с их обновлением
Просто исправьте их и судя по вашему рассказу не будет вообще никаких проблем ни с чем.
0
> И проще написать еще одну, лишь бы не исправлять эти? :)
Ну вам уже не придется так напрягаться — можете воспользоваться моей :)
> Просто исправьте их и судя по вашему рассказу не будет вообще никаких проблем ни с чем.
Да и так нет проблем ни с чем
Ну вам уже не придется так напрягаться — можете воспользоваться моей :)
> Просто исправьте их и судя по вашему рассказу не будет вообще никаких проблем ни с чем.
Да и так нет проблем ни с чем
0
Вообще-то, если по-честному, я немного лукавлю, говоря, что библиотеки написаны не очень хорошо. Просто сам Python, на мой взгляд устроен не совсем хорошо. Сам факт того, что при декорировании, метод переданый в декоратор становиться unbound функцией и зарефлектить, к какому классу принадлежит данный метод, не представляется возможным без таких вещей, как f.__code__.co_filename, f.__code__.co_firstlineno, и с последующим парсингом исходника с целью найти какому классу принадлежит данный метод. Если же декораторов было несколько, то вы вообще теряете связь с самим исходным методом! Вам опять может стать непонятным для чего это нужно, но ведь, в целом, рефлексии позволяют создавать некоторые абстракции, что может быть полезным при проектировании некоторых библиотек общего назначения. Все было бы гораздо проще, если бы на уровне языка рефлексии, да и реализация декораторов, были бы немного продуманнее.
0
Если честно, вы говорите на непонятном языке :) Нет никаких рефлексий, вся информация доступная интерпретатору доступна в рантайме и вам.
> при декорировании, метод переданый в декоратор становиться unbound функцией
Он и до декорирования обычная функция. Он привязывается к классу строго в тот момент, когда вы получаете его как атрибут, не раньше. Это и позволяет вам сделать декоратор для метода: все что вы вернете из декоратора тоже станет «методом». Взгляните на пример:
Какому классу «принадлежит» метод
Я думаю вам нужно больше узнать про дескрипторы, благодаря которым все это и работает. А так вы со своим уставом ищете правды. И пожалуйста, не пишите библиотеку для парсинга исходников :)
> при декорировании, метод переданый в декоратор становиться unbound функцией
Он и до декорирования обычная функция. Он привязывается к классу строго в тот момент, когда вы получаете его как атрибут, не раньше. Это и позволяет вам сделать декоратор для метода: все что вы вернете из декоратора тоже станет «методом». Взгляните на пример:
def method(self):
return id(self)
class A(object):
id = method
class B(object):
id = method
A().id()
B().id()
Какому классу «принадлежит» метод
method
по вашему мнению?Я думаю вам нужно больше узнать про дескрипторы, благодаря которым все это и работает. А так вы со своим уставом ищете правды. И пожалуйста, не пишите библиотеку для парсинга исходников :)
0
Дорогой КО, зачем вы мне это пишете?
Я говорю именно о том, что подобный подход мне не нравится в Python.
Ваш пример:
Теперь объявляем класс с таким задекорированым методом:
Все вы даже определить класс не можете! Чтобы это все таки работало — будете парсить код. Я не вижу другого решения. Вы видите?
И еще раз — я не пишу библиотеку «для парсинга исходников». Я просто использую библиотеку, у которой это внутри.
Я говорю именно о том, что подобный подход мне не нравится в 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'
Все вы даже определить класс не можете! Чтобы это все таки работало — будете парсить код. Я не вижу другого решения. Вы видите?
И еще раз — я не пишу библиотеку «для парсинга исходников». Я просто использую библиотеку, у которой это внутри.
-2
Э… а
С функциями — спасает, с методами сам не пробовал…
functools.wraps
, случайно, не спасёт отца русской демократии ©? ;)С функциями — спасает, с методами сам не пробовал…
0
Продолжу…
Итак, мы выяснили, что для решения такой ситуации придется парсить исходник (что и делают эти мои сторонние библиотеки). Такой код выполниться только один раз при запуске (что нам собственно и нужно, и поэтому не критично, т.е. можем с этим мириться).
Кстати, если кто-то может ткнуть меня мордой в то, как обойти парсинг исходников в такой ситуации — я буду просто благодарен и наконец-то стану спать спокойно.
Усложняем модель.
Вводим второй декоратор, который делает в какой-то мере то же самое (опять парсит, опять единожды при запуске, но делает что-то уже другое, но нам также полезное). Но так как второй декоратор будет декорировать не метод класса (а по написанному коду — это метод, объявленный внутри класса — так код написан!), а декоратор этого метода, то выходит, что будем парсить далеко не тот кусок кода, который ожидаем. (напоминаю — это не мое, чужое, но мне с ним жить)
Вот тут и поможет моя библиотека. Она позволяет получить get_real_function(). Но это будет доступно только если оба декоратора «зарегистрированы» перед определением класса. При этом декоратор, который используется сверху, переопределяем, подсовывая нативному реальную функцию. И все работает. И овцы целы, и волки сыты. Кстати можно переопределить таким способом оба и тогда можно вообще забить на порядок их следования при написании кода классов.
А теперь уж, я надеюсь, не составляет труда представить ситуацию, когда может понадобится проверить, а не задекорирован ли какой-то метод каким-то декоратором. А раз может, то и мы можем такую возможность в нашей библиотеке предоставить и несколько других подобных.
Я специально не хотел описывать этот кейс (он очень специфичный), а подать статью немного в другом ракурсе, как бы «вот библиотека, вот это она умеет». Но не фартануло… :)
Итак, мы выяснили, что для решения такой ситуации придется парсить исходник (что и делают эти мои сторонние библиотеки). Такой код выполниться только один раз при запуске (что нам собственно и нужно, и поэтому не критично, т.е. можем с этим мириться).
Кстати, если кто-то может ткнуть меня мордой в то, как обойти парсинг исходников в такой ситуации — я буду просто благодарен и наконец-то стану спать спокойно.
Усложняем модель.
Вводим второй декоратор, который делает в какой-то мере то же самое (опять парсит, опять единожды при запуске, но делает что-то уже другое, но нам также полезное). Но так как второй декоратор будет декорировать не метод класса (а по написанному коду — это метод, объявленный внутри класса — так код написан!), а декоратор этого метода, то выходит, что будем парсить далеко не тот кусок кода, который ожидаем. (напоминаю — это не мое, чужое, но мне с ним жить)
Вот тут и поможет моя библиотека. Она позволяет получить get_real_function(). Но это будет доступно только если оба декоратора «зарегистрированы» перед определением класса. При этом декоратор, который используется сверху, переопределяем, подсовывая нативному реальную функцию. И все работает. И овцы целы, и волки сыты. Кстати можно переопределить таким способом оба и тогда можно вообще забить на порядок их следования при написании кода классов.
А теперь уж, я надеюсь, не составляет труда представить ситуацию, когда может понадобится проверить, а не задекорирован ли какой-то метод каким-то декоратором. А раз может, то и мы можем такую возможность в нашей библиотеке предоставить и несколько других подобных.
Я специально не хотел описывать этот кейс (он очень специфичный), а подать статью немного в другом ракурсе, как бы «вот библиотека, вот это она умеет». Но не фартануло… :)
0
> зарефлектить, к какому классу принадлежит данный метод, не представляется возможным
Я правда не понимаю, в чем тут проблема. В каком порядке не применяй декораторы, все равно декоратор метода легко получает класса.
Другое дело, что вы не получите этой информации извне. Но вы этого не узнаете и для любого другого метода, безотносительно декораторов. И ваша библиотека тут вроде бы никак не поможет.
Я правда не понимаю, в чем тут проблема. В каком порядке не применяй декораторы, все равно декоратор метода легко получает класса.
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
Другое дело, что вы не получите этой информации извне. Но вы этого не узнаете и для любого другого метода, безотносительно декораторов. И ваша библиотека тут вроде бы никак не поможет.
0
Приведите пожалуйста реальный пример с конкретными библиотеками и конкретными декораторами из этих библиотек.
0
Я не автор, но с _похожей_ бедой столкнулся при играх с bottle.py — у них декораторы
Никогда не напечатает «HI».
Есть штатный обходной путь, который приходится использовать с их же декоратором:
Первым желанием было сделать что-то наподобие сделанного автором топика, но потом — «отпустило», когда нагуглил таки про «apply». Отнюдь не в родной документации :)
@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». Отнюдь не в родной документации :)
+2
Мдя… Спасибо большое за пример! Хорошо хоть в bottle.py штатный путь решения этой проблемы есть. Только вот очень уж не нравится мне такой подход авторов библиотек.
0
Не за что!
Мне тоже такой подход не понравился, но другой «лёгкой» альтернативы пока не придумалось.
Самое неприятное, что этот метод нагуглился на каком-то форуме, чуть ли не в багтрекере :(
Собственно, было желание указывать шаблон через декоратор, как показано в примерах, ан — не вышло.
Замечу ещё, что порядок вызова декораторов из apply — LIFO:
Сначала сработает
Мне тоже такой подход не понравился, но другой «лёгкой» альтернативы пока не придумалось.
Самое неприятное, что этот метод нагуглился на каком-то форуме, чуть ли не в багтрекере :(
Собственно, было желание указывать шаблон через декоратор, как показано в примерах, ан — не вышло.
Замечу ещё, что порядок вызова декораторов из apply — LIFO:
@route("/", apply=[view("my_index_template"), my_beaker_cache])
def index():
return {
'data': some_data()
}
Сначала сработает
my_beaker_cache
, закешировав отданный нами dict, а только потом вызовется шаблонизатор (и его никто кешировать не станет). Это может вызвать ошибки при отличии типа кеша от «memory» — в beaker сериализация идёт через json, который поддерживает ограниченное количество типов данных.+1
Угу… Вот это и есть «нехорошее» т.е. не очевидное поведение.
И, кстати, вот такое не работает по причине не соответствия параметров функции:
но прекрасно работает, когда вызовом занимается route.
В документации описания подобного поведения не нашёл. А нашёл слова, что, подключая plugin, функции, использующие параметр с указанным именем, автоматически его получают из плагина…
Возможно — плохо искал :)
И, кстати, вот такое не работает по причине не соответствия параметров функции:
@route("/abc")
@view("abcview")
def abc(db): # db - подключённая через Plugin база данных
# ...
но прекрасно работает, когда вызовом занимается route.
В документации описания подобного поведения не нашёл. А нашёл слова, что, подключая plugin, функции, использующие параметр с указанным именем, автоматически его получают из плагина…
Возможно — плохо искал :)
+1
Понятия не имею про плагины bottle.py. Краткий обзор и интуиция подсказывают мне, что у них что-то фатально сломано. То ли плагины, то ли view, то ли route.
Сами же декораторы работают вполне логично. Порядок их применения очевиден. С «регистрирующими» тоже все довольно очевидно — неявный стейт, все дела.
Сами же декораторы работают вполне логично. Порядок их применения очевиден. С «регистрирующими» тоже все довольно очевидно — неявный стейт, все дела.
0
Тот же самый вопрос возник.
+1
Для того чтобы внутри декоратора проверить не задекорирована ли декорируемая функции другим декоратором вы написали библиотеку декораторов, декорирующую декораторы вспомогательными декораторами.
Я прав?
Я прав?
+9
Правы. Вас это смущает? В конце концов я не агитирую использовать данную библиотеку именно в таком контексте. Можно на это, в конце-концов, посмотреть под другим углом. К примеру, вы ищете решение такой задачи — узнать какие методы в заданном классе задекорированы заданным декоратором. Данная библиотека позволяет задачу решить без парсинга исходного кода. Сам язык такого инструментария не дает. Это что, что-то совершенно бесполезное?
0
Но для этого нужно задекорировать ваши(или не ваши) декораторы вспомогательным декоратором из вашей библиотеки декораторов. А если в исходной сторонней библиотеке считается, что декоратор не задеркорирован (в конце-то концов, он же в той библиотеке, никто и не думал, что его возьмут, да извне задекорируют), что вы на это имеете сказать?
0
Я имею на это сказать. Почему по вашему он не ожидает, что он не задекорирован? Как он об этом знает? Вед декоратор только знает о функции, которую он декорирует. Я вед именно эту задачу и решаю — позволить себе знать не только вниз но и вверх. Если он об этом знает, значит он использует мою библиотеку? :)
0
Sign up to leave a comment.
Добавляем чуть больше рефлексии: декораторы