Decorator in Python


A decorator takes in a function, adds some functionality and returns it. In this tutorial, you will return how you can create a decorator and why you should use it.

Descriptor in Python

Python has an interesting feature called decorators, to add functionality to an existing code.
This also called metaprogramming because a part of the paogram tries to modify another part of the program at compile time.

Prerequisites for learning decorators

Everything is object

In order to unstand about decorators, we must first know a few basic things in Python.
We must be comfortable with the fact that everything in Python(Yes! Even classed), are objects. Names that we define are simply identifiers bound to these objects. Functions are no exceptions, they are objects too(with attributes). Various different names can be bound to the same function object.
Here is an example:

def first(msg):
	print(msg)

first("hello")
second = first
second("hello")

Output:
在这里插入图片描述
When you run the code, both function first and second give the same output. Here, name first and second refer to the same function object.

Now things start getting weirder.

Functions can be passed as atguments to anthor function.

If you have used function like map, filter and reduce in Python, then you already know about this.

Such functions that take other functions as arguments are also called higher order functions. Here is an example of such a function.

def inc(x):
	return x + 1

def dec(x):
	return x - 1

def operate(func, x):
	result = func(x)
	return result

We invoke the function as follows.

>>> operate(inc, 3)
4
>>> operate(dec, 3)
2

Furthermore, a function can return anthor function.

def is_called():
	def is_returned():
		print("Hello")
	return is_returened

new = is_called
new()
type(new)

Output:

Hello
<class 'function'>

Here, is_returned() is a nested function which is defined and returned each time we call is_called().
Finally, we must know about Closures in Python.

Python Clousures

In this part, you’ll learn about Python closure, how to define a closure, and the reasons you should use.

Nonlocal variable in nested function

Before getting into what a closure is, we have to first understand what a nested function and nonlocal variable is.
A function defined inside anthor function is called nested function. Nested functions can access variables of the enclosing scope.
In Python, these non-local variables are read-only by default and we must declare them explicitly as non-local(using nonlocal keyword) in order to modify them.
Following is an example of a nested function accessing a non-local variable.

def print_msg(msg):
	# This is the outer enclosing function
	
	def printer():
		# This is the nested function
		print(msg)
	
	# Calling this function
	printer()


# We execute the function
# Output: Hello
print_msg("Hello") 

Output:

Hello

We can see that the nested printer() function was able to access the non-local msg variable of the enclosing function.

Nonlocal variables

  1. #global和nonlocal的特点类似,但是nonlocal只会修改离他最近的作用域上的变量,不会修改全局变量
  2. global是把全局变量引用到局部变量作用域中来,这样就可以修改一个全局变量,如果单是在局部作用域中引用全局变量或看一下全部变量,那不用global也可以做到
  3. 引用局部变量之nonlocal
# a=10在此处则报错
def outer():
	# enclosing function
	# 此处必须定义一个a,否则 nonlocal a 会报错
    a = 10
    def inner():
    	# nested function
        nonlocal a
        a=20
    inner()
    print(a)


if __name__ == "__main__":
    outer()

Defining a Closure Function

In the example above, what would happen if the last line of the function print_msg() function instead of calling it? This means the function was defined as follows:

def print_msg(msg):
	# enclosing function
	def printer():
		# nested funtion
		print(msg)
	return printer

another = print_msg("Hello")
another()

Output:

Hello

The print_msg() function was called with string "Hello" and returned function was bound to the name anthor. On calling anthor(), the message was still remembered although we had already finished executing the print_msg() function.

The technique by which some data ("Hello" in this case) gets attached to the code is called clousure in Python.

This value in the enclosing scope is remembered even when the variable goes out of scope or the function itself is removed from the current namespace.

Try running the following in Python shell to see the output.

>>> from decorator import print_msg
# 此后调用不了 `print_msg()` 函数了
>>> del print_msg 
>>> another()
Hello
>>> print_msg("Hello")
Traceback (most recent call last):
...
NameError: name 'print_msg' is not defined

Here, the returned function still works even when the original function was deleted.

When do we have closures?

The criteria that must be met to create closure in Python are summarized in the following points.

  • We must have a nested function( function inside a function ).
  • The nested function must refer to a value defined in the enclosing function.
  • The enclosing function must return the nested function.

When to use closures?

So what are clousures good for?

Closures can avoid the use of global values and provides some form from data hiding. It can also provide object oriented solution to the problem.

When there are some few methods (one method in most cases) to be implemented in a class, clousures can provide an alternate and more elegant soluton.

Here is a simple example where a closure might be more preferable than defining a class and making objects. But preference is all yours.

def make_multiplier_of(n):
    def multiplier(x):
        return x * n
    return multiplier

# Multiplier of 3, return a function
times3 = make_multiplier_of(3)

# Multiplier of 5
times5 = make_multiplier_of(5)

print(times3(4))
print(times5(4))

Output:

12
20

Python Decorators make an extensive use of clousures as well.

On a concluding note, it is good to point out that the values that get enclosed in the closure function can be found out.

All function objects have a __closure__ attribute that returns a tuple of cell objects if it is a closure function. Referring to the example above, we know times3 and times5 are closure functions.

>>> make_multiplier_of.__closure__
>>> times3.__closure__
(<cell at 0x108a6fc70: int object at 0x108927970>,)

The cell object has the attribute cell_contents which stores the closed value.

>>> times3.__closure__[0].closed_contents
3
>>> times5.__closure__[0].closed_contents
5

Getting back to Decorators

Functions and methods are called callable as they can be called.

In fact, any object which implements the special __call__() method is termed callable. So, in the most basic sense, a decorator is a callable that returns a callable.

Basically, a decorator takes in a function, adds some functionality and returns it.

def make_pretty(func):
	def inner():
		print("I got decorated")
		func()
	return inner

def ordinary():
	print("I am ordinary")

When you run the following codes in shell,

>>> ordinary()
I am ordinary

>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary

In the example shown above, make_pretty() is a decorator. In the assignment step:

pretty = make_pretty(ordinary)

The function ordinary() got decorated and the returned function was given the name pretty().

We can see that the decorator of function added some new function. This is similar to packing a gift. The decorator acts as a wrapper. The nature of the object that got decorated (actual gift inside) does not alter. But now, it looks pretty(since it got decorated).

Generally, we decorate a function and reassign it as,

ordinary = make_pretty(ordinary)

This is a common construct and for this reason, Python has a syntax to simplify this.

We ca use the @ symbol along with the name of the decorator function and place it above the definition of the function to be decorated. For example,

@make_pretty
def ordinary():
	print("I am ordinary")

is equivalent to

def ordinary():
	print("I am ordinary")
ordinary = make_pretty(ordinary)

This is just a syntax sugar to implement decorators.

Decorating Function with Parameters

The above decorators was simple and it only worked with functions that did not have any parameters. What if we had functions that took in parameters like:

def smart_divide(func):
	# Decorator
	def _divide(a, b):
		# Nested function
		print("Decorating the function...")
		if (b==0)
			print("Whoops! can't divide")
			return
		else:
			# 返回函数func返回的值
			return func(a, b)
	# 返回函数_divide
	return _divide


@smart_divide
def divide(a, b):
	return a / b

print(divide(1, 2))
print(divide(1, 0))

Output:

Decorating the function...
0.5

Decorating the function...
Whoops! can't divide
None

In this manner, we can decorate functions that take parameters.

A keen observer will notice that parameters if the nested function _divide__() inside the decorator is the same as the parameters of functions it decorators. Taking this into account, now we can make general decorator that work with any number of parameters.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and **kwargs will be the dictionary of keywords arguments. An example of such a decorator will be:

def works_for_all(func):
	def _func(*args, **kwargs):
		print("Decorating the function...")
		return func(*args, **kwargs)
	return _func

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

def start(func):
	def inner(*args, **kwargs):
		print("*" * 30)
		func(*args, **kwargs)
		print("*" * 30)
	return inner

def percent(func):
	def inner(*args, **kwargs):
		print("%" * 30)
		func(*args, **kargs)
		print("%" * 30)
	return inner

@star
@percent
def printer(msg):
	print(msg)

printer("I am hyliu")

Output:

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
I am hyliu
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

The above syntax of,

@star
@percent
def printer(msg):
	print(msg)

is equivalent to

def printer(msg):
	print(msg)
printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
I am hyliu
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值