python中的闭包和装饰器

闭包函数介绍

什么是闭包

维基百科中关于闭包的概念:

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组 “私有” 变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。

对上面这段话总结一下,即python中的闭包需要满足3个条件:
1) 内嵌函数,即函数里定义了函数 —— 这对应函数之间的嵌套
2) 内嵌函数必须引用定义在外部函数里中的变量(不是全局作用域中的引用)—— 内部函数引用外部变量
3) 外部函数必须返回内嵌函数

闭包函数示例:

def funx():
    x = 1
    y = 2
    def funy():
        print(x, y)
    return funy       # 返回的函数就是 闭包函数

a = funx()          # 这里的 变量 a 就是闭包函数

Tip:funx 返回的函数不仅仅是函数本身,返回的函数外面还包了一层作用域关系~

所有的闭包函数都有这个属性:closure(若没有就不是闭包函数,这是闭包函数的特点),a.closure为元组,每个元素包含了闭包外面的那层作用域中的一个变量的值,a.closure[0].cell_contents 和 a.closure[1].cell_contents 分别引用作用域中 x 和 y 变量

print(type(a.__closure__))       #  <class ‘tuple‘>
print(a.__closure__)
# (<cell at 0x000001CA6EAA64F8: int object at 0x000000006F036DE0>, <cell at 0x000001CA6EAA6528: int object at 0x000000006F036E00>)

print(a.__closure__[0].cell_contents)    # 1
print(a.__closure__[1].cell_contents)    # 2

闭包的应用

闭包函数不光光是函数,还带了一层作用域;在调用外部函数时,可以通过参数传递不同的值,使得返回的闭包函数的作用域中所带的变量各不相同。上面示例中的 x,y 也可以通过参数传入:

def funx(x,y):
    def funy():
        print(x, y)
    return funy       # 返回的函数就是 闭包函数

a = funx()          # 这里的 变量 a 就是闭包函数

所以可以总结一下,闭包的特点有2个:
1)自带作用域
2)延迟计算

个人总结了python的闭包大致有如下2个应用:
(1)通过自带的作用域保留状态

def foo(sum = 0):
    def add(x):
        nonlocal sum                 # nonlocal 指定这里使用的 sum 为外部函数中的 sum 变量
        sum = sum + x
        print(‘sum: ‘ + str(sum))
    return add

g = foo(sum = 10)
g(4)             # sum: 14
g(5)             # sum: 19
g(6)             # sum: 25

如上示例中,每次调用add函数(g())都会将所传递的参数与外部函数中的 sum 变量相加,并打印,参数的总和会保留在闭包函数自带作用域的 sum 变量中。在获取闭包函数时,还可以指定sum的初始大小~

(2)根据自带作用域的局部变量来得到不同的结果
使用自带作用域存储文件名,每次返回的闭包函数仅用于 针对一个文件、不同关键字的过滤

import os, sys
def foo(filename):
    def find_line(parse_str):
        # 判断文件是否存在
        if not os.path.exists(filename):
            print(‘file not exist~‘)
            sys.exit(1)
        with open(file=filename, mode=‘r‘, encoding=‘utf-8‘) as f:
            for line in f:
                if line.find(parse_str) != -1:
                    print(line, end=‘‘)
    return find_line

log_1 = foo(filename=r‘F:\tmp\abc.txt‘)    # 专用于对文件 ‘F:\tmp\abc.txt‘ 的过滤
log_1(‘[ERROR]‘)
log_1(‘[INFO]‘)

log_2 = foo(filename=r‘F:\tmp\abc.txt‘)    # 专用于对文件 ‘F:\tmp\abc.txt‘ 的过滤
log_2(‘[ERROR]‘)
log_2(‘[INFO]‘)

开放封闭原则

开放封闭原则:对扩展是开放的,对修改是封闭的,即源代码不能修改,留出扩展的可能性~
在维护过程中,很多时候需要对原有的功能(例如函数)进行扩展,但是直接修改某个函数的源代码存在一定风险,理想的状况是在不修改源码的情况下对函数的原有功能进行扩展~

例如现在需要对如下 index 函数进行扩展,计算这个函数的执行时间~

import random, time

def index():
    time.sleep(random.randrange(1,5))
    print(‘welcome to index page‘)

def foo():
    start_time = time.time()
    index()
    stop_time = time.time()
    print(‘run time is %s‘ % (stop_time - start_time))

# 调用 index 函数
foo()      # index()

这样的话,源代码没有发生改变,新功能也添加了,但是调用方式发生了改变,原本调用 index(),现在变成了 foo() ~,且若要为很多个函数添加相同的功能,只能一个一个的添加

现改用闭包函数,可为 多个函数添加相同的功能:

import random, time
def timmer(func):
    def warpper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print(‘run time is %s‘ % (stop_time - start_time))
    return warpper

def index():
    time.sleep(random.randrange(1,5))
    print(‘welcome to index page‘)

def error():
    time.sleep(random.randrange(2, 10))
    print(‘welcome to error page‘)

index = timmer(index)   # 原本直接调用index(),现在需要添加这么一行
index()

error = timmer(error)
error()

这样还是存在缺陷,就是每次执行前都需要 生成闭包函数(index = timmer(index))。那如何解决呢?就是使用装饰器~

装饰器

装饰器语法

装饰器的语法,在被装饰对象的正上方 添加 ‘@装饰器名字‘;将正下方的函数名当做一个参数传递给装饰器,并将返回值重新赋值给函数名~
上面的示例通过装饰器实现:

import random, time
def timmer(func):
    def warpper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print(‘run time is %s‘ % (stop_time - start_time))
    return warpper

@timmer            # 等效于 index = timmer(index)
def index():
    time.sleep(random.randrange(1,5))
    print(‘welcome to index page‘)

@timmer
def error():
    time.sleep(random.randrange(2, 10))
    print(‘welcome to error page‘)

index()           # 调用的是warpper()
error()

这样就满足了开放封闭原则~

多个装饰器的使用

装饰器可以添加多个,执行顺序是 从下往上执行,如下示例中是 先添加auth,再执行timmer ~,即 index 函数先由 auth 进行封装,而后在这个基础之上再由 timmer 进行封装~

def timmer(func):
    def warpper():
        print(‘timmer_before_codes‘)
        func()                 # 执行时这里的 func 就是 deco (即 auth(index))
        print(‘timmer_after_codes‘)
    return warpper

def auth(func):
    def deco():
        print(‘auth_before_codes‘)
        func()                # 执行时这里是原始的index()
        print(‘auth_after_codes‘)
    return deco

@timmer
@auth
def index():
    print(‘welcome to index page‘)

index()     # 调用 index()

调用index后输出结果如下:

timmer_before_codes
auth_before_codes
index page
auth_after_codes
timmer_after_codes

原始函数有参数的场景

很简单,就是将参数传递到被装饰的函数当中~

def auth(func):
    def warpper(user):
        print(‘before_user_login‘)
        func(user)
        print(‘after_user_login‘)
    return warpper

@auth
def login(user):
    print(‘%s login success‘ % user)

login(‘kitty‘)

但是这个时候无参函数无法再使用这个装饰器进行装饰~,*agrs, **kwargs,可以使用可变长参数解决这个问题:

def auth(func):
    def warpper(*agrs, **kwargs):
        print(‘before_user_login‘)
        func(*agrs, **kwargs)
        print(‘after_user_login‘)
    return warpper

@auth
def login(user):
    print(‘%s login success‘ % user)

@auth
def index():
    print(‘welcome to index page‘)

login(‘kitty‘)
index()

原始函数有返回值的场景

若原始函数有返回值,在内部函数中 调用原始函数,获取返回值,并通过内部函数进行返回即可~

def timmer(func):
    def warpper(*agrs, **kwargs):
        start_time = time.time()
        res = func(*agrs, **kwargs)
        stop_time = time.time()
        print(‘run time is %s‘ % (stop_time - start_time))
                return res
    return warpper

wraps 的常用功能

原始函数被装饰以后,原有的一些属性会被 装饰后的函数所替代,例如文档字符串~

def timmer(func):
    def warpper(*agrs, **kwargs):
        ‘warpper function‘  # 文档字符串
        start_time = time.time()
        res = func(*agrs, **kwargs)
        stop_time = time.time()
        print(‘run time is %s‘ % (stop_time - start_time))
        return res
    return warpper

@timmer
def index():
    ‘index function‘      # 文档字符串
    print(‘welcome to index page‘)

print(index.__doc__)

输出结果:
warpper function

原本想获取index函数的说明信息,而返回的却是 warpper 函数的。这里可以使用 @wraps(func) ,用以保留原函数自己的一些原始信息;若函数已被装饰,又想调用原始的函数,可以在调用函数时使用函数的 wrapped 属性 就能够使用原始的函数,而不是被装饰后的函数~

def timmer(func):
    @wraps(func)
    def warpper(*agrs, **kwargs):
        ‘warpper function‘  # 文档字符串
        start_time = time.time()
        res = func(*agrs, **kwargs)
        stop_time = time.time()
        print(‘run time is %s‘ % (stop_time - start_time))
        return res
    return warpper

@timmer
def index():
    ‘index function‘      # 文档字符串
    print(‘welcome to index page‘)

print(index.__doc__)
index.__wrapped__()    # 调用原始函数

输出结果:
index function
welcome to index page

有参装饰器

之前用到的都是无参装饰器,有参装饰器,简单的说就是在原有的装饰器外面再套上一层带参数的函数~
还是这个函数,现在除了添加计时功能外,还需要添加debug功能,debug是否启用通过参数来实现~

def index():
    time.sleep(random.randrange(1,5))
    print(‘welcome to index page‘)

添加有参装饰器:

import time, random
import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

def timmer(is_debug):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if is_debug:
                begin = time.time()
                res = func(*args, **kwargs)
                logging.debug( "[" + func.__name__ + "] --> " + str(time.time() - begin) )
            else:
                res = func(*args, **kwargs)
            return res
        return wrapper
    return decorator

@timmer(is_debug = True)
def index():
    time.sleep(random.randrange(1, 5))
    print(‘welcome to index page‘)

index()

index函数上方的 @timmer(is_debug = True) 相当于 index = timmer(is_debug=True)(index),开启debug后输出结果如下:

welcome to index page
DEBUG:root:[index] --> 3.000962018966675

@timmer(is_debug = False),关闭 debug 后,函数的输出与装饰前一致:

welcome to index page

.................^_^

原文地址:http://blog.51cto.com/ljbaby/2235383

时间: 2024-08-10 11:46:44

python中的闭包和装饰器的相关文章

21.python中的闭包和装饰器

python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python2.7,其他版本可能存在差异. 也许直接看定义并不太能明白,下面我们先来看一下什么叫做内部函数: def wai_hanshu(canshu_1): def nei_hanshu(canshu_2): # 我在函数内部有定义了一个函数 return canshu_1*canshu_2 return

轻松理解python中的闭包和装饰器 (下)

在 上篇 我们讲了python将函数做为返回值和闭包的概念,下面我们继续讲解函数做参数和装饰器,这个功能相当方便实用,可以极大地简化代码,就让我们go on吧! 能接受函数做参数的函数我们称之为高阶函数,例如filter, map, reduce这些函数 可以定义一个函数作为高阶函数例如: def func(x, y, f): return f(x)+f(y) 可以这样调用func(2,-1,abs) 函数返回结果为3 有些时候,我们不需要显式地定义传入的函数,直接传入匿名函数更方便. 在Pyt

聊聊Python中的闭包和装饰器

1. 闭包 首先我们明确一下函数的引用,如下所示: def test1(): print("--- in test1 func----") # 调用函数 test1() # 引用函数 ret = test1 print(id(ret)) print(id(test1)) #通过引用调用函数 ret() 运行结果: --- in test1 func---- 140212571149040 140212571149040 --- in test1 func---- 以y=kx+b为例,请

python中的无参装饰器和有参装饰器

python中的无参装饰器和有参装饰器 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 装饰器特点: 1>.开放封闭原则,即对扩展是开放的,对修改时封闭的: 2>.装饰器本质可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象: 3>.装饰器的功能是在不修改被装饰器对象源代码以及被装饰器对象的调用方式的前提下为其扩展新功能: 4>.装饰器本质是函数,(即装饰其他函数)就是为其他函数添加附加功能. 一.典型装饰器案例 1 #!/usr/bin/env pyth

Python中,关于@property装饰器

1.为什么使用@property装饰器?br/>在类中,当我么不想在外界直接调用到类的属性,或者不想展示属性的真实内容时,可以用到@property.它规定了我们直接用 对象名.属性名 获取对象属性时并不会直接取得对象的属性,而是通过调用@property装饰过的属性函数来给调用者反馈. 2.我们为什么不使用特定的方法来进行上面的操作?原因是因为太繁琐.例: class Person: def __init__(self, username, password) -> None: self.u

Python基础day-7[闭包,装饰器]

闭包: 首先说下闭包是什么? 闭包就是在函数内部定义的函数,包含对外部作用域的引用,但不包含全局作用域.因为函数的作用域在定义的时候就固定死了,所以闭包函数有自带作用域和延迟计算的特点. 闭包函数定义:如果一个内部函数,包含了对外部作用域的引用,但是不是包含全局作用域.那么这个函数就被认为是闭包函数.闭包函数可以使用".__closure__" 来查看闭包函数的属性.下面我们来看一个示例: def t(): money = 100 def s(): print(money) retur

Python中带参数的装饰器

装饰器本身是用来是为一个函数是实现新的功能,并且不改变原函数的代码以及调用方式. 遇到这样一种问题: 众多函数调用了你写的装饰器,但客户有需求说,我想实现我可以随之控制装饰器是否生效. 那你就不可能在得到命令的时候去原函数头部去做删除和添加装饰器调用的命令.这是就可以用到带参数的装饰器,定义一个开关,调用装饰器的时候,把这个装饰器的开关参数给传递进去,这样当开关打开的时候装饰器生效,关闭的时候则只执行原函数的代码. 举例:开关参数为True的时候执行过程: 1 F = True #step 1

python函数2_闭包和装饰器

变量的范围(局部/全局) 局部变量 在函数内部声明的变量 在函数体外部无法获取 全局变量 在函数外部声明的变量 所有函数都可以访问 在函数内,局部变量和全局变量同名,优先使用局部变量 name = '月月' def fun2(): name = '小月月' name += '会弹吉他' print(name) fun2() 小月月会弹吉他 当在函数体内,尝试更改全局变量会报错 name = '月月' def fun2(): print(name) name += '会弹吉他' fun2() 当需

13、python中的函数(闭包与装饰器)

一.嵌套函数 函数的内部又再定义另一个函数,这个函数就叫嵌套函数,里面含函数就叫内部函数. 示例: 二.返回函数 函数可以接收函数对象作为参数,同理函数也能返回一个函数对象作为返回值. 示例: 返回函数可以用来延迟函数的执行. 三.命名空间与变量作用域 变量作用域指的是变量的存活的范围.命名空间指的是属于一个对象的所有属性(对象)的集合. 示例: A的命名空间是A函数对象里面的所有对象的集合,包括变量a.函数B.变量b:B的命名空间就是属于函数B的所有对象的集合,包括变量b: a的变量作用域就是