Как атрибуты в функциях могут выдержать перенос?

Допустим, у меня есть следующая функция, которая имеет атрибут, который помечает ее для специальной обработки в подсистеме обратного вызова:

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
Другие вопросы по тегам