Атрибуты функции 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 для связывания синтаксического правила с методом. Но как насчет пользовательских атрибутов ? Есть ли веские причины их использовать ?
8 ответов
обычно я использую атрибуты функций в качестве хранилища для аннотаций. Предположим, я хочу написать в стиле C# (указывая, что определенный метод должен быть частью интерфейса веб-службы)
class Foo(WebService):
@webmethod
def bar(self, arg1, arg2):
...
тогда я могу определить
def webmethod(func):
func.is_webmethod = True
return func
затем, когда приходит вызов webservice, я ищу метод, проверяю, имеет ли базовая функция атрибут is_webmethod (фактическое значение не имеет значения) и отказываюсь от службы, если метод отсутствует или не предназначен для звонили через интернет.
я использовал их в качестве статических переменных для функции. Например, учитывая следующий код 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
это определенно попадет в" злоупотребления " конец спектра.
вы можете делать объекты способом JavaScript... Это не имеет смысла, но это работает;)
>>> def FakeObject():
... def test():
... print "foo"
... FakeObject.test = test
... return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
Я использую их редко, но они могут быть довольно удобным:
def log(msg):
log.logfile.write(msg)
теперь я могу использовать log
во всем моем модуле и перенаправить вывод, просто установив log.logfile
. Есть много и много других способов сделать это, но этот легкий и простой. И хотя это пахло смешно в первый раз, когда я это сделал, я пришел к выводу, что это пахнет лучше, чем иметь глобальный logfile
переменной.
атрибуты функции можно использовать для записи облегченных замыканий, которые обертывают код и связанные данные вместе:
#!/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
иногда я использую атрибут функции для кэширования уже вычисленных значений. Вы также можете иметь общий декоратор, который обобщает этот подход. Имейте в виду проблемы параллелизма и побочные эффекты таких функций!
Я всегда предполагал, что единственная причина, по которой это было возможно, заключалась в том, что было логическое место для размещения doc-строки или другого такого материала. Я знаю, что если бы я использовал его для любого производственного кода, это смутило бы большинство, кто его читал.
Я создал этот вспомогательный декоратор, чтобы легко установить атрибуты функции:
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