Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Python decorators rather than closures?

I still haven't got my head around decorators in Python.

I've already started using a lot of closures to do things like customize functions and classes in my coding.

Eg.

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)

As far as I can see, decorators are just a different syntax for doing something similar.

Instead of

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

I would write :

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

Is this all there is to decorators? Or is there a fundamental difference that I've missed?

like image 250
interstar Avatar asked Oct 18 '08 23:10

interstar


People also ask

What is the difference between closures and decorators in Python?

A decorator is a function that takes in a function and returns an augmented copy of that function. When writing closures and decorators, you must keep the scope of each function in mind. In Python, functions define scope. Closures have access to the scope of the function that returns them; the decorator's scope.

What is the biggest advantage of the decorator in Python?

Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.

What is the benefit of using a decorator Python?

You'll use a decorator when you need to change the behavior of a function without modifying the function itself. A few good examples are when you want to add logging, test performance, perform caching, verify permissions, and so on. You can also use one when you need to run the same code on multiple functions.

Why do we need closure in Python?

Python closures help avoiding the usage of global values and provide some form of data hiding. They are used in Python decorators.


2 Answers

While it is true that syntactically, decorators are just "sugar", that is not the best way to think about them.

Decorators allow you to weave functionality into your existing code without actually modifying it. And they allow you to do it in a way that is declarative.

This allows you to use decorators to do aspect-oriented programming (AOP). So you want to use a decorator when you have a cross-cutting concern that you want to encapsulate in one place.

The quintessential example would probably be logging, where you want to log the entry or exit of a function, or both. Using a decorator is equivalent to applying advice (log this!) to a joinpoint (during method entry or exit).

Method decoration is a concept like OOP or list comprehensions. As you point out, it is not always appropriate, and can be overused. But in the right place, it can be useful for making code more modular and decoupled.

like image 135
Dutch Masters Avatar answered Oct 15 '22 08:10

Dutch Masters


Are your examples real code, or just examples?

If they're real code, I think you overuse decorators, probably because of your background (i.e. you are used to other programming languages)

Stage 1: avoiding decorators

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

This run method obsoletes makeRunner. Your example turns to:

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

However, this ignores completely generators, so…

Stage 2: using generators

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)

Your example remains

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

Note that the special method __iter__ allows us to use the for node in rootnode: construct. If you don't like it, just rename the __iter__ method to e.g. walker, and change the run loop into: for node in rootnode.walker():
Obviously, the run function could be a method of class Node instead.

As you see, I suggest you use directly run(tree, func) instead of binding them to the name printTree, but you can use them in a decorator, or you can make use of the functools.partial function:

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

and from then on, you would just

printTree(tree)
like image 31
tzot Avatar answered Oct 15 '22 08:10

tzot