Ответ 1
TL;DR
Вы можете исправить эту проблему, преобразовывая дескриптор класса Timed
и возвращая частично примененную функцию из __get__
, которая применяет объект Test
как один из аргументов, например этот
class Timed(object):
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print self
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
Актуальная проблема
Задание документации по Python для decorator,
Синтаксис декоратора - это просто синтаксический сахар, следующие два определения функций семантически эквивалентны:
def f(...): ... f = staticmethod(f) @staticmethod def f(...): ...
Итак, когда вы говорите,
@Timed
def decorated(self, *args, **kwargs):
это фактически
decorated = Timed(decorated)
только объект функции передается Timed
, объект, к которому он фактически привязан, не передается вместе с ним. Итак, когда вы вызываете его вот так
ret = self.func(*args, **kwargs)
self.func
будет ссылаться на несвязанный объект функции, и он вызывается с помощью Hello
в качестве первого аргумента. Вот почему self
печатает как Hello
.
Как я могу это исправить?
Поскольку у вас нет ссылки на экземпляр Test
в Timed
, единственным способом сделать это будет преобразование Timed
в качестве класса дескриптора. Выделив документацию, раздел "Вызов дескрипторов",
В общем, дескриптор является атрибутом объекта с "поведением привязки", тот, чей доступ к атрибуту был переопределен методами в протоколе дескриптора:
__get__()
,__set__()
и__delete__()
. Если какой-либо из этих методов определен для объекта, он называется дескриптором.Поведение по умолчанию для доступа к атрибутам - это получение, установка или удаление атрибута из словаря объектов. Например,
a.x
имеет цепочку поиска, начиная сa.__dict__['x']
, затемtype(a).__dict__['x']
и продолжая базовые классыtype(a)
, исключая метаклассы.Однако , если искомое значение является объектом, определяющим один из методов дескриптора, тогда Python может переопределить поведение по умолчанию и вместо этого использовать метод дескриптора.
Мы можем сделать Timed
дескриптор, просто определяя метод, подобный этому
def __get__(self, instance, owner):
...
Здесь self
относится к самому объекту Timed
, instance
относится к фактическому объекту, по которому происходит поиск атрибута, и owner
относится к классу, соответствующему instance
.
Теперь, когда __call__
вызывается в Timed
, вызывается метод __get__
. Теперь, как-то, нам нужно передать первый аргумент как экземпляр класса Test
(еще до Hello
). Итак, мы создаем еще одну частично применимую функцию, первым параметром которой будет экземпляр Test
, как этот
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
Теперь self.__call__
является связанным методом (привязанным к экземпляру Timed
), а второй параметр partial
является первым аргументом вызова self.__call__
.
Итак, все это эффективно переводится как
t.call_deco()
self.decorated("Hello", world="World")
Теперь self.decorated
на самом деле Timed(decorated)
(это будет теперь обозначаться как TimedObject
). Всякий раз, когда мы обращаемся к нему, метод __get__
, определенный в нем, вызывается и возвращает функцию partial
. Вы можете подтвердить, что вот так
def call_deco(self):
print self.decorated
self.decorated("Hello", world="World")
будет печатать
<functools.partial object at 0x7fecbc59ad60>
...
Итак,
self.decorated("Hello", world="World")
переводится на
Timed.__get__(TimedObject, <Test obj>, Test.__class__)("Hello", world="World")
Так как мы возвращаем функцию partial
,
partial(TimedObject.__call__, <Test obj>)("Hello", world="World"))
который фактически
TimedObject.__call__(<Test obj>, 'Hello', world="World")
Итак, <Test obj>
также становится частью *args
, и когда вызывается self.func
, первым аргументом будет <Test obj>
.