美文网首页
python通过闭包来理解装饰器

python通过闭包来理解装饰器

作者: BourneKing | 来源:发表于2018-11-30 18:16 被阅读1次

前言

装饰器作为Python语言很重要的一个特性,在实际开发中,我们都经常用到,包括面试的时候也会拿出来问,所以想总结一下。
在讲装饰器之前,我想讲一下闭包这个概念。在任何开发的语言中,我们都会接触到闭包这个概念,也比较难理解。

基本了解

闭包是什么?
闭包指嵌套在非全局作用域里面的函数能够记住它在定义时候,它所处的封闭命名空间.
听起来是不是很难理解,不用担心,下面可以看看一个例子:

def outer():
     x = 1
     def inner():
         print x 
     return inner

foo = outer()#2
foo() #1

我定义一个outer函数,里面包含一个本地变量x,和一个定义的函数inner,最后outer函数返回值为inner函数本身。#1处,当我们执行foo()时,结果为1。这是为什么呢?
我们知道从变量的生存周期来看,变量x作为函数outer的一个本地变量,这意味着只有当函数outer正在运行也就是#2处的时候才会存在,inner函数中才能调用x并打印。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在#1处执行foo()时,函数inner被调用的时候,变量x早已不复存在,理论上会发生一个运行时错误。然而,这时候Python会支持一种叫做函数闭包的特性:

foo.__closure__ 
(<cell at 0x: int object at 0x>,)

也就是foo作为一个函数,包含了一个__closure__(python2是func_closure )的属性,用来记录封闭作用域里面的值(只会包含被捕捉到的值(或者叫被引用的值),比如变量x。当每次函数foo被调用时,函数inner都会被重新定义,变量x的值是不会变化。
接下来,对outer函数稍微改动一下:

def outer(x):
     def inner():
         print x # 1
     return inner
print1 = outer(1)
print2 = outer(2)
print1()
1
print2()
2

从这个改动后的outer函数中,我们可以看到闭包--被函数记住的封闭作用域,能够被用来创建自定义的函数,本质上来说是一个硬编码的参数,不管调用多少次,函数都会记住调用时对应赋予x的值。

闭包单独拿出来就是一个非常强大的功能, 在某些方面,你也许会把它当做一个类似于面向对象的技术:outer像是给inner服务的构造器,x像一个私有变量。
总结:实际上创建一个闭包必须满足以下几点:
1.必须有一个内嵌函数
2.内嵌函数必须要引进外部函数的变量
3.外部函数的返回值必须是内嵌函数

重点是函数运行后并不会被撤销,就像上面的例子中的outer函数,当函数运行完后,参数x并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.

装饰器

接着上面的outer函数来进行修改:

def outer(func):
    def inner():
        print("before func")
        ret = func()
        return ret + 1
    return inner
def foo():
    return 1

decorated = outer(foo)
decorated()

before func
2

仔细看看上面的代码,在outer函数里定义一个嵌套函数inner,inner函数会先打印一串字符串,然后在调用传进来的函数func并执行它得到它的返回值,然后inner返回func()+1,所以通过函数作为参数传到decorated,得到的结果为2并非为这个函数的返回值1。
装饰器其实就是一个闭包,把一个函数作为参数然后返回一个替代版本的函数
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能
因此,我们可以认为变量decorated是函数foo的一个装饰版本,一个加强版本。

现在,写一个含有X和Y坐标的函数,并实现坐标数字运算:

class Coordinate(object):
        def __init__(self, x, y):
              self.x = x
              self.y = y
        def __repr__(self):
              return "coordinate:" + str(self.__dict__)
def add(a, b):
     return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
     return Coordinate(a.x - b.x, a.y - b.y)


def wrapper(func):
     def checker(a, b): # 1
         if a.x < 0 or a.y < 0:
             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
         if b.x < 0 or b.y < 0:
             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
         ret = func(a, b)
         if ret.x < 0 or ret.y < 0:
             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
         return ret
     return checker

add = wrapper(add)
sub = wrapper(sub)
one = Coordinate(100, 200)
two = Coordinate(300, 200)
sub(one, two)
Coord: {'y': 0, 'x': 0}
add(one, three)
Coord: {'y': 200, 'x': 100}

在坐标进行数学运算时,需要检测传递的参数a,b的值为非负值,或者通过相加减后得出的值为非负值。
这个装饰器就是将边界检查的逻辑隔离到单独的方法中,然后通过装饰器包装的方式应用到我们需要进行检查的地方

使用 @ 标识符将装饰器应用到函数

Python支持使用标识符@应用到函数上。只需要在函数定义之前加上@和装饰器函数名,上面例子可以写成:

add = wrapper(add)
sub = wrapper(sub)

@wrapper
def add(a, b):
     return Coordinate(a.x + b.x, a.y + b.y)
@wrapper
def sub(a, b):
     return Coordinate(a.x - b.x, a.y - b.y)

需要明白的是,这样的做法和先前简单的用包装方法替代原有方法是一样的, python只是加了一些语法糖让装饰的行为更加的直接明确和优雅一点。

更通用的装饰器

在装饰器中使用*args, **kwargs来捕获任何用于被装饰的函数。也就是说,当定义函数用了任何参数,意味着那些通过位置传递的参数将会被放在带有前缀的变量中,先来个简单地把日志输出到界面的例子:

def logger(func):
     def inner(*args, **kwargs): #1
         print "Arguments were: %s, %s" % (args, kwargs)
         return func(*args, **kwargs) #2
     return inner

请注意我们的函数inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器来装饰任何方法。

@logger
def foo1(x, y=1):
     return x * y
@logger
def foo2():
     return 2


foo1(5, 4)
Arguments were: (5, 4), {}
20

foo1(1)
Arguments were: (1,), {}
1

foo2()
Arguments were: (), {}
2

总结

通过对闭包的理解,加深了装饰器的认识,其实装饰器就是一种特殊的闭包,把一个函数当做参数然后返回一个替代版函数。对于Python初学者来说,感觉还是有一定的难度的,所以要多加应用。

相关文章

  • 让Python的装饰器不再难理解

    注意: 如果对闭包不了解的同学请移步到这里先,因为装饰器要通过闭包来实现 前言 刚开始学Python的时候,装饰器...

  • python通过闭包来理解装饰器

    前言 装饰器作为Python语言很重要的一个特性,在实际开发中,我们都经常用到,包括面试的时候也会拿出来问,所以想...

  • python装饰器

    装饰器简述 要理解装饰器需要知道Python高阶函数和python闭包,Python高阶函数可以接受函数作为参数,...

  • python之理解闭包和装饰器

    python之理解闭包和装饰器 1、闭包函数 1.1 python中函数都是对象 结果: 上面定义一个shut函数...

  • Python装饰器与闭包!

    闭包是Python装饰器的基础。要理解闭包,先要了解Python中的变量作用域规则。 变量作用域规则 首先,在函数...

  • Python的自定义超时机制——装饰器的妙用

    装饰器 关于装饰器的入门,可以参考这篇文章:12步轻松搞定python装饰器简单来说,装饰器其实就是一个闭包(闭包...

  • Python 装饰器的诞生过程

    Python中的装饰器是通过利用了函数特性的闭包实现的,所以在讲装饰器之前,我们需要先了解函数特性,以及闭包是怎么...

  • 只需四步,让你了解Python装饰器的诞生过程

    Python中的装饰器是通过利用了函数特性的闭包实现的,所以在讲装饰器之前,我们需要先了解函数特性,以及闭包是怎么...

  • Python装饰器-专题笔记

    学会装饰器,Python更进阶 函数作用域到闭包到装饰器讲解,及闭包和装饰器的运用。 [√] 慕课网Meshare...

  • [python] 装饰器学习

    很多python的代码都带有装饰器=。=现在不学以后也要学学一下装饰器 闭包 在学装饰器之前先看看python的闭...

网友评论

      本文标题:python通过闭包来理解装饰器

      本文链接:https://www.haomeiwen.com/subject/qfnhcqtx.html