Які існують для декораторів Python? [зачинено]


337

Хоча мені подобається думати про себе як про грамотно розробленому Python-кодері, одним із аспектів мови, який я ніколи не вдавався виправити, є декоратори.

Я знаю, що вони (поверхово), я читав підручники, приклади, запитання щодо переповнення стека, і я розумію синтаксис, можу писати власний, періодично використовувати @classmethod і @staticmethod, але мені ніколи не трапляється використовувати декоратор для вирішення проблеми в моєму власному коді Python. Я ніколи не стикаюся з проблемою, де думаю: "Гм ... це схоже на роботу декоратора!"

Тож мені цікаво, чи можете ви, хлопці, запропонувати кілька прикладів того, де ви використовували декораторів у власних програмах, і, сподіваюся, у мене буде "А-ха!" момент і дістати їх.


5
Також декоратори корисні для пам'яті - це кешування результатів функції, що повільно обчислюється. Декоратор може повернути функцію, яка перевіряє вхідні дані, і якщо вони вже були представлені, повернути кешований результат.
Петро

1
Зауважте, що в Python є вбудований декоратор, functools.lru_cacheякий робить саме те, що сказав Петро, ​​оскільки Python 3.2 вийшов у лютому 2011 року.
Taegyung

Зміст бібліотеки декораторів Python повинен дати вам гарне уявлення про інші види використання для них.
мартіно

Відповіді:


126

Я використовую декораторів головним чином для цілей часу

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...

13
У Unix time.clock()вимірює час процесора. Ви можете time.time()замість цього використати, якщо ви хочете виміряти час настінного годинника.
Джабба

20
Чудовий приклад! Поняття не маю, що це робить. Пояснення, що ви там робите, і як декоратор вирішує проблему, було б дуже приємно.
MeLight

7
Ну, це вимірює час, який потрібно myFunctionпробігти ...
RSabet

98

Я використовував їх для синхронізації.

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

Як зазначено в коментарях, оскільки Python 2.5 ви можете використовувати withоператор спільно з об'єктом threading.Lock(або multiprocessing.Lockз версії 2.6) для спрощення реалізації декоратора просто:

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

Незалежно від цього ви використовуєте його так:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

В основному він просто ставить lock.acquire()/ lock.release()з обох боків виклику функції.


18
Можливо, виправдано, але декоратори за своєю суттю плутають, особливо. для нобудів першого року, які приходять за тобою і намагаються модифікувати свій код. Уникайте цього просто: просто do_something () додайте його код у блок під заголовком "з блокуванням:", і кожен може чітко бачити вашу мету. Декораторів сильно зловживають люди, які хочуть здатися розумними (а багато насправді є), але тоді код потрапляє до простого смертного і стає пошкодженим.
Кевін Дж. Райс

18
@ KevinJ.Rice Обмеження коду таким чином, щоб "першокласники" могли краще зрозуміти, це жахлива практика. Синтаксис декораторів набагато простіше читати і значно роз’єднує код.
TaylerJones

18
@TaylerJones, читабельність коду - це майже мій пріоритет при написанні. Код читається 7+ разів щоразу, коли його змінюють. Важко зрозуміти код (для ноубів або для експертів, які працюють під тиском часу) - це технічна заборгованість, яку потрібно сплачувати кожного разу, коли хтось відвідує вихідне дерево.
Кевін Дж. Райс

@TaylerJones Одне з найважливіших завдань програміста - забезпечити чіткість.
JDOaktown

71

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

Наприклад, замість:

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

Я просто заявляю:

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

і accepts()робить всю роботу для мене.


15
Для всіх, хто цікавиться, є реалізація програми @acceptsPEP 318.
martineau

2
Я думаю, що є помилка .. перший метод повинен бути прийнятий .. ви оголосили обидва як "myMethod"
DevC

1
@DevC Ні, це не схоже на помилку друку. Оскільки це явно не є реалізацією "приймає (..)", а тут "приймає (..)" робить роботу, яка б інакше була виконана двома рядками на початку "myMethod (..)" - це лише інтерпретація, яка підходить.
Євгеній Сергєєв

1
Вибачте за помилку, я просто хотів зазначити, що перевірка типу переданих аргументів і підняття TypeError в іншому випадку вважається поганою практикою, оскільки він не збирається приймати, наприклад, int, якщо він перевіряє лише на floats, і тому що зазвичай сам код повинен адаптуватися до різних видів значень, переданих для досягнення максимальної гнучкості.
Gustavo6046

2
Рекомендований спосіб перевірити тип в Python - це через вбудовану isinstance()функцію, як це робиться в PEP 318 реалізації декоратора. Оскільки його classinfoаргумент може бути одного або декількох типів, його використання також пом'якшить (дійсні) заперечення @ Gustavo6046. У Python також є Numberабстрактний базовий клас, тому isinstance(42, numbers.Number)можливі дуже загальні тести на зразок .
мартіно

48

Декоратори використовуються для всього, що ви хочете прозоро «обернути» додатковою функціональністю.

Django використовує їх для обгортання функцій "необхідного входу" на функції перегляду , а також для реєстрації функцій фільтра .

Ви можете використовувати декоратори класів для додавання іменних журналів до класів .

Будь-яка достатньо загальна функціональність, яку ви можете "застосувати" до існуючого класу або поведінки функції, - це чесна гра для прикраси.

Також обговорюються випадки використання на групі новин Python-Dev, на яку вказує PEP 318 - «Декоратори для функцій та методів» .


Cherrypy використовує @ cherrypy.expose, щоб постійно розуміти, які функції є загальнодоступними, а які - прихованими. Це було моє перше вступ, і я звик до його форми там.
Марк Максмайстер

26

Для насадок можна написати декоратор, який забезпечує функцію або метод тестування одиниці з декількома наборами параметрів:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

23

Бібліотека Twisted використовує декоратори в поєднанні з генераторами, щоб створити ілюзію, що асинхронна функція є синхронною. Наприклад:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

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


14

Одне очевидне використання, звичайно, для ведення журналу:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()

10

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


6

Я використовую наступний декоратор для створення функції безпечної нитки. Це робить код більш читабельним. Він майже схожий на той, який запропонував Джон Фугі, але різниця полягає в тому, що одна робота над однією функцією і що немає необхідності створювати об'єкт блокування чітко.

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var

1
Чи означає це, що кожна функція, така прикрашена, має власний замок?
скорботу

1
@grieve так, щоразу, коли декоратор використовується (називається), він створює новий об'єкт блокування для функції / методу, який декорується.
мартіно

5
Це справді небезпечно. Метод inc_var () є "безпечним для потоків", оскільки одночасно може викликати його лише одна людина. Однак, оскільки метод працює над змінною члена "var", і, мабуть, інші методи також можуть працювати на змінній члена "var", і цей доступ не є безпечним для потоків, оскільки блокування не поділяється. Цей спосіб дає користувачеві класу X помилкове почуття безпеки.
Боб Ван Зант

Це не є безпечним потоком, доки не буде використано єдиний замок.
Чанду

5

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

CherryPy використовує диспетчеризацію об'єктів для відповідності URL-адрес об'єктам і, зрештою, методів. Декоратори за цими методами сигналізують, чи дозволено CherryPy використовувати ці методи. Наприклад, адаптований з підручника :

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())

Це не правда. Декоратор може повністю змінити поведінку функції.
рекурсивна

Добре. Але як часто декоратор "повністю змінює поведінку функції?" З того, що я бачив, коли вони не використовуються для визначення властивостей, вони просто використовуються для кодового коду. Я відредагував свою відповідь.
Нікіл Челлі

5

Я використовував їх недавно, працюючи над веб-додатком у соціальних мережах. Для спільноти / груп я повинен був надати дозвіл на членство для створення нового обговорення та відповіді на повідомлення про те, що ви повинні бути членом цієї конкретної групи. Отже, я написав декоратора @membership_requiredі поклав туди, куди вимагав, на мій погляд.


1

Я використовую цей декоратор для виправлення параметра

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

це написано, коли я рефактор деяких функцій потрібно передавати аргумент "wanN", але в старих кодах я передав лише N або "N"


1

Декоратор можна використовувати для легкого створення змінних методів функції.

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1

6
Дякую за Ваш приклад, але (аполігії) я повинен сказати WTF - Навіщо ви використовуєте це? Він має величезний потенціал для заплутування людей. Звичайно, я поважаю потреби у кращих випадках використання, але ти стикаєшся із поширеною проблемою у багатьох недосвідчених розробників Python - недостатньо використовуючи класи. Тобто достатньо простого класу var count, ініціалізувати його та використовувати. Noobs прагнуть писати drop-thru (некласовий код) і намагаються впоратися з недостатнім функціоналом класу із складними обхідними шляхами. Будь ласка, не варто? Будь ласка? вибачте за арфу, дякую за вашу відповідь, але ви натиснули на мене гарячу кнопку.
Кевін Дж. Райс

Я став би -1 на це, якби він з'явився як запит на те, щоб переглядати код, і тому я також -1 на цьому як хороший пітон.
Techdragon

Симпатичний. Дурне, але мило. :) Я не проти атрибута випадкових функцій, але вони є такою рідкісною річчю в типовому Python-коді, що якщо я збираюся його використовувати, я б краще це зробити явно, а не ховати його під декоратором.
PM 2Ring
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.