Атрибуты функции Python - использование и злоупотребления
Немногие знают об этой функции, но функции (и методы) Python могут иметь атрибуты . Вот:
>>> def foo(x):
... pass
...
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11
Каковы возможные применения и злоупотребления этой функцией в Python? Одно хорошее использование, о котором я знаю, - это PLY использование docstring для связывания правила синтаксиса с методом. Но как насчет пользовательских атрибутов? Есть ли веские причины для их использования?
Ответы
Ответ 1
Обычно я использую атрибуты функции как хранилище для аннотаций. Предположим, что я хочу писать в стиле С# (указывая, что определенный метод должен быть частью интерфейса веб-службы)
class Foo(WebService):
@webmethod
def bar(self, arg1, arg2):
...
то я могу определить
def webmethod(func):
func.is_webmethod = True
return func
Затем, когда приходит вызов webservice, я просматриваю этот метод, проверяю, имеет ли базовая функция атрибут is_webmethod (фактическое значение не имеет значения) и отказывается от службы, если метод отсутствует или не предназначен для вызова в Интернете.
Ответ 2
Я использовал их как статические переменные для функции. Например, учитывая следующий код C:
int fn(int i)
{
static f = 1;
f += i;
return f;
}
Я могу реализовать функцию аналогично в Python:
def fn(i):
fn.f += i
return fn.f
fn.f = 1
Это определенно попадет в конец "злоупотреблений" спектра.
Ответ 3
Вы можете делать объекты способом JavaScript... Это не имеет смысла, но оно работает;)
>>> def FakeObject():
... def test():
... print "foo"
... FakeObject.test = test
... return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
Ответ 4
Я использую их экономно, но они могут быть довольно удобными:
def log(msg):
log.logfile.write(msg)
Теперь я могу использовать log
во всем модуле и перенаправить вывод, просто установив log.logfile
. Есть много и много других способов сделать это, но этот легкий и простой. И хотя в первый раз, когда я это делал, он смеялся, я пришел к выводу, что он пахнет лучше, чем глобальная переменная logfile
.
Ответ 5
Функциональные атрибуты могут использоваться для записи облегченных замыканий, которые объединяют код и связанные данные:
#!/usr/bin/env python
SW_DELTA = 0
SW_MARK = 1
SW_BASE = 2
def stopwatch():
import time
def _sw( action = SW_DELTA ):
if action == SW_DELTA:
return time.time() - _sw._time
elif action == SW_MARK:
_sw._time = time.time()
return _sw._time
elif action == SW_BASE:
return _sw._time
else:
raise NotImplementedError
_sw._time = time.time() # time of creation
return _sw
# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()
+1,00934004784
+2,00644397736
+3,01593494415
Ответ 6
Иногда я использую атрибут функции для кэширования уже вычисленных значений. У вас также может быть общий декоратор, который обобщает этот подход. Помните о проблемах concurrency и побочных эффектах таких функций!
Ответ 7
Я создал этот вспомогательный декоратор, чтобы легко установить атрибуты функции:
def with_attrs(**func_attrs):
"""Set attributes in the decorated function, at definition time.
Only accepts keyword arguments.
E.g.:
@with_attrs(counter=0, something='boing')
def count_it():
count_it.counter += 1
print count_it.counter
print count_it.something
# Out:
# >>> 0
# >>> 'boing'
"""
def attr_decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return fn(*args, **kwargs)
for attr, value in func_attrs.iteritems():
setattr(wrapper, attr, value)
return wrapper
return attr_decorator
В качестве примера используется создание коллекции фабрик и запрос типа данных, который они могут создать на уровне функции.
Например (очень тупой):
@with_attrs(datatype=list)
def factory1():
return [1, 2, 3]
@with_attrs(datatype=SomeClass)
def factory2():
return SomeClass()
factories = [factory1, factory2]
def create(datatype):
for f in factories:
if f.datatype == datatype:
return f()
return None
Ответ 8
Я всегда полагал, что единственная причина, по которой это было возможно, состояла в том, что было логическое место, чтобы поставить doc-строку или другой такой материал. Я знаю, если бы я использовал его для любого производственного кода, он бы запутал большинство, кто его прочитал.