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


Posted in Python onJune 20, 2016

昨天正当我用十成一阳指功力戳键盘、昏天暗地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 相关文章推荐
python的几种开发工具介绍
Mar 07 Python
python实现倒计时的示例
Feb 14 Python
在Django框架中编写Contact表单的教程
Jul 17 Python
python cx_Oracle模块的安装和使用详细介绍
Feb 13 Python
python字符串过滤性能比较5种方法
Jun 22 Python
Python入门必须知道的11个知识点
Mar 21 Python
使用Python发现隐藏的wifi
Mar 04 Python
Python tcp传输代码实例解析
Mar 18 Python
Django实现图片上传功能步骤解析
Apr 22 Python
python多线程实现同时执行两个while循环的操作
May 02 Python
Python实现单例模式的5种方法
Jun 15 Python
Python Pandas pandas.read_sql函数实例用法
Jun 21 Python
Python中的数学运算操作符使用进阶
Jun 20 #Python
Python中在for循环中嵌套使用if和else语句的技巧
Jun 20 #Python
解析Python中的生成器及其与迭代器的差异
Jun 20 #Python
Python判断列表是否已排序的各种方法及其性能分析
Jun 20 #Python
Python编程中装饰器的使用示例解析
Jun 20 #Python
12步入门Python中的decorator装饰器使用方法
Jun 20 #Python
深入学习Python中的装饰器使用
Jun 20 #Python
You might like
在Mac OS上搭建PHP的Yii框架及相关测试环境
2016/02/14 PHP
PHP连接MySQL数据库的三种方式实例分析【mysql、mysqli、pdo】
2019/11/04 PHP
Yii 框架入口脚本示例分析
2020/05/19 PHP
判断页面是关闭还是刷新的js代码
2007/01/28 Javascript
PNGHandler-借助JS让PNG图在IE下实现透明(包括背景图)
2007/08/31 Javascript
javascript比较文档位置
2008/04/08 Javascript
从jquery的过滤器.filter()方法想到的
2013/09/29 Javascript
jquery+ajax实现跨域请求的方法
2015/01/20 Javascript
AngularJS 实现JavaScript 动画效果详解
2016/09/08 Javascript
详解js的延迟对象、跨域、模板引擎、弹出层、AJAX【附实例下载】
2016/12/19 Javascript
MUI 解决动态列表页图片懒加载再次加载不成功的bug问题
2017/04/13 Javascript
前端MVVM框架解析之双向绑定
2018/01/24 Javascript
深入理解 Koa 框架中间件原理
2018/10/18 Javascript
Vue表单控件绑定图文详解
2019/02/11 Javascript
express.js中间件说明详解
2019/03/19 Javascript
JavaScript深入V8引擎以及编写优化代码的5个技巧
2019/06/24 Javascript
layui form.render('select', 'test2') 更新渲染的方法
2019/09/27 Javascript
laydate只显示时分 不显示秒的功能实现方法
2019/09/28 Javascript
vue跳转同一个组件,参数不同,页面接收值只接收一次的解决方法
2019/11/05 Javascript
[14:36]2014 DOTA2国际邀请赛中国区预选赛5.21 Orenda VS NE
2014/05/22 DOTA
python连接远程ftp服务器并列出目录下文件的方法
2015/04/01 Python
python同时遍历数组的索引和值的实例
2018/11/15 Python
基于树莓派的语音对话机器人
2019/06/17 Python
Django之富文本(获取内容,设置内容方式)
2020/05/21 Python
python下载的库包存放路径
2020/07/27 Python
戴尔新加坡官网:Dell Singapore
2020/12/13 全球购物
家具厂厂长岗位职责
2014/01/01 职场文书
酒店中秋节促销方案
2014/01/30 职场文书
幼儿园大班教学反思
2014/02/10 职场文书
平安建设实施方案
2014/03/19 职场文书
《路旁的橡树》教学反思
2014/04/07 职场文书
组工干部演讲稿
2014/09/02 职场文书
部队2014年终工作总结
2014/11/27 职场文书
2015年设计师个人工作总结
2015/04/25 职场文书
日本动漫十大公认神作:第五现已全网禁播,《死亡笔记》在榜
2022/03/18 日漫
MySQL中的 inner join 和 left join的区别解析(小结果集驱动大结果集)
2023/05/08 MySQL