Python 中的内部函数 | 闭包

最近在学习 Python 的过程中遇到了闭包这一概念,现总结如下:

首先什么是闭包呢

咱们看看下面一段代码:

def greetingConfig(prefix):
    def greeting(postfix):
        print(prefix, postfix)
    return greeting


m = greetingConfig('Good morning!')
m('July')
m('Mike')

这段代码实现了一个打招呼的函数 greetingConfig(),实现该程序的结果为:

Good morning! July
Good morning! Mike

在以上程序中,greetingConfig() 函数嵌套 greeting() 函数,且 greeting() 函数的引用作为返回值。greeting() 函数访问上一级函数的变量 prefix。此时,greetingConfig() 函数就是闭包。

闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用自由变量的函数。这个被引用的自由变量将与这个函数一同存在,即使已经离开创造它的环境也不例外。所以,闭包是由函数和与其相关的引用环境组合而成的实体。

闭包是函数中提出的一个概念,要形成一个完整严格的闭包,需要满足以下条件(三个条件,缺一不可):

  • 必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
  • 外部函数有返回值,且返回的值是:内部函数的引用(即内部函数的地址)
  • 内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量(满足这个条件才能算是严格的闭包)
格式
def 外部函数():
	...
	def 内部函数():
		...
	return 内部函数引用
作用
  • 闭包主要用在函数式编程上
  • 可以根据外部作用域的局部变量来得到不同的结果
    比如:求一个点到其他点之间的距离(不使用闭包,使用闭包分别验证)
import math


# 不使用闭包需要4个参数
def dis_bt_point(x1, y1, x2, y2):
    return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))


# 不使用闭包的调用求距离函数
print('点(10,10)到坐标(0,0)的距离:{:.2f}'.format(dis_bt_point(0, 0, 10, 10)))
print('点(20,10)到坐标(0,0)的距离:{:.2f}'.format(dis_bt_point(0, 0, 20, 10)))


# 闭包
def disOut(x1, y1):
    def disIn(x2, y2):
        return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))

    return disIn


distance = disOut(0, 0)
# 获取点(10,10)到坐标(0,0)的距离
print('点(10,10)到坐标(0,0)的距离:{:.2f}'.format(distance(10, 10)))
# 获取点(20,10)到坐标(0,0)的距离
print('点(20,10)到坐标(0,0)的距离:{:.2f}'.format(distance(20, 10)))

运行结果:

点(10,10)到坐标(0,0)的距离:14.14
点(20,10)到坐标(0,0)的距离:22.36
点(10,10)到坐标(0,0)的距离:14.14
点(20,10)到坐标(0,0)的距离:22.36
  • 在不修改源代码的情况下增加函数功能
    比如:为调用的函数增加日志功能,记录访问的函数名以及访问函数的时间
import time


def write_log(func):
    try:
        with open('log.txt', 'a', encoding='utf-8') as f:
            # 写入相关数据信息(访问的函数名,访问的时间)
            f.write(func.__name__)
            f.write('\t')

            # 写入访问时间
            f.write(time.asctime())
            f.write('\n')
    except Exception as e:
        print(e)


# 闭包
def funcOut(func):
    def wrapper():
        write_log(func)
        func()

    return wrapper


def func1():
    print('功能1')


def func2():
    print('功能2')


if __name__ == '__main__':
    func1()
    func2()

运行结果:

  • 控制台:
功能1
功能2
  • log.txt:
func1	Mon Dec  7 21:39:27 2020
func2	Mon Dec  7 21:39:27 2020

闭包中的关键部分—内部函数

特点
  • 可以访问外部函数的变量
  • 内部函数可以修改外部函数的可变变量比如: list
  • 内部函数修改全局的不可变变量时,需要在内部函数声明:global 变量名
  • 内部函数修改外部函数的不可变变量时,需要在内部函数中声明:nonlocal 变量名
  • locals() 查看本地变量有哪些,以字典的形式输出
  • globals() 查看全局变量有哪些,以字典的形式输出(注意里面会有一些系统的键值对)
示例代码
a = 100


def func():
    b = 99

    def inner_func():
        global a
        nonlocal b
        c = 88
        a += 10
        b += 1
        c += 12
        print(a, b, c)

    inner_func()
    print(locals())
    print(globals())


if __name__ == '__main__':
	func()

# 运行结果:
110 100 100
{'inner_func': <function func.<locals>.inner_func at 0x0000020B1CF02378>, 'b': 100}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002517AA6B4A8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/PythonProject/test.py', '__cached__': None, 'math': <module 'math' (built-in)>, 'random': <module 'random' from 'E:\\python\\lib\\random.py'>, 'quadratic': <function quadratic at 0x000002517A961E18>, 'a': 110, 'func': <function func at 0x000002517AD222F0>}

示例代码(简单应用:计数器)

def counter():
    count = [0]

    def add_one():
        count[0] += 1
        print('当前是第{}次访问'.format(count[0]))

    return add_one


if __name__ == '__main__':
	count = counter()
	
	count()
	count()
	count()

# 运行结果:
当前是第1次访问
当前是第2次访问
当前是第3次访问

闭包中内部函数访问外部函数内的变量时,内部函数在调用完后并不会立即将该内存空间回收,而是会一直保持调用时的状态。用不同的参数值进行调用时,会另外开辟一块内存空间进行存储,而不会影响之前的引用内容。

总结

  • 由于闭包引用了外部函数的局部变量,使外部函数的局部变量没有及时释放,消耗内存
  • 作用域没有那么直观
  • 闭包的好处,使代码变得简洁,便于阅读代码。
  • 闭包是理解装饰器的基础
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值