Каковы некоторые общие применения для декораторов Python? [закрыто]


337

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

Я знаю, что они (поверхностно), я прочитал учебники, примеры, вопросы по переполнению стека, и я понимаю синтаксис, могу написать свой, иногда использовать @classmethod и @staticmethod, но мне никогда не приходит в голову использовать декоратор, чтобы решить проблему в моем собственном коде Python. Я никогда не сталкиваюсь с проблемой, когда думаю: «Хм ... это похоже на работу декоратора!»

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


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

1
Обратите внимание, что в Python есть встроенный декоратор, functools.lru_cacheкоторый делает именно то, что сказал Питер, начиная с Python 3.2, выпущенного в феврале 2011 года.
Taegyung

Содержимое библиотеки Python Decorator должно дать вам хорошее представление о других способах их использования.
Мартино

Ответы:


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. Таким образом, вместо того, чтобы повторять один и тот же подсчет параметров, снова и снова вызывать исключение mumbo-jumbo.

Например, вместо:

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

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

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

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


15
Для всех, кто интересуется, есть реализация @acceptsв PEP 318.
Мартино

2
Я думаю, что есть опечатка .. первый метод должен быть принят .. вы объявили оба как "myMethod"
DevC

1
@DevC Нет, это не похоже на опечатку. Поскольку это явно не реализация «accept (..)», а здесь «accept (..)» выполняет работу, которая в противном случае выполнялась бы двумя строками в начале «myMethod (..)» - это единственное толкование, которое подходит.
Евгений Сергеев

1
Извините за удар, я просто хотел отметить, что проверка типа передаваемых аргументов и вызов TypeError в противном случае считается плохой практикой, потому что он не будет принимать, например, int, если он проверяет только для чисел с плавающей запятой, и потому что обычно сам код должен адаптироваться к различным типам значений, передаваемых для максимальной гибкости.
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 yes, каждый раз, когда декоратор используется (вызывается), он создает новый объект блокировки для декорируемой функции / метода.
Мартино

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())

Это неправда. Декоратор может полностью изменить поведение функции.
рекурсивный

Ладно. Но как часто декоратор «полностью меняет поведение функции?» Из того, что я видел, когда они не используются для указания свойств, они просто используются для стандартного кода. Я отредактировал свой ответ.
Nikhil Chelliah

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 of count, инициализируйте его и используйте его. Noobs, как правило, пишут по каплям (не основанный на классах код) и пытаются справиться с отсутствием функциональности класса с помощью сложных обходных путей. Пожалуйста, не надо? Пожалуйста? извините за арфу, спасибо за ваш ответ, но вы нажали горячую кнопку для меня.
Кевин Дж. Райс

Я был бы -1 на этом, если бы это показалось как запрос на извлечение кода для проверки кода, и поэтому я тоже -1 на этом хорошем Python.
Techdragon

Милый. Глупо, но мило. :) Я не против случайного атрибута функции, но в обычном коде Python они настолько редки, что, если я собираюсь использовать его, я бы предпочел сделать это явно, а не скрывать его под декоратором.
PM 2Ring
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.