Python——装饰器 闭包的函数

闭包的定义

将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象称为闭包
我们知道函数在Python中是第一类对象,也就是说可以把它们当作参数传递给其他函数,放在数据结构中,以及作为函数的返回结果。
我们定义一个函数,它接受另一函数作为输入并调用它

foo.py
def call(func):
    return func

我们在另一个程序中去调用这个函数

cal_close.py
from python_book import foo
def helloworld():
    return 'hello world'
print(foo.call(helloworld))

我们把函数当作数据处理时,它将隐式地携带与定义该函数的周围环境相关的信息,这将影响到函数中自由变量的绑定方式。
我们在刚才的栗子中加入一个变量的定义

foo.py
x = 42
def call(func):
    return func

然后在调用函数的地方同样也加入自己的函数定义

cal_close.py
from python_book import foo

x = 37

def helloworld():
    return 'hello world is %d' % x
print(foo.call(helloworld))
>>> hello world is 37

在这个栗子中,函数helloworld()使用的x的值是在与他相同的环境中定义的,即使在foo.py中也定义了一个变量x,而且这里也是实际调用helloworld()函数的地方,但x的值与helloworld()函数执行时使用的x不同
事实上所有函数都拥有一个指向了定义该函数的全局命名空间的globals属性,始终对应于定义函数的闭包模块,闭包将捕捉内部函数执行所需的整个环境
我们可以看一个闭包函数的栗子

def countdown(n):
    def next():
        nonlocal n
        r = n
        n -= 1
        return r
    return next
# 使用
next = countdown(10)
while True:
    v = next()
    if not v: break
    print(v)

在Python中,装饰器是很重要的高级函数。要掌握装饰器我们必须掌握相关的知识

函数作用域

LEGB原则

函数即对象

在Python中,函数和我们之前的数据类型等都一样都是对象,而且函数是最高级的对象
在内存中和对象一样,也是将函数的指针存储在函数名中


函数在内存中的情况

既然函数和对象相同那么函数自然满足对象的几个条件
1.可以被赋值给其他变量

def foo():
    print('foo')
bar=foo
bar()
foo()
print(id(foo),id(bar))  #4321123592 4321123592

函数的函数名其实就是函数的指针,可以将函数的函数名赋值给其他的变量
2.其可以被定义在另外一个函数内(作为参数&作为返回值):

#*******函数名作为参数**********
def foo(func):
    print('foo')
    func()
 
def bar():
    print('bar')
 
foo(bar)
 
#*******函数名作为返回值*********
 
def foo():
    print('foo')
    return bar
 
def bar():
    print('bar')
 
b=foo()
b()

函数的嵌套及闭包

Python允许创建嵌套函数,通过在函数内部使用def关键字声明一个函数作为内部函数

看下面一个例子:

#想执行inner函数,两种方法
def outer():
     x = 1
     def inner():
         print (x) # 1
     # inner() # 2
     return inner
 
# outer()
in_func=outer()
in_func()

作为调用内部函数inner,有下面两种方法

in_func=outer() 
in_func()  
###########
inner()(已经加载到内存啦)

如果直接在外部调用内部函数inner就会报错,原因就是这里找不到这个引用变量

但是这里就会有一个问题,在内部函数被调用执行的时候,它的外部函数也就是outer()已经执行完毕了,那么为什么inner还是可以调用声明在外部outer函数中的变量x呢?

这里就涉及到我们说的闭包的概念,因为outer里return的inner是一个闭包函数,所以就会有这个x变量

我们也就可以抛出闭包的定义

如果在一个内部函数中,对在外部作用域(且不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包,内部函数可以称为闭包函数

在上面的例子中,inner就是内部函数,inner中引用了外部作用域的变量x(x在外部作用域outer,不在模块的全局作用域)。
所以这个inner函数就是闭包函数
如下函数,内部闭包函数的作用是给外部的函数增加字符串参数

>>> def saying(something):
...     def outsaying():
...             return "is inner read params %s" %something
...     return outsaying
... 
>>> a = saying('hello')
>>> a()
'is inner read params hello'

上面这个函数中 outsaying作为内部函数直接访问外部的变量所以是一个闭包函数,outsaying()函数可以得到参数something的值并且记录下来,return outsaying 这一行返回的是outsaying函数的一个复制(并没有直接调用)

装饰器概念

装饰器本质上来说是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也会一个函数对象。装饰器可以被用于:插入日志、性能测试、事物处理、缓存、权限校验等应用场景,我们有了装饰器我们就可以抽离大量与函数功能本身无关的雷同代码并继续重用。装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器定义

装饰器是一个函数,只要用途是包装另一个函数或类。语法上使用特殊符号@表示装饰器
使用装饰器时,它们必须出现在函数或者类定义之前的单独行上,可以同时使用多个装饰器
···
@foo
@bar
@spam
def grok(x)
pass
···
上述代码等同于

def grok(x):
      pass
grok = foo(bar(spam(grok)))

装饰器也可以接受参数

@eventhandler('a')
def handle_a(msg):
        pass
@eventhandler('b')
def handle_b(msg):
        pass

如果装饰器提供参数,装饰器的语义如下所示:
def handle_button(msg):
tmp = eventhandle('a')# 使用提供的参数调用装饰器
handle_button = tmp(handle_button) # 调用装饰器返回的函数

@trace
def square(x):
      return x*x
等价:
def square(x):
      return x*x
square = trace(square)

现在在生产中有这样的函数

def foo():
  print('hello foo')
foo()

现在有一个新的需求,希望可以记录下函数的执行时间,在这种需求下我们最好不要修改源代码来实现这个功能,我们可以引入一个内置函数,而且为了不影响结构,我们还不应该改变函数的调用方式
这里我们可以引入一个内置函数

import time
 
def show_time(func):
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        print('spend %s'%(end_time-start_time))
 
    return wrapper
 
 
def foo():
    print('hello foo')
    time.sleep(3)
 
foo=show_time(foo)
foo()

在上面这个例子中函数show_time就是装饰器。它将真正的业务方法func包裹在函数里面。

@符号是装饰器的语法糖,在定义函数的时候使用,避免多次的赋值操作

import time
 
def show_time(func):
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        print('spend %s'%(end_time-start_time))
 
    return wrapper
 
@show_time   #foo=show_time(foo)
def foo():
    print('hello foo')
    time.sleep(3)
 
 
@show_time  #bar=show_time(bar)
def bar():
    print('in the bar')
    time.sleep(2)
 
foo()
print('***********')
bar()

这里:foo=show_time(foo)其实就是把内置函数wrapper引用的对象引用给了foo,而wrapper里的变量func之所以可以使用,就是因为wrapper是一个闭包函数

带参数的被装饰函数

import time
 
def show_time(func):
 
    def wrapper(a,b):
        start_time=time.time()
        func(a,b)
        end_time=time.time()
        print('spend %s'%(end_time-start_time))
 
    return wrapper
 
@show_time   #add=show_time(add)
def add(a,b):
 
    time.sleep(1)
    print(a+b)
 
add(2,4)
  • 不定长参数
#***********************************不定长参数
import time

def show_time(func):

    def wrapper(*args,**kwargs):
        start_time=time.time()
        func(*args,**kwargs)
        end_time=time.time()
        print('spend %s'%(end_time-start_time))

    return wrapper

@show_time   #add=show_time(add)
def add(*args,**kwargs):

    time.sleep(1)
    sum=0
    for i in args:
        sum+=i
    print(sum)

add(2,4,8,9)

装饰器带参数

import time
 
def time_logger(flag=0):
 
    def show_time(func):
 
            def wrapper(*args,**kwargs):
                start_time=time.time()
                func(*args,**kwargs)
                end_time=time.time()
                print('spend %s'%(end_time-start_time))
 
                if flag:
                    print('将这个操作的时间记录到日志中')
 
            return wrapper
 
    return show_time
 
 
@time_logger(3)
def add(*args,**kwargs):
    time.sleep(1)
    sum=0
    for i in args:
        sum+=i
    print(sum)
 
add(2,7,5)

@time_logger(3) 做了两件事:

  • (1)time_logger(3):得到闭包函数show_time,里面保存环境变量flag
  • (2)@show_time :add=show_time(add)

多层装饰器

def makebold(fn):
    def wrapper():
        return "<b>" + fn() + "</b>"
    return wrapper
 
def makeitalic(fn):
    def wrapper():
        return "<i>" + fn() + "</i>"
    return wrapper
 
@makebold
@makeitalic
def hello():
    return "hello alvin"
 
hello()

匿名函数: lambda()函数

Python中,lambda函数是用一个语句表达的匿名函数,可以用它来代替小的函数。

# 定义一个函数,参数接收一个列表,并遍历
>>> def edit_story(words,func):
...     for word in words:
...             print(func(word))
... 
>>> stairs = ['a','ab','abc','abcd']
>>> def add_str(word):
...     return word+'!!!'
...  
>>> edit_story(stairs,add_str)
a!!!
ab!!!
abc!!!
abcd!!!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂...
    TypingQuietly阅读 19,508评论 26 186
  • 原文出处: dzone 译文出处:Wu Cheng(@nullRef) 1. 函数 在python中,函数通过...
    DraculaWong阅读 506评论 0 3
  • 无论项目中还是面试都离不开装饰器话题,装饰器的强大在于它能够在不修改原有业务逻辑的情况下对代码进行扩展,权限校验、...
    liuzhijun阅读 1,646评论 0 30
  • 前任对很多很多人来说都是从内心深处不愿提及的一类人,那个时候懵懂的爱过, 狠狠地恨过,没心没肺地笑过,也傻傻地哭过...
    假装我是流浪歌手阅读 295评论 0 1
  • 在社会上,像我这样平凡的人应该是占多的。 毕业12年多,侥幸混迹于一家国企;至今还是一个基层科员,干着不紧不慢的活...
    winyear阅读 284评论 0 1