0

i have a simple decorator,the output confused me.

def deco(func):
   def kdeco():
      print("before myfunc() called.")
      func()
      print("  after myfunc() called.")
   return kdeco

@deco
def myfunc():
   print(" myfunc() called.")

myfunc()

before myfunc() called.
 myfunc( deco(myfunc)()) called.
  after myfunc() called.

deco(myfunc)()

before myfunc() called.
before myfunc() called.
 myfunc() called.
  after myfunc() called.
  after myfunc() called.

i know the output of myfunc(),but the output of deco(myfunc)() confused me,whay the output of deco(myfunc)() can not be either of them in the following?

status one:

before myfunc() called.
before myfunc() called.
 myfunc() called.
 myfunc() called.
  after myfunc() called.
  after myfunc() called.

status two:

before myfunc() called.
 myfunc( deco(myfunc)()) called.
  after myfunc() called.
before myfunc() called.
 myfunc( deco(myfunc)()) called.
  after myfunc() called.
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
showkey
  • 482
  • 42
  • 140
  • 295
  • `deco(myfunc())` is equivalent to `deco(deco(myfunc))()` when there is no decorator on `myfunc()`, as you can see, the outer `deco` calls the inner `deco`, which in turn calls `myfunc`. – Joel Cornett Jan 16 '13 at 00:27
  • this should help http://stackoverflow.com/questions/739654/understanding-python-decorators – Jeff Jan 16 '13 at 00:28
  • 1
    As a side note: Don't indent with 3 spaces. PEP 8 recommends 4 spaces. The only good reason to violate this is to condense something that would otherwise be too wide (e.g., asking for help on SO with some code that you inherited that has unreasonable nesting or overly-complex one-liners), in which case 2 spaces is better than 3. – abarnert Jan 16 '13 at 01:39

3 Answers3

4

myfunc is itself already wrapped by your decorator. The name myfunc no longer points to the original method, it now points to the return value of deco(myfunc):

>>> def deco(func):
...    def kdeco():
...       print("before myfunc() called.")
...       func()
...       print("  after myfunc() called.")
...    return kdeco
... 
>>> @deco
... def myfunc():
...    print(" myfunc() called.")
... 
>>> myfunc
<function kdeco at 0x10068cb18>

That's because the @decorator syntax is the same as:

def myfunc():
    # body of function
myfunc = deco(myfunc)

So myfunc is already producing the lines before .., then .. called, then after ... Now you wrap that again. The wrapper prints before .., calls the wrapped myfunc (itself already wrapped), which prints before .., then .. called, then after .., and then the wrapper prints after ...

In a diagram:

call wrapped myfunc():
    kdeco: "before .."
    call original myfunc()
        original myfunc: ".. called"
    kdeco: "after .."

call deco(myfunc)():
    kdeco: "before .."
    call wrapped myfunc():
        kdeco: "before .."
        call original myfunc()
            original myfunc: ".. called"
        kdeco: "after .."
    kdeco: "after .."
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • After reading this through, it has almost exactly the same info as my answer, and was posted at exactly the same time, but I think it's more readable, so +1, and hopefully it gets accepted. – abarnert Jan 16 '13 at 01:40
  • @abarnert and 8 years later, the OP switched to yours! :-) – Martijn Pieters Sep 24 '21 at 21:34
1

The decorated function is only called once, hence it cannot claim to have been called twice.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
1

Decorators are actually just a shortcut to something you can do manually. This code:

@deco
def myfunc():
    print(" myfunc() called.")

Is equivalent to:

def myfunc():
    print(" myfunc() called.")
myfunc = deco(myfunc)

For the sake of discussing this, let's say the original were still available as original_myfunc (even though in reality it isn't).

So, when you do this:

deco(myfunc)()

What you end up calling is:

deco(deco(original_myfunc))()

And if you trace through that, it should be obvious why it prints what you expected. But let's do the exercise anyway.

First, you call deco(original_myfunc), and it does this:

def kdeco():
   print("before myfunc() called.")
   original_myfunc()
   print("  after myfunc() called.")

return kdeco

So that returns a function that prints the "before", calls original_myfunc, then prints the "after".

And now we pass deco(original_myfunc) to deco again, which does this:

def kdeco():
   print("before myfunc() called.")
   deco(original_myfunc)()
   print("  after myfunc() called.")
return kdeco

So, that returns a function that prints the "before", then calls deco(original_myfunc)—which itself prints the "before", calls original_myfunc, and prints "after"—then prints "after".

That's why you get the output you do.

abarnert
  • 354,177
  • 51
  • 601
  • 671