Атрибуты функции 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-строку или другой такой материал. Я знаю, если бы я использовал его для любого производственного кода, он бы запутал большинство, кто его прочитал.