Атрибуты функции 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