python闭包函数与装饰器函数

一、闭包函数

什么是闭包:python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。这样我们可以理解,在函数内创建一个函数的行为是完全合法的,而这种函数就叫内嵌函数。内嵌函数(可以理解为内部函数)可以在外部函数作用域内正常调用,在外部函数作用域外则会报错。如果内嵌函数(可以理解为内部函数)引用了外部函数定义的对象(可以是外层之外,但不是全局变量),那么此时的函数就叫闭包函数。

总之,一句话。如果外部函数的变量,被内部函数所引用,这种方式就是闭包。

比如:实现一个常规的累加函数,这是常规写法:

def func():
    num1 = 1
    num2 = 2
    return num1 + num2
print(func())
3

使用闭包函数的写法:

def func(num1):
    def add(num2):
        return num1 + num2
    return add
a = func(1)
print(a(2))
3

在这里,func()相当于外部函数,add()相当于内部函数,外部函数(func)的变量num1被内部函数(add)引用,这种方式就是闭包。另外,需要注意的是外部函数返回的是内部函数名。写法可以固定为:

def func1():
    def func2():
        ...
    return func2 #外部函数返回的是内部函数名

对比以上两种写法,其实看不出闭包有什么优势,反而变得更加繁琐。现在再举个例子,加深对闭包优势的了解。

比如,设计一个计数器:

def counter():
    cnt = [0]
    def add_one():
        cnt[0] += 1
        return cnt[0]
    return add_one

num1 = counter()
print(num1())
print(num1())
print(num1())
print(num1())
1
2
3
4

其实用常规方法设计的函数也可以实现,但换一种思路,在实际生活中,需求是不断变化的,现在需要一种从10开始的计数器,难道我们又要重新开发一套程序吗?没有更好的办法了吗?现在闭包的优势就来了。

def counter(FIRST=0):
    cnt = [FIRST]
    def add_one():
        cnt[0] += 1
        return cnt[0]
    return add_one

num1 = counter() #从1开始的计数器
num10 = counter(10)#从10开始的计数器
print(num1())
print(num1())
print(num1())
print(num10())
print(num10())
print(num10())
1
2
3
11
12
13

在这里,我们只需要简单的更改少量的代码counter(10),就可以实现新的需求。

在实际测试工作中,我们会经常测试程序的性能,其中一个主要的指标是看程序的运行时间,在此场景中,闭包函数就会经常被使用,它会大大提高工作的效率。比如:

import time

def cost_time(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'程序运行时间为:{end_time - start_time}')#f字符串格式化在pyhton3.7开始使用
    return wraper

@cost_time #装饰器
def my_func():
    time.sleep(2)

my_func()
程序运行时间为:2.0004215240478516

这里引用了装饰器,一般闭包函数和装饰器是配套使用的,这会大大提高python程序的效率。下面介绍装饰器的用法:

二、装饰器函数

什么是装饰器:python装饰器(function decorators)就是用来扩展函数功能的一种函数,其目的就是不改变原函数名(或类名)的情况下,给函数增加新的功能。装饰器就是通过闭包函数来给原来函数增加功能。因为调用函数不美观,所以引用了语法糖,也就是在需要添加功能的函数前面加上@即可。

需要注意,@修饰符的用法。

  • '@’符号用作函数修饰符,必须出现在函数定义的前一行。不允许和函数定义在同一行。什么意思?还是用上面的例子举例:
@cost_time #'@’符号用作函数修饰符,必须出现在函数定义的前一行,这就是第一行。
def my_func():#这是函数,应该在'@’函数修饰符的下一行。
    time.sleep(2)
  • '@’符号用作函数修饰符,必须模块或者类定义层内对函数进行修饰,不允许修饰一个类。
  • 一个修饰符就是一个函数,它将被修饰的函数作为参数,并返回修饰的同名函数或其他可调用的东西。
@cost_time #装饰器
def my_func():
    time.sleep(2)

这里的@,我们称之为语法糖,@cost_time就相当于cost_time(my_func),只不过更加的简洁。

  1. 带一个参数的装饰器

在之前计算程序运行时间的例子中,my_func()函数没有带参数,如果需要带参数呢?

import time

def cost_time(func):
    def wrapper(info):
        print("this is decorator")
        start_time = time.time()
        func(info)
        end_time = time.time()
        print(f'程序运行时间为:{end_time - start_time}')
    return wraper

@cost_time
def my_func(info):
    print(info)
    time.sleep(1)

my_func("hello world")
this is decorator
hello world
程序运行时间为:1.0005288124084473
  1. 带多个参数的装饰器

在这里, my_func(info)带了info参数,如果要实现的程序需要多个参数怎么办呢?一般情况下,我们会把*args和**kwargs,,作为装饰器内部函数wrapper()的参数。

def decorator(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper
  1. 带自定义参数的装饰器

装饰器可以接受原函数任意类型和数量的参数,还可以接受自定义的参数,比如我要想控制程序运行的次数。

import time

def repeat(num):
    def cost_time(func):
        def wrapper(*args,**kwargs):
            start_time = time.time()
            for i in range(num):
                print("this is decorator")
                func(*args,**kwargs)
            end_time = time.time()
            print(f'程序运行时间为:{end_time - start_time}')
        return wrapper
    return cost_time

@repeat(3)
def my_func(info):
    print(info)
    time.sleep(1)

my_func("hello world")
print(my_func.__name__)
this is decorator
hello world
this is decorator
hello world
this is decorator
hello world
程序运行时间为:3.006178617477417
wrapper#不再是my_func函数

这里需要注意的是my_func(info)函数被装饰后,元信息发生了改变,print(my_func.name)显示的结果是wrapper函数,不再是原来的my_func函数。为了解决这个问题,可以使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息,其本质也就是将原函数的元信息,拷贝到对应的装饰器函数里。

import time
import functools

def cost_time(func):
    @functools.wraps(func)#使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息
    def wraper(info):
        print("this is decorator")
        start_time = time.time()
        func(info)
        end_time = time.time()
        print(f'程序运行时间为:{end_time - start_time}')
    return wraper

@cost_time
def my_func(info):
    print(info)
    time.sleep(1)

my_func("hello world")
print(my_func.__name__)
this is decorator
hello world
程序运行时间为:1.0009491443634033
my_func#元信息保留了
  1. 装饰器的嵌套

python也支持多个装饰器,比如:

@decorator1
@decorator2
def hello(info):
    print(info)

它等同于

decorator1(decorator2(hello))

执行的顺序是从左到右执行,比如:

import functools

def decorator1(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        print("this is decorator1")
        func(*args,**kwargs)
    return wrapper

def decorator2(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        print("this is decorator2")
        func(*args,**kwargs)
    return wrapper

@decorator1
@decorator2
def hello(info):
    print(info)

hello("hello world!")
this is decorator1
this is decorator2
hello world!

先执行的是decorator1,然后再执行decorator2,最后执行hello函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容