python-闭包和装饰器

闭包

 

inner作为一个函数被outer返回,保存在变量res中,并且还能够调用res()。为什么能调用呢?

通过上面对变量的作用域和生存周期我们不难明白,name是函数outer里的一个局部变量,也就是说只有当outer正在运行时,该变量才会存在。根据python的运行模式,我们是没法在函数outer执行退出之后还能继续调用inner函数的,并且在inner函数被调用时,变量name早已不存在了,但是为什么我们调用成功了呢?

这就回到了我们的闭包这个问题上了,python支持一个叫函数闭包的特性。

 

def outer() :

  name = "python"

  def inner() :

    print name

  return inner

 

res = outer()

res()

print res.func_closure#打印闭包里包含哪些外部变量,

c:\Python27\Scripts>python task_test.py

python

(<cell at 0x0282EB50: str object at 0x0278E860>,)

 

  如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包。 闭包是Python所支持的一种特性,它让在非global scope定义的函数可以引用其外围空间中的变量,这些外围空间中被引用的变量叫做这个函数的环境变量。环境变量和这个非全局函数一起构成了闭包。

上例中的inner()函数就是一个闭包,它本身也是一个函数,而且还可以访问本身之外的变量。 这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如name,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)

每次函数outer被调用时,inner函数都会被重新定义,上面每次返回的函数inner结果都一样,因为name没变。如下例所示,我们将函数稍微改动一下,结果就不一样了

#coding=utf-8

 

def outer(name) :

  def inner() :

    print name

  return inner

 

res1 = outer("python")#返回闭包

res2 = outer("java")#返回闭包

res1()#执行函数

res2()

c:\Python27\Scripts>python task_test.py

python

java

 

print res.func_closure#打印闭包里包含哪些外部变量,

 

 

返回一个函数+函数需要的变量

  name = "python"

  def inner() :

    print name

是整体返回的内容

 

加了个外部的整形变量

 

def outer():

  name = "python"

  nbr=12

  def inner():

    print name

    print nbr

  return inner#不加括号就是返回函数对象,不是函数调用

 

res = outer()#把inner函数对象赋值给res,因为没有括号,所以不是调用而是返回个函数对#象

res()#调用inner函数

print res.func_closure

 

c:\Python27\Scripts>python task_test.py

python

12

(<cell at 0x00F8EB50: str object at 0x00EEE860>, <cell at 0x00F944B0: int object at 0x00EF72FC>)

 

闭包定义

一个函数返回的函数对象,这个函数对象执行的话依赖非
函数内部的变量值,这个时候,函数返回的实际内容如下:
函数对象
函数对象需要使用的外部变量和变量值

以上称为闭包

 

闭包必须嵌套在一个函数里,必须返回一个调用外部变量的函数对象,才是闭包

使用闭包才能用装饰器

相对于inner来说  outer函数 就是它得全局变量    就好像你存粹写个函数 会用到函数外面环境定义得全局变量一样  都是相对来说

 

心里上先别抵触这个概念闭包就是别人给起的一个名字

一步一步去看里面执行的一个过程先别一下想全部的结论

里面函数执行   需要用到外面函数的一个变量  所以 就把外面变量和  里面这个函数 合到一块

 

 

 

装饰器

 

装饰器其实就是一个闭包,把一个函数当做参数后返回一个替代版函数。

#coding=utf-8

 

import time

 

def now():

    print "current time is %s" %time.strftime('%Y-%m-%d %H-%M-%S',time.localtime())

 

res=now

res()

 

c:\Python27\Scripts>python task_test.py

current time is 2018-04-11 15-59-35

 

 

现在如果我们想给now()函数增加一些别的功能,比如在调用该函数前后自动打印一些日志,但又不希望修改原now()的定义,这时候我们的装饰器就配上用场了。

本质上,decorator就是一个返回函数的高阶函数。所以我们需要定义一个能打印日志的

 

#coding=utf-8

 

import time

 

#定义装饰器

def log(func):

    def wrapper(*args,**kw):

        print 'call func is %s' %func.__name__

        return func(*args,**kw)

    return wrapper

 

@log

def now():

    now=time.strftime('%Y-%m-%d %H-%M-%S',time.localtime())

    print "current time is %s" %now

 

观察log函数,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。调用now()函数时,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志。

把@log放到now()函数的定义处,相当于执行了如下语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原本的now()函数仍然存在,只是现在同名的now()变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

正因为有了(*args, **kw)这样的参数格式,这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法,同时能够用传递给它的参数对被装饰的方法进行调用。这样就能处理参数个数不同的函数了。

#coding=utf-8

 

import time

 

#定义装饰器

def log(func):

    def wrapper(*args,**kw):

        print 'call func is %s' %func.__name__

        start_time=time.time()

        func(*args,**kw)

        print "elapse time: ",time.time()-start_time

        return 1

    return wrapper

 

@log

def now():

    now=time.strftime('%Y-%m-%d %H-%M-%S',time.localtime())

    print "current time is %s" %now

    time.sleep(3)

   

 

now()

 

c:\Python27\Scripts>python task_test.py

call func is now

current time is 2018-04-11 16-09-53

elapse time:  3.00300002098

 

 

 

装饰器分类

 

装饰器分为无参数decorator和有参数decorator

无参数decorator:生成一个新的装饰器函数

有参数decorator:装饰函数先处理参数,再生成一个新的装饰器函数,然后对函数进行装饰。

装饰器的具体定义:

1、把要装饰的方法作为输入参数;

2、在函数体内可以进行任意的操作(可以想象其中会有很多应用场景);

3、只要确保最后返回一个可执行的函数即可(可以是原来的输入参数函数,也可以是一个新函数)。

 

 

 

装饰器学习七步法

第一步-最简单的函数,准备附加额外功能

第一步:最简单的函数,准备附加额外功能

代码示例:

# -*- coding:utf-8 -*-

'''示例1: 最简单的函数,表示调用了两次'''

#coding=utf-8

 

def myfunc():

    print "myfunc() called."

 

myfunc()

myfunc()

c:\Python27\Scripts>python task_test.py

myfunc() called.

myfunc() called.

第二步-使用装饰函数在函数执行前和执行后分别附加额外功能

第二步:使用装饰函数在函数执行前和执行后分别附加额外功能

代码示例:

# -*- coding:utf-8 -*-

'''示例2: 替换函数(装饰)

装饰函数的参数是被装饰的函数对象,返回原函数对象

装饰的实质语句: myfunc = deco(myfunc)'''

 

def deco(func):
    print "before myfunc() called."
    func()
    print "  after myfunc() called."
    return func

def myfunc():
    print " myfunc() called."

myfunc = deco(myfunc)
myfunc()
myfunc()

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc() called.

  after myfunc() called.

 myfunc() called.

 myfunc() called.

 

 

 

 

 

 

 

 

 

 

第三步-使用@来装饰函数

 

# -*- coding:utf-8 -*-

'''示例3: 使用语法@来装饰函数,相当于“myfunc = deco(myfunc)”

但发现新函数只在第一次被调用,且原函数多调用了一次。等价于第二步程序'''

 

def deco(func):
    print "before myfunc() called."
    func()
    print "  after myfunc() called."
    return func

def myfunc():
    print " myfunc() called."

myfunc = deco(myfunc)
myfunc()
myfunc()

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc() called.

  after myfunc() called.

 myfunc() called.

 myfunc() called.

 

#注释掉最后两行代码可以看到装饰器函数也被执行

 

def deco(func):

    print "before myfunc() called."

    func()

    print "  after myfunc() called."

    return func

 

def myfunc():

    print " myfunc() called."

 

myfunc = deco(myfunc)

#myfunc()

#myfunc()

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc() called.

  after myfunc() called.

 

第四步-使用内嵌包装函数来确保每次新函数都被调用

 

# -*- coding:gbk -*-

'''示例4: 使用内嵌包装函数来确保每次新函数都被调用,

内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''

def deco(func):
    def _deco():
        print "before myfunc() called."
        func()
        print "  after myfunc() called."
        # 不需要返回func,实际上应返回原函数的返回值
    return _deco#返回的是闭包,因为需要外部传函数变量func

@deco
def myfunc():
    print " myfunc() called."
    return 'ok'

myfunc()
myfunc()

 

 

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc() called.

  after myfunc() called.

before myfunc() called.

 myfunc() called.

  after myfunc() called.

 

装饰器的作用是增加函数的功能,减少重复代码,比如在装饰器函数里加上函数执行时间,

那么在使用装饰器的函数都会打印出执行的时间

 

 

 

 

#encoding=utf-8
import time
def deco(func):
    def _deco():
        time1=time.time()
        func()  #函数执行部分
        time2=time.time()
        print "function invoked time elapse:",time2-time1
        # 不需要返回func,实际上应返回原函数的返回值
    return _deco

@deco
def myfunc():
    print " myfunc() called."
    time.sleep(0.1)
    return 'ok'

@deco
def yourfunc():
    print " yourfunc() called."
    time.sleep(0.1)
    return 'ok'

yourfunc()  #等价于--->deco(yourfunc)()
print "*"*50
myfunc()   #等价于--->deco(myfunc)()

 

c:\Python27\Scripts>python task_test.py

 yourfunc() called.

function invoked time elapse: 0.101000070572

**************************************************

 myfunc() called.

function invoked time elapse: 0.106000185013

 

 

在装饰器里加列表的操作

 

#encoding=utf-8
import time
def deco(func):
    def _deco():
        list1=[]
        func(list1)  #函数执行部分
        # 不需要返回func,实际上应返回原函数的返回值
        print list1
    return _deco

@deco
def myfunc(list1):
    list1.append(1)
    return 'ok'

@deco
def yourfunc(list1):
    list1.append(2)
    return 'ok'

yourfunc()  #等价于--->deco(yourfunc)()
print "*"*50
myfunc()   #等价于--->deco(myfunc)()

 

c:\Python27\Scripts>python task_test.py

[2]

**************************************************

[1]

装饰器和装饰器之间是独立的,每次使用装饰器时,原有变量都会初始化

 

#encoding=utf-8
import time
def deco(func):
    def _deco():
        list1=[]
        func(list1)  #函数执行部分
        # 不需要返回func,实际上应返回原函数的返回值
        print list1
    return _deco

@deco
def myfunc(list1):
    list1.append(1)
    return 'ok'

@deco
def yourfunc(list1):
    list1.append(2)
    return 'ok'

yourfunc()  #等价于--->deco(yourfunc)()
print "*"*50
myfunc()   #等价于--->deco(myfunc)()

c:\Python27\Scripts>python task_test.py

[2]

**************************************************

[1]

 

 

 

 

 

第五步-带参数

 

第五步:对带参数的函数进行装饰

代码示例:

# -*- coding:utf-8 -*-

'''示例5: 对带参数的函数进行装饰,

内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''

#encoding=utf-8

def deco(func):

    def _deco(a, b):

        print "before myfunc() called."

        ret = func(a, b)

        print "  after myfunc() called. result: %s" % ret

        return ret

    return _deco

 

@deco

def myfunc(a, b):

    print " myfunc(%s,%s) called." % (a, b)

    return a + b

 

myfunc(1, 2)

myfunc(3, 4)

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc(1,2) called.

  after myfunc() called. result: 3

before myfunc() called.

 myfunc(3,4) called.

  after myfunc() called. result: 7

 

 

打印出结果:

#encoding=utf-8

def deco(func):

    def _deco(a, b):

        print "before myfunc() called."

        ret = func(a, b)

        print "  after myfunc() called. result: %s" % ret

        return ret

    return _deco

 

@deco

def myfunc(a, b):

    print " myfunc(%s,%s) called." % (a, b)

    return a + b

 

print myfunc(1, 2)  #--->deco(myfunc)(1,2)

print myfunc(3, 4)  #--->deco(myfunc)(3,4)

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc(1,2) called.

  after myfunc() called. result: 3

3

before myfunc() called.

 myfunc(3,4) called.

  after myfunc() called. result: 7

7

 

 

 

第六步-带多个参数

 

第六步:对参数数量不确定的函数进行装饰

代码示例:

# -*- coding:utf-8 -*-

'''示例6: 对参数数量不确定的函数进行装饰,

参数用(*args, **kwargs),自动适应变参和命名参数''':

 

#encoding=utf-8

def deco(func):

    def _deco(*arg, **kw):

        print "before myfunc() called."

        ret = func(*arg, **kw)

        print "  after myfunc() called. result: %s" % ret

        return ret

    return _deco

 

@deco

def myfunc(a, b,c,d):

    print " myfunc(%s,%s) called." % (a, b)

    return a + b

 

print myfunc(1, 2,3,4)  #--->deco(myfunc)(1,2)

print myfunc(3, 4,1,2)  #--->deco(myfunc)(3,4)

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc(1,2) called.

  after myfunc() called. result: 3

3

before myfunc() called.

 myfunc(3,4) called.

  after myfunc() called. result: 7

7

 

 

改了参数

#encoding=utf-8

def deco(func):

    def _deco(*arg, **kw):

        print "before myfunc() called."

        ret = func(*arg, **kw)

        print "  after myfunc() called. result: %s" % ret

        return ret

    return _deco

 

@deco

def myfunc(a, b,c,d):

    print " myfunc(%s,%s) called." % (a, b)

    return a + b+c+d

 

print myfunc(1, 2,1,1)  #--->deco(myfunc)(1,2,1,1)

print myfunc(3, 4,1,2)  #--->deco(myfunc)(3,4,1,2)

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc(1,2) called.

  after myfunc() called. result: 5

5

before myfunc() called.

 myfunc(3,4) called.

  after myfunc() called. result: 10

10

 

 

参数中有列表和字典:

#encoding=utf-8
def deco(func):
    def _deco(*arg, **kw):
        print "before myfunc() called."
        ret = func(*arg, **kw)
        print "  after myfunc() called. result: %s" % ret
        return ret
    return _deco

@deco
def myfunc(a, *arg,**kw):
    #print " myfunc(%s,%s) called." % (a, b)
    result = a
    for i in arg:
        result +=i
    for k in kw:
        result+=kw[k]
    return result

print myfunc(1, 2,3,x=4)  #--->deco(myfunc)(1,2,3,x=4)
print myfunc(3, 4,1,x=2)  #--->deco(myfunc)(3,4,1,x=2)

 

c:\Python27\Scripts>python task_test.py

before myfunc() called.

 myfunc(1,2) called.

  after myfunc() called. result: 5

5

before myfunc() called.

 myfunc(3,4) called.

  after myfunc() called. result: 10

10

 

 

 

 

第七步,装饰器带参数

# -*- coding:utf-8 -*-

'''示例7: 在示例4的基础上,让装饰器带参数,

和上一示例相比在外层多了一层包装。

装饰函数名实际上应更有意义些'''

#encoding=utf-8
def deco(arg):
    def _deco(func):
        def __deco():
            print "before %s called [%s]." % (func.__name__, arg)
            func()
            print "  after %s called [%s]." % (func.__name__, arg)
        return __deco
    return _deco

@deco("mymodule")
def myfunc():
    print " myfunc() called."

@deco("module2")
def myfunc2():
    print " myfunc2() called."


#deco("mymodule")()()+arg—第一层闭包>_deco()()+arg+func---》这是另一层闭包
#__deco()++arg+func另一层闭包
myfunc()   
myfunc2()  #

 

 

c:\Python27\Scripts>python task_test.py

before myfunc called [mymodule].

 myfunc() called.

  after myfunc called [mymodule].

before myfunc2 called [module2].

 myfunc2() called.

  after myfunc2 called [module2].

 

 

 

内置装饰器

 

Python中内置的装饰器有有三个:

    staticmethod:定义实例方法为静态方法

    classmethod:定义实例方法为类方法

property:对类属性的操作

 

这些在上面我们都讲过了,这里就不在赘述了。

 

 

装饰器顺序

同时对一个函数使用多个不同的装饰器进行装饰时,这个时候装饰器的顺序就很重要了。

代码示例:

@A

@B

@C

def f():

pass

等价于:

f = A(B(C(f)))

 

posted @ 2018-04-12 11:18  夏晓旭  阅读(303)  评论(0编辑  收藏  举报