通俗易懂解释python闭包,python中闭包必须存在于什么关系的函数中

  通俗易懂解释python闭包,python中闭包必须存在于什么关系的函数中

  本文主要介绍Python中闭包和自由变量的相关信息,有需要的朋友可以参考一下。

  

目录
1.定义2 .非局部关键字3。注意事项4。使用场景总结

  

1.定义

  在函数内部定义另一个函数,这个函数使用外部函数的变量(LEGB),最后返回新创建函数的函数名的索引。然后,这样一个可以访问定义它的作用域和使用的一些变量的函数被称为闭包。引用的非全局变量也称为自由变量。这个自由变量存储在外部函数的只读属性__closure__中,它会和内部函数产生绑定关系,即自由变量不会轻易在内存中消失。如下例所示:

  #统计该函数被调用的次数

  定义计数器(FIRST=0):

  -_ _关闭__ -

  cnt=[FIRST] #选择该列表是因为范围问题。详见下文。

  def add_one():

   cnt[0]=1

   return cnt[0]

  -

  返回add_one

  #每当调用外部函数时,内部函数都会被重新定义,变量cnt的值可能不同。

  num5=计数器(5)

  num10=计数器(10)

  print(num5()) # 6

  print(num5()) # 7

  print(num10()) # 11

  print(num10()) # 12

  #如果这个函数只是一个嵌套函数,它的__closure__应该是None

  打印(num5。_ _ closure _ _)#(0x 0163 Fe 30:处的单元格列出0x01514A80处的对象,)

  打印(num5。__closure__[0]。单元格_内容)# 7

  打印(num10。__closure__[0]。单元格内容)# 12

  #或者通过__code__检查函数中是否有自由变量。co_freevars。如果有一个自由变量,它就是一个闭包。

  打印(num10。__代码_ _。co_freevars) # (cnt ,)

  

2.nonlocal 关键字

  上面代码中的cnt变量是一个列表,一个变量对象,但是如果是一个不可变的对象呢,比如numer,tuple等?

  定义计数器(FIRST=0):

  cnt=第一个#号

  def add_one():

  cnt=1

  返回cnt

  返回add_one

  num5=计数器(5)

  打印(num5。__closure__)

  打印(num5。__代码_ _。co_freevars)

  打印(num5())

  -

  定义计数器(FIRST=0):

  cnt=(FIRST,)# tuple

  def add_one():

  cnt[0]=1

  返回计数[0]

  返回add_one

  num5=计数器(5)

  打印(num5。__closure__)

  打印(num5。__代码_ _。co_freevars)

  打印(num5())

  上面的示例输出结果:

  没有人

  ()

  回溯(最近呼叫):

  模块中文件 test.py 的第行

  打印(num5())

  add_one中文件“test.py”的第行

  cnt=1

  UnboundLocalError: lo

  cal variable cnt referenced before assignment

  ----------------------------------------------------------------------------

  (<cell at 0x0180FE10: tuple object at 0x0173A750>,)

  (cnt,)

  Traceback (most recent call last):

   File "test.py", line, in <module>

   print(num5())

   File "test.py", line, in add_one

   cnt[0] += 1

  TypeError: tuple object does not support item assignment

  

  可以看出,此时 cnt 不再是自由变量,而是变成了局部变量,且提示 UnboundLocalError 未绑定局部错误。为什么不是自由变量了呢?为什么列表就没问题呢?

  这是因为Python 中并没有要求先声明一个变量才能使用它,Python 解释器认为:在函数体内,只要对一个变量进行赋值操作,那么这个变量就是局部变量。
Python的模块代码执行之前,并不会经过预编译,模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。

  而 cnt += 1 相当于 cnt = cnt + 1,对 cnt 进行了赋值操作,所以 Python 解释器认为 cnt 是函数内的局部变量,但是执行的时候,先执行 cnt+1 时发现:
因为先前已经认定 cnt 为局部变量了,现在在局部作用域内找不到 cnt 的值,也不会再到外部作用域找了,就会报错。所以说现在 cnt 已经不是自由变量了。

  那么 tuple 类型的 cnt 呢?首先 cnt[0] = cnt[0] + 1,虽然有赋值,但是其左边也是 cnt[0],cnt 是从外边作用域索引了的。
所以,你看它显示的结果:此时,cnt 确实也是自由变量的,但是它是不可变对象啊,所以报了 TypeError 错误。这下列表为什么行,你应该知道了。

  或者你使用nonolocal关键字,这个关键字的用法与 global 很像,让你能够给外部作用域(非全局作用域)内的变量赋值。它可以使得一个被赋值的局部变量变为自由变量,并且nonlocal声明的变量发生变化时,__closure__中存储的值也会发生变化:

  

def counter(FIRST=0):

   cnt = FIRST # number

   def add_one():

   nonlocal cnt

   cnt += 1

   return cnt

   return add_one

  num5 = counter(5)

  print(num5.__closure__)

  print(num5.__code__.co_freevars)

  print(num5())

  

  

(<cell at 0x01BFFE30: int object at 0x53E064D0>,)

  (cnt,)

  6

  

  nonlocal 和 global

  

def scope_test():

   spam = "test spam"

   def do_nonlocal():

   nonlocal spam

   spam = "nonlocal spam"

   def do_global():

   global spam

   spam = "global spam"

   do_nonlocal()

   print("After nonlocal assignment:", spam) # nonlocal spam

   do_global()

   print("After global assignment:", spam) # nonlocal spam

  scope_test()

  print("In global scope:", spam) # global spam

  

  

After nonlocal assignment: nonlocal spam

  After global assignment: nonlocal spam

  In global scope: global spam

  

  

  

3.注意事项

  lambda 自由参数之坑,特别是和列表解析或for循环结合使用时。lambda para_list : expression == > def(para_list): return expression

  

#---CASE1

  fs = [lambda j:i*j for i in range(3)]

  print([f(2) for f in fs])

  #---CASE2

  fs = map(lambda i:(lambda j: i*j), range(3))

  print([f(2) for f in fs])

  #---CASE3

  fs = [(lambda i:lambda j:i*j)(i) for i in range(3)]

  print([f(2) for f in fs])

  

  

[4, 4, 4]

  [0, 2, 4]

  [0, 2, 4]

  

  首先,CASE1 和 CASE3 显然都是每循环一次,就添加一个 lambda 函数到列表中,不同的是,CASE1 添加的 lambda 函数中的 i 每次并没有接收 for 循环中 i 的值,它只是定义的时候指了下 i,所以说,CASE1 中的几个 lambda 函数的 i,是最后调用的时候,也就是 f(2) 时才到外层作用域找它的值的,此时找到的 i 的值就是里面 for 循环结束时的 i 的值。CASE3 则是一开始定义、添加的时候就给 i 赋好了初值。CASE2 则是因为 map 每次迭代的时候都会将一个可迭代对象的元素传给了 i,所以 CASE2 里面的每个 lambda 函数的 i 也是各有各的值的。

  像这种 lambda 的自由参数的问题的话,如果你不是故意这么做的话,还是转为默认参数的好:

  

fs = [lambda x: x+i for i in range(3)]

  print([f(2) for f in fs])

  fs = [lambda x, i=i: x+i for i in range(3)]

  print([f(2) for f in fs])

  

  

[4, 4, 4]

  [2, 3, 4]

  

  另外,就是列表解析里面的作用域是一个全新的作用域,和普通的 for 循环则有所不同:

  

#---CASE4

  fs = [lambda j:i*j for i in range(3)]

  print([f(2) for f in fs])

  i = 4

  print([f(2) for f in fs])

  #---CASE5

  fs = []

  for i in range(3):

   fs.append(lambda j:i*j)

  print([f(2) for f in fs])

  i = 4

  print([f(2) for f in fs])

  

  

[10, 10, 10]

  [10, 10, 10]

  [10, 10, 10]

  [8, 8, 8]

  

  

  

4.使用场景

  

  • 装饰器

      

  • 惰性求值,比较常见的是在数据库访问的时候,可参考 Django 的 queryset 的实现

      

  • 需要对某个函数的参数提前赋值的情况;当然也可以使用 functools.parial 的偏函数:functools.partial(func, *args, **kw),返回一个 partial 函数对象。

      

  

# y = a*x + b, a 和 b 可能只出现一次, x 会出现多次

  def line(a, b, x):

   return a*x + b

  print(line(3, 4, 5))

  print(line(3, 4, 6))

  print(line(7, 4, 5))

  print(line(7, 4, 6))

  # 2.使用闭包

  def line(a, b):

   def value(x):

   return a*x + b

   return value

  # y = 3x + 4

  line1 = line(3, 4)

  print(line1(5))

  print(line1(6))

  print(line1(7))

  # y = 9x + 7

  line2 = line(9, 7)

  print(line2(5))

  print(line2(6))

  print(line2(7))

  # 3.使用 functools.partial 偏函数

  from functools import partial

  line3 = partial(line, 3)

  print(line3) # functools.partial(<function line at 0x011237C8>, 3)

  print(line3(4, 5))

  line4 = partial(line, 3, 4)

  print(line4(5))

  print(line4(6))

  print(line4(7))

  line5 = partial(line, 9, 7)

  print(line5(5))

  print(line5(6))

  print(line5(7))

  

  简单总结functools.partial的作用就是:其能把一个函数的某些参数给固定住(也就是设置默认值),并返回一个新的函数,调用这个新函数会更简单。

  

  

总结

  到此这篇关于Python中闭包和自由变量的文章就介绍到这了,更多相关Python闭包和自由变量内容请搜索盛行IT软件开发工作室以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT软件开发工作室!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: