I have a question about function-based decorators. When I make the first call to sqrt(), I enter the decorator function, which I understand. However, every subsequent call to sqrt() only calls the temp_func, not the actual decorator again.

I thought decorators were called every time the method is called, but from the output it's obviously not happening. The temp function is being entered every time I call sqrt(), but not is_val_pos().

Could someone please elaborate on why this is happening? Thanks!

def is_val_pos(orig_func):
	print 'Entering decorator'
	def temp_func(val):
		print 'Entering temp func'
		if val < 0:
			return 0
		else:
			return orig_func(val)
	return temp_func

@is_val_pos
def sqrt(val):
	print 'Entering sqrt method'
	import math
	return math.pow(val, (1.0/2))

print sqrt(-1)
print sqrt(4)
print sqrt(16)

Output:

Entering decorator
Entering temp func
0
Entering temp func
Entering sqrt method
2.0
Entering temp func
Entering sqrt method
4.0

Decorator constructs the function, which takes the original function as argument that happens when function definations are created, after the meaning of original function becomes changed to include the additions from decorator.

The code

@is_val_pos
def sqrt(val):
	print 'Entering sqrt method'
	import math
	return math.pow(val, (1.0/2))

is equivalent to

def sqrt(val):
	print 'Entering sqrt method'
	import math
	return math.pow(val, (1.0/2))
sqrt = is_val_pos(sqrt)

This show that is_val_pos() is only called once after the definition of sqrt(), and then, sqrt() becomes the function temp_func() returned by is_val_pos(). A slight improvement is to use update_wrapper():

from functools import update_wrapper

def is_val_pos(orig_func):
	print 'Entering decorator'
	def temp_func(val):
		print 'Entering temp func'
		if val < 0:
			return 0
		else:
			return orig_func(val)
        update_wrapper(temp_func, orig_func)
	return temp_func

This makes temp_func() look more like sqrt(). For example after the definition, the value of sqrt.__name__ will be "sqrt" and not "temp_func" .

Edited 5 Years Ago by Gribouillis: n/a

I understand that sqrt=is_val_pos(sqrt) after the first call to is_val_pos, but why does the call to is_val_pos(sqrt) immediately enter temp_func? Shouldn't it enter the decorator, then enter the temp_func function?
Also, where is temp_func being called? All I see the decorator doing is defining the function, but not calling it. Where does temp_func get called?

Thanks for your help, Gribouillis!

Edited 5 Years Ago by Purkinje: n/a

I understand that sqrt=is_val_pos(sqrt) after the first call to is_val_pos, but why does the call to is_val_pos(sqrt) immediately enter temp_func? Shouldn't it enter the decorator, then enter the temp_func function?
Also, where is temp_func being called? All I see the decorator doing is defining the function, but not calling it. Where does temp_func get called?

Thanks for your help, Gribouillis!

The first call to is_val_pos() doesn't call temp_func(). temp_func() is called every time your code calls sqrt() since sqrt() was replaced with temp_func(). Then temp_func() calls the original sqrt() if the argument is >= 0. Here is a version with more prints

def is_val_pos(orig_func):
	print 'Entering is_val_pos()'
	def temp_func(val):
		print 'Entering temp func()'
		if val < 0:
			return 0
		else:
			return orig_func(val)
	return temp_func

@is_val_pos
def sqrt(val):
	print 'Entering sqrt() method'
	import math
	return math.pow(val, (1.0/2))

for value in (-1, 4, 16):
    print "Calling sqrt() with value %s" % str(value)
    rv = sqrt(value)
    print "sqrt() returned %s" % str(rv)

""" my output --->
Entering is_val_pos()
Calling sqrt() with value -1
Entering temp func()
sqrt() returned 0
Calling sqrt() with value 4
Entering temp func()
Entering sqrt() method
sqrt() returned 2.0
Calling sqrt() with value 16
Entering temp func()
Entering sqrt() method
sqrt() returned 4.0
"""

Edited 5 Years Ago by Gribouillis: n/a

Ah, that makes sense.

In the context of the code you just posted, what are the advantage(s) from using the update_wrapper to make temp_func look like orig_func (which is sqrt() in this example)?

Thanks again.

Edited 5 Years Ago by Purkinje: n/a

Ah, that makes sense.

In the context of the code you just posted, what are the advantage(s) from using the update_wrapper to make temp_func look like orig_func (which is sqrt() in this example)?

Thanks again.

The first advantage is sqrt's docstring which gets copied into temp_func(). For example if you write

@is_value_pos
def sqrt(val):
    "compute the square root"
    etc

then typing >>> help(sqrt) in a python shell will still print the help despite the fact that the original sqrt() was wrapped into temp_func(). This is also useful for some automated documentation tools like pydoc. Some tracing or debugging modules may also use the function's name ( sqrt.__name__ ).
Published python decorators always include the update_wrapper() call, because update_wrapper() was precisely designed for this use case. However, your decorator would work without this call.

Edited 5 Years Ago by Gribouillis: n/a

You've helped me a lot to understand this concept. Thanks for your time and expertise.

Edited 5 Years Ago by Purkinje: n/a

This question has already been answered. Start a new discussion instead.