深入浅出详解Python中的闭包与装饰器

1、什么是装饰器

Python中的装饰器decorator是一个非常有用的功能。
相信学过设计模式的同学都会或多或少的了解一点,简单来说装饰器decorator能够在无需修改原有函数的情况下动态地修正一个函数,类或者类的方法的功能。即当你希望在不修改函数本身的前提下扩展函数的功能时非常有用。
形象的说,装饰器就像一个外包装wrapper,在函数执行之前或者之后调用,用来修改原有函数的行为,但是不需修改函数本身的代码,这也是修饰器名称的来由。

2、Python函数

我们在开始学习Python的时候就知道这个说法:在Python中,函数本身也是对象,这是什么意思呢?就是说我们可以像操作类对象一样操作函数。下面我们通过一些演示说明一下。

2.1 将函数赋值给变量:

def func_test(name):    
       return "hello " + name
test = func_test # 将函数赋值给test变量,类似于重命名
print greet_someone("World")  # Outputs: hello World

2.2 函数内嵌套函数:

def func_out(name):    
           def func_in():        
                   return "Hello "    
           result = func_in() + name    
           return result
    print func_out("World")    # Outputs: Hello World

2.3 函数被当做参数传给其他函数:

def func_in(name):   
        return "Hello " + name 
def func_out(func):    
        other_name = "John"
        return func(other_name)  
print func_out(func_in)  # Outputs: Hello World

2.4 函数可以返回其他函数(函数产生函数):

def func_out():    
        def func_in():        
               return"Hello World!"
        return func_in
test = func_out()
print test()  # Outputs: Hello World!

2.5 内部函数可以访问外层函数变量

def func_out(name):    
        def func_in():        
               return"Hello " + name
        return func_in
test = func_out("World")
print test()  # Outputs: Hello World

这里的内嵌函数使用了外部函数的参数,上面这种函数,便被称为闭包。跟普通函数的不同是,内嵌函数func_in用了外部函数的变量name,使外部变量生命周期被延长了。而普通函数的变量,在进入其他函数时,变量便无法再用了
这种内部函数调用外部变量的行为,就叫做闭包

3、装饰器

3.1 装饰器实现

说了半天,闭包和装饰器有什么关系呢?
我们平常用的装饰器,就是闭包实现的
这里我们传入都是一个字符串作为参数,如果传入函数作为参数呢,这就是我们要说的装饰器了。
到底如何实现呢?话不多说,直接上代码。

def ori_func(text):   
      return"{0}".format(text)
      
def func_decorate(func):   
      def func_a(name):       
            return"<a>{0}</a>".format(func(name))   
      return func_a
my_func = func_decorate(ori_func)
print my_func("hello World ")   # <a>hello World </a>

这就是我们实现的修饰器。一个函数接收另一个函数作为参数,并且产生一个新的函数,在原有函数ori_func功能基础上添加新功能,并且返回一个新函数func_a 。然后我们将修饰器函数func_decorate直接赋值给my_func,这样就有了自己想要的函数
my_func = func_decorate(ori_func)
需要注意的是:被修饰的函数ori_func具有一个text参数,我们必须在赋值操作时传入那个参数。
那如果我不想改变函数名称呢,这个时候只要在赋值语句时,使用原有函数名就可以了,这样就覆盖了原来的函数!

def ori_func(text):   
      return"{0}".format(text)
      
def func_decorate(func):   
      def func_a(name):       
            return"<a>{0}</a>".format(func(name))   
      return func_a
ori_func = func_decorate(ori_func)  # 覆盖了原来的函数
print ori_func("hello World ")   # <a> hello World </a>

3.2 修饰符语法

在上面的例子中我们通过ori_func = func_decorate(ori_func)的方式覆盖了原有函数,从而形成了有新功能的同名函数,这个显得有点啰嗦,python提供了简洁清晰的对应语法。比如:

def func_decorate(func):   
      def func_a(name):       
            return"<a>{0}</a>".format(func(name))   
      return func_a

@func_decorate
def ori_func(text):   
      return"{0}".format(text)
      
print ori_func("hello World ")   # <a>hello World </a>

上述代码中,@符号后面的是修饰器本身,紧跟后面的则是将被修饰的函数(这种语法等价于使用@后面的修饰器先对ori_func修饰,并且返回产生的新函数替代被修饰的函数名。
相当于用@func_decorate 替换了ori_func = func_decorate(ori_func)

那就有同学又问了,添加多个修饰符可不可以呢?当然可以,不过修饰符的顺序不同,效果也不同

def func_decorate_a(func):   
      def func_a(name):       
            return"<a>{0}</a>".format(func(name))   
      return func_a
def func_decorate_div(func):   
      def func_a(name):       
            return"<div>{0}</div>".format(func(name))   
      return func_a
def func_decorate_td(func):   
      def func_a(name):       
            return"<td>{0}</td>".format(func(name))   
      return func_a

@func_decorate_div
@func_decorate_td
@func_decorate_a
def ori_func(text):   
      return"{0}".format(text)
      
print ori_func("hello World ")   
#output:  <div><td><a> hello World </a></td></div>

从结果可以看出,装饰器是逐级向下装饰的效果,也就是从下往上执行的顺序。

3.3 类方法的装饰器实现

def func_decorate(func):   
      def func_a(name):       
            return"<a>{0}</a>".format(func(name))   
      return func_a

class Test(object):
	   def __init__(self):        
	         self.name = "test_name"        
	   @func_decorate    
	    def get_name(self):        
	           return self.name
obj = Test()
print my_person.get_name()

一定需要注意的是,由于类方法中的第一个变量是self ,虽然最后调用时my_person.get_name()没带参数,但是装饰器函数中的func依然要带参数,此处是 return"{0}".format(func(name)) 语句中的name

这时候我们就想了,有些时候我们希望一个装饰器 可以修饰不通的函数,但是被修饰函数参数又不尽相同,这怎么办呢?
经常用Python的同学肯定脑海中第一时间想到了,当然是args和*kwargs 了

def func_decorate(func):   
      def func_a(*args, **kwargs):       
            return"<a>{0}</a>".format(func(*args, **kwargs))   
      return func_a

class Test(object):
	   def __init__(self):        
	         self.name = "test_name"        
	   @func_decorate    
	    def get_name(self):        
	           return self.name
obj = Test()
print my_person.get_name()

这样可以接受任意个数的参数或者keyword型参数。

3.4 装饰器传入参数

上面的各例子中装饰器都只包含唯一的参数,就是传入的被修饰函数
如果想要传入其他参数呢,例如上述2.2中多个装饰器,明显代码重复冗余,如何通过传入参数优化实现呢,请看下面代码:

def add_tag(tag):
	  def func_decorate(func):   
            def func_a(*args, **kwargs):       
                 return"<{1}>{0}</{1}>".format(func(*args, **kwargs),tag)
             return func_a   
       return func_decorate

@add_tag("td")
@add_tag("div")
def ori_func(text):   
      return"{0}".format(text)
      
print ori_func("hello World ")   # <div>hello World </div>

这样是不是就灵活多了。

3.5 装饰器代码调试

看代码时经常会看到@wraps修饰符 出现在装饰器中,这到底是干什么用的呢?下面我们就来说明一下。
通过以上学习我们知道,装饰器返回的函数func_a重载了原有函数get_name,这带来一个问题就是如果要调试代码可能有问题,因为装饰器函数并不会携带原函数的函数名、模块名和docstring等信息,
例如上面的例子,如果我们打印print(ori_func.name)则返回func_a
而不是ori_func,原因就是__name__,doc,__module__这些属性都被func_a函数所重载。虽然我们可以手工重置(在func_a里面),但是python提供了更好的办法:
functools
functools模块包含了wraps函数。wraps也是一个decorator,但是仅仅用于更新装饰器(func_a)的属性为原始函数的属性(ori_func),看下面的代码:

from functools import wraps
def add_tag(tag):
	  def func_decorate(func):   
	        @wraps(func)
            def func_a(*args, **kwargs):       
                 return"<{1}>{0}</{1}>".format(func(*args, **kwargs),tag)
             return func_a   
       return func_decorate

@add_tag("td")
@add_tag("div")
def ori_func(text):   
      return"{0}".format(text)
      
print ori_func("hello World ")   # <div>hello World </div>
print(ori_func.__name__)

这样再运行 print(ori_func.name) 则输出 ori_func 了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值