Почему Python декораторы, а не замыкания? - PullRequest
13 голосов
/ 19 октября 2008

Я до сих пор не разбираюсь в декораторах Python.

Я уже начал использовать множество замыканий для таких вещей, как настройка функций и классов в моем коде.

Например.

class Node :
    def __init__(self,val,children) :
        self.val = val
        self.children = children

def makeRunner(f) :
    def run(node) :
        f(node)
        for x in node.children :
            run(x)
    return run

tree=Node(1,[Node(2,[]),Node(3,[Node(4,[]),Node(5,[])])])

def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
printTree(tree)

Насколько я вижу, декораторы - это просто другой синтаксис для выполнения чего-то подобного.

Вместо

def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)

Я бы написал:

@makeRunner
def printTree(n) : print "%s," % n.val

Это все, что нужно декораторам? Или есть принципиальная разница, которую я пропустил?

Ответы [ 4 ]

12 голосов
/ 03 декабря 2008

Хотя синтаксически, декораторы - это просто «сахар», это не лучший способ думать о них.

Декораторы позволяют встраивать функциональность в существующий код без его фактического изменения. И они позволяют вам делать это декларативным образом.

Это позволяет использовать декораторы для аспектно-ориентированного программирования (AOP). Таким образом, вы хотите использовать декоратор, когда у вас есть сквозная задача, которую вы хотите заключить в одном месте.

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

Оформление метода - это понятие, такое как ООП или списочное понимание. Как вы указали, это не всегда уместно, и может быть перегружен . Но в нужном месте это может быть полезно для того, чтобы сделать код более модульным и отделенным.

8 голосов
/ 19 октября 2008

Ваши примеры - это реальный код или просто примеры?

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

Этап 1: избегать декораторов

def run(rootnode, func):
    def _run(node): # recursive internal function
        func(node)
        for x in node.children:
            _run(x) # recurse
    _run(rootnode) # initial run

Этот метод запуска устарел makeRunner. Ваш пример превращается в:

def pp(n): print "%s," % n.val
run(tree, pp)

Однако это полностью игнорирует генераторы, поэтому…

Этап 2: использование генераторов

class Node :
    def __init__(self,val,children) :
        self.val = val
        self.children = children

    def __iter__(self): # recursive
        yield self
        for child in self.children:
            for item in child: # recurse
                yield item

def run(rootnode, func):
    for node in rootnode:
        func(node)

Ваш пример остается

def pp(n): print "%s," % n.val
run(tree, pp)

Обратите внимание, что специальный метод __iter__ позволяет нам использовать конструкцию for node in rootnode:. Если вам это не нравится, просто переименуйте метод __iter__, например, в. walker и измените цикл run на: for node in rootnode.walker():
Очевидно, что функция run может быть методом class Node.

Как видите, я предлагаю вам напрямую использовать run(tree, func) вместо привязки их к имени printTree, но вы можете использовать их в декораторе или использовать функцию functools.partial:

printTree= functools.partial(run, func=pp)

и с тех пор вы бы просто

printTree(tree)
0 голосов
/ 24 августа 2017

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

A closure - анонимная функция, которая ссылается на свои параметры или другие переменные вне своей области действия.

Так что в основном декораторы используют замыкания , а не заменяют их.

def increment(x):
    return x + 1

def double_increment(func):
    def wrapper(x):
        print 'decorator executed'
        r = func(x)   # --> func is saved in __closure__
        y = r * 2
        return r, y
    return wrapper

@double_increment
def increment(x):
    return x + 1

>>> increment(2)
decorator executed
(3, 6)

>>> increment.__closure__
(<cell at 0x02C7DC50: function object at 0x02C85DB0>,)

>>> increment.__closure__[0].cell_contents 
<function increment at 0x02C85DB0>

Таким образом, декоратор сохраняет исходную функцию с closure .

В чем разница между замыканиями и декораторами в Python?

0 голосов
/ 29 октября 2014

Следуя справке AOP Dutch Master, вы обнаружите, что использование декораторов становится особенно полезным, когда вы начинаете добавление параметров для изменения поведения декорированной функции / метода и считывания того, что выше определения функции намного проще.

В одном проекте, который я помню, нам нужно было контролировать тонны задач с сельдереем, и поэтому мы пришли к идее использовать декоратор для подключения и настройки по мере необходимости, что-то вроде:

class tracked_with(object):
    """
    Method decorator used to track the results of celery tasks.
    """
    def __init__(self, model, unique=False, id_attr='results_id',
                 log_error=False, raise_error=False):
        self.model = model
        self.unique = unique
        self.id_attr = id_attr
        self.log_error = log_error
        self.raise_error = raise_error

    def __call__(self, fn):

        def wrapped(*args, **kwargs):
            # Unique passed by parameter has priority above the decorator def
            unique = kwargs.get('unique', None)
            if unique is not None:
                self.unique = unique

            if self.unique:
                caller = args[0]
                pending = self.model.objects.filter(
                    state=self.model.Running,
                    task_type=caller.__class__.__name__
                )
                if pending.exists():
                    raise AssertionError('Another {} task is already running'
                                         ''.format(caller.__class__.__name__))

            results_id = kwargs.get(self.id_attr)
            try:
                result = fn(*args, **kwargs)

            except Retry:
                # Retry must always be raised to retry a task
                raise

            except Exception as e:
                # Error, update stats, log/raise/return depending on values
                if results_id:
                    self.model.update_stats(results_id, error=e)
                if self.log_error:
                    logger.error(e)
                if self.raise_error:
                    raise
                else:
                    return e

            else:
                # No error, save results in refresh object and return
                if results_id:
                    self.model.update_stats(results_id, **result)
                return result

        return wrapped

Затем мы просто украсили метод run для задач параметрами, необходимыми для каждого случая, например:

class SomeTask(Task):

    @tracked_with(RefreshResults, unique=True, log_error=False)
    def run(self, *args, **kwargs)...

Тогда изменение поведения задачи (или вообще удаление отслеживания) означало изменение одного параметра или закомментирование украшенной строки. Супер прост в реализации, но, что более важно, супер легко понять при осмотре.

...