Как атрибуты в функциях могут выдержать перенос?
Допустим, у меня есть следующая функция, которая имеет атрибут, который помечает ее для специальной обработки в подсистеме обратного вызова:
def my_func(msg):
print msg
my_func.my_marker = SPECIAL_CONSTANT
Проблема в том, что если другие биты кода переносятся my_func
с functools.partial
или другой декоратор, тогда my_marker
будет потеряно.
my_partial = partial(my_func, 'hello world')
print my_partial.my_marker
>>> AttributeError...
Есть ли способ защитить атрибуты функций при переносе? Есть ли лучший способ хранить метаданные, которые я сейчас храню в my_marker
? Кажется, что сохранение ссылки на исходную функцию страдает от той же проблемы.
3 ответа
Если вы знаете, что часть, которую вы используете, на самом деле является частичной, вы можете использовать ее func
приписывать.
Например
from functools import partial
SPECIAL_CONSTANT = 'bam'
def my_func(msg):
print msg
my_func.my_marker = SPECIAL_CONSTANT
my_partial = partial(my_func, 'hello world')
print my_partial.func.my_marker
Если вам действительно нужно обрабатывать метаданные, возможно, лучше написать классы и переопределить __call__()
метод.
Вдохновленный комментариями kindall, ответом cbo и исходным кодом:
from functools import partial as _partial, update_wrapper
def partial(func, *args, **keywords):
return update_wrapper(_partial(func, *args, **keywords), func)
Пример, показывающий это работает, и предостережения:
def my_func(msg):
print msg
my_func.my_marker = 'FOO'
my_partial = partial(my_func, 'hello world')
print my_func.my_marker
print my_partial.my_marker
# check other stuff is still there
print my_partial.func
print my_partial.args
print my_partial.keywords
# this works fine for READ ONLY stuff.
# so just remember:
my_partial.my_marker = 'BAR' # this _only_ updates partial, not the original
print my_func.my_marker
print my_partial.my_marker
my_func.my_marker = 'BAZ' # this _only_ updates original, not the partial
print my_func.my_marker
print my_partial.my_marker
Вы можете сделать что-то вроде:
import functools
setattr(functools, 'partial', partial) # from above code
Тем не менее, это, вероятно, плохая идея, поскольку (1) его нужно будет импортировать до того, как какой-либо код, на который он опирается, (2) он может сломать импортированный код, (3) это может смутить будущих людей и (4) альтернативу, хранить его локально легко. Делайте это только если вы хотите, чтобы сторонний код запускал вашу версию.
Вот еще одно решение, используя functools.update_wrapper
,
from functools import partial, update_wrapper, WRAPPER_ASSIGNMENTS
SPECIAL_CONSTANT = 'bam'
def my_func(msg):
print msg
my_func.my_marker = SPECIAL_CONSTANT
my_partial = partial(my_func, 'hello world')
update_wrapper(my_partial, my_func, WRAPPER_ASSIGNMENTS + ('my_marker', ))
print my_partial.my_marker