实例讲解Python的函数闭包使用中应注意的问题

作者:TypingQuietly 时间:2022-05-17 23:29:53 

昨天正当我用十成一阳指功力戳键盘、昏天暗地coding的时候,正好被人问了一个问题,差点没收好功,洪荒之力侧漏震伤桌边的人,废话不多说,先上栗子(精简版,只为说明问题):


from functools import wraps
from time import sleep

def retry(attempts=3, wait=2):
 if attempts < 0 or attempts > 5:
   retry_times = 3
 else:
   retry_times = attempts
 if wait < 0 or wait > 5:
   retry_wait = 2
 else:
   retry_wait = after
 def retry_decorator(func):
   @wraps(func)
   def wrapped_function(*args, **kwargs):
     while retry_times > 0:
       try:
         return func(*args, **kwargs)
       except :
         sleep(retry_wait)
         retry_times -= 1
   return wrapped_function
 return retry_decorator

简易版的retry装饰器,需要的变量被闭包完美捕捉,逻辑也挺简单明了。问的人说逻辑看着挺正常的,但就是一直报变量retry_times找不到(unresolved reference)的错误提示。

没错仔细捋一下,这是一道送分题呢:闭包捕获的变量(retry_times,retry_wait)相当时引用的retry函数的局部变量,当在wrapped_function的局部作用于里面操作不可变类型的数据时,会生成新的局部变量,但是新生成的局部变量retry_times在使用时还没来得及初始化,因此会提示找不到变量;retry_wait相反能被好好的使用到。

python是duck-typing的编程语言,就算有warning照样跑,写个简单到极限的的函数,用一下装饰器,在wrapped_function逻辑里打个断点看一下各个变量的值也是很快能找到问题的(直接跑也能看到错误:UnboundLocalError: local variable 'retry_attempts' referenced before assignment, 至少比warning msg有用):


@retry(7, 8)
def test():
 print 23333
 raise Exception('Call me exception 2333.')

if __name__ == '__main__':
 test()

output: UnboundLocalError: local variable 'retry_times' referenced before assignment

要解决这种问题也好办,用一个可变的容器把要用的不可变类型的数据包装一下就行了(说个好久没写C#代码记不太清楚完全不负责任的题外话,就像在C#.net里面,碰到闭包的时候,会自动生成一个混淆过名字的类然后把要被捕捉的值当作类的属性存着,这样在使用的时候就能轻松get,著名的老赵好像有一篇文章讲Lazy Evaluation的好像涉及到这个话题):


def retry(attempts=3, wait=2):
 temp_dict = {
   'retry_times': 3 if attempts < 0 or attempts > 5 else attempts,
   'retry_wait': 2 if wait < 0 or wait > 5 else wait
 }

def retry_decorate(fn):
   @wraps(fn)
   def wrapped_function(*args, **kwargs):
     print id(temp_dict), temp_dict
     while temp_dict.get('retry_times') > 0:
       try:
         return fn(*args, **kwargs)
       except :
         sleep(temp_dict.get('retry_wait'))
         temp_dict['retry_times'] = temp_dict.get('retry_times') - 1
       print id(temp_dict), temp_dict

print id(temp_dict), temp_dict

return wrapped_function

return retry_decorate

@retry(7, 8)
def test():
 print 23333
 raise Exception('Call me exception 2333.')

if __name__ == '__main__':
 test()

输出:


4405472064 {'retry_wait': 2, 'retry_times': 3}
4405472064 {'retry_wait': 2, 'retry_times': 3}
23333
4405472064 {'retry_wait': 2, 'retry_times': 2}
23333
4405472064 {'retry_wait': 2, 'retry_times': 1}
23333
4405472064 {'retry_wait': 2, 'retry_times': 0}

从output中可以看到,用dict包装后,程序能够正常的工作,和预期的一致,其实我们也可以从函数的闭包的值再次确认:


>>> test.func_closure[1].cell_contents
{'retry_wait': 2, 'retry_times': 2}

我是结尾,PEACE!

标签:Python,闭包
0
投稿

猜你喜欢

  • 为ABP框架增加日志组件与依赖注入服务

    2024-06-05 15:43:32
  • 验证码的最高境界

    2008-05-08 14:17:00
  • pyqt5-tools安装失败的详细处理方法

    2021-08-22 21:34:00
  • js获取select选中的option的text示例代码

    2024-04-19 09:58:48
  • Go语言底层原理互斥锁的实现原理

    2024-04-25 15:00:24
  • python实现彩色图转换成灰度图

    2022-03-11 02:41:30
  • js使用eval解析json(js中使用json)

    2024-04-19 10:00:31
  • python/golang实现循环链表的示例代码

    2021-10-31 23:32:20
  • javascript如何实现360度全景照片问题汇总

    2024-04-10 10:43:38
  • asp金额大小写转换完全无错版

    2007-09-26 09:38:00
  • 一篇文章带你入门python之推导式

    2022-02-05 08:29:35
  • python案例练习合集

    2021-03-04 15:30:27
  • 使用Python的urllib2模块处理url和图片的技巧两则

    2022-02-15 21:26:00
  • 网页特效文字之—银箔字

    2013-08-07 00:21:39
  • 最新idea2021注册码永久激活(激活到2100年)

    2023-06-21 01:42:42
  • Pytorch搭建简单的卷积神经网络(CNN)实现MNIST数据集分类任务

    2021-04-24 02:25:16
  • IDEA配置GIT的详细教程

    2022-09-25 09:18:51
  • 使用SQL Server时最容易忽略的21个问题

    2009-01-13 13:59:00
  • MySQL5.7中的JSON基本操作指南

    2024-01-27 20:38:34
  • golang 实现struct、json、map互相转化

    2024-02-15 12:30:40
  • asp之家 网络编程 m.aspxhome.com