Атрибути функції 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 використовує docstring для асоціації синтаксичного правила з методом. А як щодо користувацьких атрибутів? Чи є вагомі причини використовувати їх?


3
Перевірте PEP 232 .
user140352

2
Це дуже дивно? Загалом об’єкти Python підтримують спеціальні атрибути. Звичайно, деякі ні, особливо ті, що мають вбудований тип. Мені здається, що ті, хто це не підтримує, здаються винятками, а не правилом.
allyourcode


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
Я думав зробити саме це. Тоді я зупинився. "Це погана ідея?" Я замислився. Потім я блукав до SO. Після деякого бурчання навколо я знайшов це питання / відповідь. Все ще не впевнений, чи це гарна ідея.
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, це було б реалізовано з генератором в пітоні.

124
Це досить погана причина, щоб спростувати цю відповідь, яка демонструє аналогію між C і Python, не виступаючи за найкращий можливий спосіб написання цієї конкретної функції.
Роберт Россні

3
@RobertRossney Але якщо генератори - це шлях, то це неправильне використання атрибутів функції. Якщо так, то це зловживання. Не впевнений, чи варто заохочувати до зловживань, оскільки запитання також про це: P
allyourcode

1
Безумовно здається , що зловживання, в PEP 232, спасибі @ user140352 і коментар хопу і цей SO відповідь
варильні панелі

53

Ви можете робити об’єкти способом JavaScript ... Не має сенсу, але він працює;)

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

36
+1 Чудовий приклад зловживання цією функцією, що є однією з речей, про яку задається питання.
Майкл Данн

1
Чим це відрізняється від відповіді mipadi? Здається, це те саме, за винятком int, значення атрибута - це функція.
allyourcode

це def test()дійсно необхідно?
Кеертана Прабхакаран

15

Я використовую їх економно, але вони можуть бути досить зручними:

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

Тепер я можу використовувати logвесь модуль і перенаправляти вихід просто, встановивши log.logfile. Існує безліч інших способів досягти цього, але легкий і бруд цей простий. І хоча це було смішно пахне, коли я це зробив, я вважаю, що він пахне краще, ніж глобальна logfileзмінна.


7
Re запах: Хоча це не позбудеться глобального журналу файлів. Це просто викручує його в іншій глобальній функції журналу.
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
Навіщо натискати функції, коли у нас зручні заняття? І не будемо забувати, що класи можуть імітувати функцію.
muhuk

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прямо під декларацією, як декоратори старого стилю?
Ceasar Bautista

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

Швидке оновлення: items()замість Python 3 потрібно використовувати його iteritems().
Скотт означає

4

Іноді я використовую атрибут функції для кешування вже обчислених значень. Ви також можете мати загальний декоратор, який узагальнює такий підхід. Будьте в курсі проблем одночасності та побічних ефектів таких функцій!


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

1

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


1
Я погоджуюсь з вашим основним моментом щодо цього, швидше за все, заплутаного, але повторного виконання докерів: Так, але чому функції мають атрибути AD-HOC? Тут може бути фіксований набір атрибутів, один для зберігання docstring.
allyourcode

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