文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2019-09-15   917

Python函数式编程-闭包

搬运自https://serholiu.com/python-closures

首先给出闭包的一种浅显的定义(可能不够准确):要形成闭包,首先得有一个嵌套的函数,即函数中定义了另一个函数,闭包则是一个集合,它包括了外部函数的局部变量,这些局部变量在外部函数返回后也继续存在,并能被内部函数引用。

举个例子

定义一个函数 generate_power_func,它返回另一个函数,现在闭包形成的条件已经达到。

1
2
3
4
5
6
def generate_power_func(n):
print('id(n): %X' % id(n))
def nth_power(x):
return x**n
print('id(nth_power): %X' % id(nth_power))
return nth_power

对于内部函数 nth_power,它能引用到外部函数的局部变量 n,而且即使 generate_power_func 已经返回。把这种现象就称为闭包。具体使用一下。

1
2
3
4
5
>>> raised_to_4 = generate_power_func(4)
id(n): 7FF968E462F0
id(nth_power): 1A5E3415510
>>> repr(raised_to_4)
'<function generate_power_func.<locals>.nth_power at 0x000001A5E3415510>'

可以看出,当generate_power_func(4)执行后,创建并返回了nth_power这个函数对象,内存地址是1A5E3415510,并且发现raised_to_4和它的内存地址相同,**即raised_to_4**只是这个函数对象的一个引用。先在全局命名空间中删除 generate_power_func,再试试会出现什么结果。

1
2
3
>>> del generate_power_func
>>> raised_to_4(2)
16

这就是闭包的作用,外部函数的局部变量可以被内部函数引用,即使外部函数已经返回了。

__closure__属性和cell对象

那么,闭包在Python内部到底是怎么实现的呢?Python 中函数也是对象,所以函数也有很多属性,和闭包相关的就是 __closure__ 属性。__closure__ 属性定义的是一个包含 cell 对象的元组,其中元组中的每一个 cell 对象用来保存作用域中变量的值。

1
2
3
4
5
6
7
8
>>> raised_to_4.__closure__
(<cell at 0x000001A5E320DDC8: int object at 0x00007FF968E462F0>,)
>>> type(raised_to_4.__closure__)
<class 'tuple'>
>>> type(raised_to_4.__closure__[0])
<class 'cell'>
>>> raised_to_4.__closure__[0].cell_contents
4

就如刚才所说,在 raised_to_4__closure__ 属性中有外部函数变量 n 的引用,通过内存地址可以发现,引用的都是同一个 n。如果没用形成闭包,则 __closure__ 属性为 None

Attention

一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

1
2
3
4
5
6
7
8
9
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append()
return fs

f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

1
2
3
4
5
6
>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

1
2
3
4
5
6
7
8
9
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
Copyright © ca01h 2019-2020 | 本站总访问量