Атрибуты функции Python - использует и злоупотребляет [закрыто]


196

Не многие знают об этой функции, но функции (и методы) 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 строки документации для связи правила синтаксиса с методом. Но как насчет пользовательских атрибутов? Есть ли веские причины для их использования?


3
Проверьте PEP 232 .
user140352

2
Это очень удивительно? В общем, объекты Python поддерживают специальные атрибуты. Конечно, некоторые этого не делают, особенно те, которые имеют встроенный тип. Для меня те, кто не поддерживают это, кажутся исключениями, а не правилом.
allyourcode

3
Одно приложение в Django: настроить список
смен

2
@GrijeshChauhan Я пришел к этому вопросу, увидев эти документы!
Александр Сурафель

5
Жаль, что это закрыто, я хотел добавить, что вы можете прикрепить любые пользовательские исключения, которые может вызвать функция, чтобы обеспечить легкий доступ при перехвате в вызывающем коде. Я бы привел иллюстративный пример, но лучше всего это сделать в ответе.
Уилл Харди

Ответы:


154

Я обычно использую атрибуты функции в качестве хранилища для аннотаций. Предположим, я хочу написать в стиле C # (указывая, что определенный метод должен быть частью интерфейса веб-службы)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

тогда я могу определить

def webmethod(func):
    func.is_webmethod = True
    return func

Затем, когда поступает вызов веб-службы, я просматриваю метод, проверяю, имеет ли базовая функция атрибут is_webmethod (фактическое значение не имеет значения), и отказываюсь от службы, если метод отсутствует или не предназначен для вызова через Интернет.


2
Как вы думаете, есть недостатки в этом? Например, что если две библиотеки пытаются написать один и тот же специальный атрибут?
allyourcode

18
Я думал сделать именно это. Затем я остановился. "Это плохая идея?" Я поинтересовался. Затем я забрел на ТАК. После некоторого возни, я нашел этот вопрос / ответ. Все еще не уверен, что это хорошая идея.
allyourcode

7
Это определенно наиболее законное использование атрибутов функции из всех ответов (по состоянию на ноябрь 2012 г.). Большинство (если не все) других ответов используют атрибуты функций в качестве замены глобальных переменных; тем не менее, они НЕ избавляются от глобального состояния, что в точности является проблемой глобальных переменных. Это отличается, потому что как только значение установлено, оно не изменяется; это постоянно. Хорошим следствием этого является то, что вы не столкнетесь с проблемами синхронизации, которые свойственны глобальным переменным. Да, вы можете предоставить свою собственную синхронизацию, но в этом суть: это не безопасно автоматически.
allyourcode

Действительно, я говорю, что если атрибут не меняет поведение рассматриваемой функции, это хорошо. Сравните с.__doc__
Дима Тиснек

Этот подход также можно использовать для добавления описания вывода к оформленной функции, которая отсутствует в Python 2. *.
Juh_

126

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

Это определенно попадет в конец «злоупотреблений» спектра.


2
Интересный. Есть ли другие способы реализации статических переменных в python?
Эли Бендерский

4
-1, это будет реализовано с помощью генератора в python.

124
Это довольно плохая причина для занижения этого ответа, который демонстрирует аналогию между C и Python, а не пропагандирует наилучший из возможных способов написания этой конкретной функции.
Роберт Росни

3
@RobertRossney Но если генераторы - это путь, то это плохое использование атрибутов функций. Если так, то это злоупотребление. Не уверен, стоит ли возражать против злоупотреблений, так как вопрос тоже касается тех: P
allyourcode

1
Определенно похоже на злоупотребление, согласно PEP 232, спасибо @ user140352, а также комментариям хмеля и этому SO-ответу
hobs

53

Вы можете делать объекты способом JavaScript ... Это не имеет смысла, но это работает;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo

36
+1 Прекрасный пример злоупотребления этой функцией, которая является одной из вещей, которые задают вопрос.
Майкл Данн

1
Чем это отличается от ответа Мипади? Кажется, это то же самое, за исключением того, что вместо int значение атрибута является функцией.
allyourcode

это def test()действительно необходимо?
Киртана Прабхакаран

15

Я использую их экономно, но они могут быть довольно удобными:

def log(msg):
   log.logfile.write(msg)

Теперь я могу использовать logвесь модуль и перенаправлять вывод, просто установив log.logfile. Есть много и много других способов сделать это, но этот легкий и грязный. И хотя в первый раз, когда я это сделал, пахло забавно, я пришел к выводу, что пахнет лучше, чем наличие глобальной logfileпеременной.


7
Запах: Это не избавляет от глобального файла журнала, хотя. Он просто сжимает его в другой глобальной функции log.
allyourcode

2
@allyourcode: Но это может помочь избежать конфликта имен, если вам нужно иметь кучу глобальных лог-файлов для разных функций в одном модуле.
firegurafiku

11

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

#!/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


3
Зачем нажимать функции, когда у нас есть классы под рукой? И давайте не будем забывать, что классы могут эмулировать функцию.
Мухук

1
и time.sleep(1)лучше чемos.system('sleep 1')
Борис Горелик

3
@bgbg Правда, хотя этот пример не о сне.
allyourcode

Это определенно злоупотребление; использование функций здесь совершенно бесплатно. Мухук абсолютно прав: классы - лучшее решение.
allyourcode

1
Я также спросил бы: «В чем преимущество этого класса?» чтобы устранить недостаток этого не столь очевидного для многих программистов Python.
cjs

6

Я создал этот вспомогательный декоратор для легкой установки атрибутов функции:

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

Как декоратор помогает? Почему бы просто не установить factory1.datatype=listпрямо под объявлением, как декораторы старого стиля?
Цезарь Баутиста

2
2 основных отличия: стиль, несколько атрибутов легче установить. Вы можете определенно установить его как атрибут, но, по моему мнению, он будет многословным с несколькими атрибутами, и вы также получите возможность расширить декоратор для дальнейшей обработки (например, определить значения по умолчанию в одном месте вместо всех мест, которые используют функцию, или необходимость вызовите дополнительную функцию после того, как атрибуты были установлены). Есть и другие способы достижения всех этих результатов, я просто нахожу это чище, но с радостью передумаю;)
DiogoNeves

Быстрое обновление: с Python 3 вам нужно использовать items()вместо iteritems().
Скотт Минс

4

Иногда я использую атрибут функции для кэширования уже вычисленных значений. Вы также можете иметь универсальный декоратор, который обобщает этот подход. Помните о проблемах параллелизма и побочных эффектах таких функций!


Мне нравится эта идея! Более распространенным приемом для кэширования вычисленных значений является использование dict в качестве значения по умолчанию атрибута, который вызывающая сторона никогда не должна предоставлять - поскольку Python оценивает это только один раз при определении функции, вы можете хранить там данные и сохранять их вокруг. Хотя использование атрибутов функций может быть менее очевидным, оно кажется мне гораздо менее хакерским.
Сорен Бьорнстад

1

Я всегда полагал, что единственная причина, по которой это было возможно, заключалась в том, что было логичное место для размещения строки документации или другого подобного материала. Я знаю, если бы я использовал его для какого-либо производственного кода, это бы сбило с толку большинство, кто его читает.


1
Я согласен с вашим главным утверждением о том, что это скорее всего сбивает с толку, но поясню: да, но почему функции имеют атрибуты AD-HOC? Может быть фиксированный набор атрибутов, один для хранения строки документации.
allyourcode

@allyourcode Наличие общего кейса, а не специальных кейсов, встроенных в язык, упрощает работу и повышает совместимость со старыми версиями Python. (Например, код, который устанавливает / манипулирует строкой документации, будет по-прежнему работать с версией Python, которая не выполняет строки документации, если он обрабатывает случай, когда атрибут не существует.)
cjs
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.