python闭包与装饰器

  今天写一篇儿纸来解释一下python下的闭包的概念,再说一下python里非常有用的一个特性:装饰器。我自己之前在学习这部分内容的时候很蒙,看了很多blog的解释才理解。希望能解释清楚一些,和大家一起讨论。

一、参数作用域与闭包

  任何一门语言都有参数作用域的概念,python也不例外。总体来说,在哪个作用域里定义的变量一般就应该在哪个作用域里使用,尽量不要跨作用域使用。当然python也提供了跨作用域使用变量的功能,但是处理起来要小心一些,否则就会出错误。
  一般的,在某个作用域内定义的变量,在该作用域被销毁之前,是可以被当前作用域以及当前作用域的子作用域使用的,比如:

def scope_1():
	a = 1
	def scope_2():
		print (a)
		return
	scope_2()
	return
scope_1()
# 1

上述代码段,在scope_1的作用域内定义的局部变量a=1,可以在scope_2内被使用。当我们执行scope_1时,在scope_1的return语句执行(scope_1作用域被销毁)之前,调用了scope_2,此时变量a还未被销毁,因此可以被scope_2使用。
  python中如果在一个函数(如scope_1)的内部定义了另外一个函数(如scope_2),我们称第一个函数(scope_1)为外函数,第二个函数(scope_2)为内函数。下面是闭包的概念:在一个外函数中定义了一个内函数,内函数里使用了外函数作用域的局部变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。 比如:

# 外函数scope_1
def scope_1():
	# 外函数的局部变量a=1
	a = 1
	# 内函数scope_2
	def scope_2():
		# 内函数中使用了外函数的局部变量a
		print (a)
		return
	# 外函数返回值为内函数的引用
	return scope_2
func = scope_1()
func()
# 1

这段代码和前面那一段看起来很像,都是嵌套定义了两个函数,都是内层函数使用了外层函数的局部变量。但是有2个明显的区别:
1、外函数的返回值是内函数的引用;
2、调用时先调用外函数,返回值为一个函数对象赋值给func,再调用func。
那么在执行的时候,func=scope_1()这一行执行结束之后,外函数scope_1已经退出,按理说下面再调用func时,a这个变量已经被销毁不能被print出来,但是在实际执行时却可以被正确调用。一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

二、装饰器

2.1 装饰器概念

  在介绍装饰器晦涩难懂的定义之前,我们先通过一个例子来理解装饰器的逻辑。比如小明买了一个毛坯房,小明把它装修一下刮了大白贴了瓷砖。后来小明交了个女朋友,女朋友进家之后觉得家里的装修太素了,于是想在卧室里增加一些可爱的元素点缀一下。那么小明没有必要把整个卧室的装修全铲了重新再装一遍,他去买了一些墙纸、壁画和花草,再原始装修的基础上装饰了一下卧室,就完美的跟女朋友交差了。
  python的装饰器也是同样的逻辑:我们本来已经有了完成业务逻辑的代码,比如一个函数,现在我们想对这个逻辑新增一些功能,这些功能跟已有的逻辑没有很大的冲突,更多的是在原始功能上的一些补充,因此没有必要去修改原始的功能模块,直接在原始的基础上进行新增即可。考虑这样一种情况,比如我们已经有了一个函数 t a s k task task用来执行某项任务,现在我们需要新增以下功能,即在每次执行 t a s k task task之前去打印一个log,代表当前正在执行任务,比如在执行task之前print(‘Doing work now.’)。我们考虑如何实现上述修改。

def task():
	print (1)
	return
task()
# 1

2.1.1 直接修改task函数

  最直观的想法就是直接去修改task函数,把新增的打印log功能加入到代码段里,比如:

def task():
	print ('Doing work now.')
	print (1)
	return
task()
# Doing work now.
# 1

这样做至少有以下2个问题:
1、直接修改很麻烦。比如我们有100种不同的任务,我们分别为每种任务都写了一个函数来实现逻辑, t a s k 1 , t a s k 2 . . . t a s k 100 task_1,task_2...task_{100} task1,task2...task100。那么我们需要去修改每一个函数,非常啰嗦。
2、不利于扩展。新增的业务逻辑可能是多种多样的,比如有时候我们需要去打印log,有的时候我们需要去开启计时,有的时候我们需要去报警。这样修改起来还是很啰嗦。

2.1.2 把新增的功能和task分开

  既然新增的功能仅仅是对原始功能的一个装饰,即新增的功能不会破坏task函数的内容,那么可以考虑把新增的功能单独包装成一个函数,比如:

def task():
	print (1)
	return
def log():
	print ('Doing work now.')
	return
log()
task()
# Doing work now.
# 1

但是这样做也有问题,原来只需要执行task(),现在需要再task()之前加上一行log()来显示的调用log函数先输出,看起来似乎破坏了结构,也显得不fancy。

2.1.3 把task通过wrapper打包起来

  还可以通过一个wrapper把task打包起来,类似上面讲的闭包的例子,比如:

def wrapper():
	print ('Doing work now.')
	def task():
		print (1)
		return
	return task
task = wrapper()
task()
# Doing work now.
# 1

这样做的问题类似于2.1.2小节,需要手动去添加task = wrapper()这行代码,而且看起来好像重新定义了一个新的task,破坏了原始结构不fancy!如何不手动修改每个task的代码,又不修改执行时的仅仅需要一行task()的代码逻辑呢?

2.2 最简单的装饰器

  为了解决上述问题,我们可以把代码如下修改,执行的时候不需要修改,仍然是一行task():

# 下面是一个典型的闭包
# 外函数log
def log(func):
	# 外函数作用域的变量func
	print ('Doing work now.')
	# 内函数wrapper
	def wrapper():
		# 内函数里使用了外函数作用域的变量func
		func()
		return
	# 外函数的返回值为内函数的引用 
	return wrapper
@log
def task():
	print (1)
	return
task()
# Doing work now.
# 1

通过@关键字可以将一个装饰器函数装饰到业务函数上,上述修改后的代码在执行时候的逻辑,相当于:
1、被装饰函数=装饰器符号@后面的所有符号(被装饰函数);
2、调用被装饰函数。即:

task = log(task)
# 执行log(func=task),外函数中新增的装饰功能首先运行,然后返回wrapper对象,此时task=wrapper
task()
# 调用task相当于调用wrapper,而wrapper函数体内执行了func函数的功能,func函数正是外函数传入的参数task(闭包发挥作用),完成了原始业务逻辑的执行

2.3 被装饰函数需要带参数执行

  有些业务函数(被装饰函数)执行时需要带参数,可以通过wrapper函数传入业务函数执行时需要的实参,例如:

def log(func):
	print ('Doing work now.')
	def wrapper(*args, **kwds):
		func(*args, **kwds)
		return
	return wrapper
@log
def task(*args, **kwds):
	for arg in args:
		print (arg)
	for key in kwds.keys():
		print ('%s: %s.' % (key, kwds[key]))
	return
task(1,name='Bob', sex='male')
# Doing work now.
# 1
# name: Bob.
# sex: male.

执行task()的过程相当于:

task = log(task)
task(1, name='Bob', ses='male')

2.4 带参数的装饰器

  这部分需要和2.3节的内容区分开,这部分说的是有些装饰器需要带参数,可以引入更大的灵活性,帮助用户设计更多有用的功能,例如:

# 外函数log
def log(level):
	# 第一内函数decorator
	def decorator(func):
		# 第一内函数中使用了外函数log中的变量level,形成闭包
		if level == 'warning':
			print ('Warning.')
		else:
			print ('Doing work now.')
		# 第二内函数wrapper
		def wrapper():
			# 第二内函数中使用了第一内函数decorator中的变量func,形成闭包
			func()
			return
		# 返回第二内函数的引用
		return wrapper
	# 返回第一函数的引用
	return decorator
@log('warning')
def task():
	print (1)
	return
task()
# Warning.
# 1

执行task()的过程相当于:

task = log('warning')(task)
task()

2.5 多个装饰器

  函数功能复杂时,可能需要多个装饰器装饰同一个函数,其装饰顺序为从里到外,例如:

def log(func):
	print ('Doing work now.')
	def wrapper():
		func()
		return
	return wrapper
def warning(func):
	print ('Warning now.')
	def wrapper():
		func()
		return
	return wrapper
@warning
@log
def task():
	print (1)
	return
task()
# Doing work now.
# Warning now.
# 1

执行task()的过程相当于:

task = log(task)
task = warning(task)
task()

2.6 类装饰器

  类也可以作为装饰器,相比函数装饰器,类装饰器灵活度更高并且具有继承、封装、多态等面向对象的优点,使用类装饰器主要是调用类的自带方法__call__,例如:

class decorator(object):
	def __init__(self, func):
		self.func = func
	def __call__(self):
		print ('This is a decorator.')
		self.func()
@decorator
def task():
	print (1)
	return
task()
# This is a decorator.
# 1

执行task()的过程相当于:

task = decorator(task)
# 执行decorator(task),相当于以func=task实参实例化了一个decorator对象并赋值给task
task()
# 执行task相当于执行task.__call__()

  水平有限,写的不对请指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值