1

Recently I'm learning Python decorator and the use of functools.wraps.

def a():
    def b():
        def c():
            print('hello')
        return c
    return b


print a.__name__
#output:a

I understand why the output is a.But I don't know how __name__ change in the following code.

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('...')
def simple():
    print('*' * 20)

print simple.__name__
#output:wrapper

Why the output is 'wrapper' rather than 'decorator' or 'log'?

cp1d
  • 13
  • 4
  • Maybe not so related, but you should have a look at [What does `functools.wraps` do?](http://stackoverflow.com/questions/308999/what-does-functools-wraps-do) – Moses Koledoye Sep 21 '16 at 10:46
  • 2
    Are you aware how decorators works internally? Rewriting it without syntactic sugar helps a lot in comprehending what happens here. – Łukasz Rogalski Sep 21 '16 at 10:49

3 Answers3

2

Some basics:

@decorator
def f():
    pass

is equivalent to:

def f():
    pass
f = decorator(f)

Decorator with args:

@decorator(*args, **kwargs)
def f():
    pass

is equivalent to:

def f():
    pass
decorator_instance = decorator(*args, **kwargs)
f = decorator_instance(f)

With knowing so, we may rewrite your example to:

def simple():
    print('*' * 20)

log_instance = log('...')
simple = log_instance(simple)

Let's analyze what happens in last two lines:

  • log_instance is a decorator function, and text variable within it is equal to '...'
  • Since decorator (regardless of text value) returns function named wrapper, simple is replaced with function named wrapper
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
0

The point of decorators is to replace a function or class with what is returned by the decorator, when called with that function/class as argument. Decorators with arguments are a bit more convoluted, as you first call the outer method (log) to obtain a "parameterized" decorator (decorator), then you call that one and obtain the final function (wrapper), which will replace the decorated function (simple).

So, to give it some structure,

  1. call log with '...' as argument and obtain decorator
  2. call decorator with simple as argument and obtain wrapper
  3. replace simple with wrapper
BlackBear
  • 22,411
  • 10
  • 48
  • 86
0
@log('...')
def simple(...

is equivalent to

def simple(...
simple = log('...')(simple)

so log is actually called, returning decorator, which is called with simple as argument which is then replaced by decorator's return value, which is the function wrapper, thus its __name__ is wrapper.

Tobias Kienzler
  • 25,759
  • 22
  • 127
  • 221