小时候,常被一些可笑的问题困扰——尽管成年以后面临的疑惑更多,但似乎是因为已经适应了在迷茫中前行,对于未解的问题反倒是失去了那种急于想知道答案的迫切感。比如,站在两面相对的镜子中间,会看到无数个自己吗?对于少时的我,这的确是一个非常魔幻的问题,直到理解了光量子能量衰减,才算找到了答案。

近日,有同学咨询Python对象的循环引用以及垃圾回收问题,结合前些日子遇到的循环调用和循环导入问题,在整理答案的时候,我忽然意识到,这几个问题居然和困惑我多年的“两面镜子”问题居然有相通之处:看起来都有些魔幻,转身即是真实的世界!

1. 走向毁灭的函数循环调用

如果多个函数相互调用,构成闭环,就形成了函数的循环调用。下面的例子中,函数a在其函数体中调用了函数b,而函数b在其函数体中又调用了函数a,这就是典型的函数循环调用。

>>> def a():

print('我是a')

b()

>>> def b():

print('我是b')

a()

>>> a()

此种情况下,调用函数(无论是a函数还是b函数),会发生什么呢?

>>> a()

我是a

我是b

我是a

我是b

...... # 此处省略了一千余行

Traceback (most recent call last):

File "", line 1, in

a()

File "", line 3, in a

b()

...... # 此处省略了两千余行

RecursionError: maximum recursion depth exceeded while pickling an object

很快你就会发现,运行出现了问题,系统连续抛出异常,大约滚动了几千行之后,终于结束了运行。最后的提示是:

RecursionError: maximum recursion depth exceeded while pickling an object

意思是说,发生了递归错误,在序列化(pickle)对象时超过了最大递归深度。

原来,循环调用类似于递归调用,为了保护堆栈不会溢出,Python环境一般都会设置递归深度保护,一旦查过递归深度,就会抛出递归错误,然后再一层一层退出堆栈。这就是屏幕滚动几千条错误信息的原因。

关于Python环境递归深度,可以通过sys模块查看和设置。

>>> import sys

>>> sys.getrecursionlimit()

1000

>>> sys.setrecursionlimit(500)

>>> sys.getrecursionlimit()

500

2. 同生共死的对象循环引用

函数的循环调用不难理解,而对象的循环引用就有点费解了。什么是对象的循环引用呢?当一个对象被创建时(比如实例化一个类),Python会为这个对象设置一个引用计数器。如果这个对象被引用,比如被关联到一个变量名,则该对象的引用计数器加1,如果关联关系取消,则该对象的引用计数器减1。当一个对象的引用计数器为1时(关于这一点,仅凭个人观察得出,未见权威说法),系统将自动回收该对象。这就是Python的垃圾回收机制。下面的代码,借助于sys模块,可以直观地看到一个列表对象的引用计数器的变化。

>>> import sys

>>> a = list('abc')

>>> sys.getrefcount(a)

2

>>> b = a

>>> sys.getrefcount(a)

3

>>> del b

>>> sys.getrefcount(a)

2

当多个对象存在相互间的成员引用,一旦形成闭环的时候,就会发生所谓对象的循环引用。我们来看一个例子:a和b是类A的两个实例对象,del这两个对象的时候,将会调用对象的__del__方法,最后显示“运行结束”。

class A:

def __init__(self, name, somebody=None):

self.name = name

self.somebody = somebody

print('%s: init'%self.name)

def __del__(self):

print('%s: del'%self.name)

a = A('a')

b = A('b')

del a

del b

print('运行结束')

运行结果正如我们所希望的那样。

a: init

b: init

a: del

b: del

运行结束

然而,当我们创建了实例a和b之后,如果将a.somebody指向b,将b.somebody指向a,那么就产生了实例间成员相互引用形成闭环的情况。

class A:

def __init__(self, name, somebody=None):

self.name = name

self.somebody = somebody

print('%s: init'%self.name)

def __del__(self):

print('%s: del'%self.name)

a = A('a')

b = A('b')

a.somebody = b

b.sombody = a

del a

del b

print('运行结束')

运行这段代码,你会发现,del这两个对象的时候,对象的__del__方法并没有被立即执行,而是程序结束之后才被执行的。

a: init

b: init

运行结束

a: del

b: del

这意味着,在程序运行期间,应该被回收的内存并没有正确回收。这样的问题,属于内存泄漏,应该给予高度重视。通常,我们可以使用gc模块强制回收内存。

import gc

class A:

def __init__(self, name, somebody=None):

self.name = name

self.somebody = somebody

print('%s: init'%self.name)

def __del__(self):

print('%s: del'%self.name)

a = A('a')

b = A('b')

a.somebody = b

b.sombody = a

del a

del b

gc.collect()

print('运行结束')

再看运行结果,一切正常了。

a: init

b: init

a: del

b: del

运行结束

3. 转圈推磨的模块循环导入

相对而言,模块的循环导入的情况一般极少发生。如果发生,一定是模块的功能分割不合理造成的,通过调整模块的定义,可以很容地解决问题。下面用一个最精简的例子,来演示一下模块循环导入是如何产生的。

名为a.py的脚本文件内容如下:

import b

MODULE_NAME = 'a'

print(b.MODULE_NAME)

名为b.py的脚本文件内容如下:

import a

MODULE_NAME = 'b'

print(a.MODULE_NAME)

两个脚本互相引用,并且各自使用了对方定义的常量MODULE_NAME。无论我们运行哪个脚本,都会因为模块的循环导入而无法正确执行。

Traceback (most recent call last):

File “a.py”, line 1, in

import b

File “D:\temp\csdn\b.py”, line 1, in

import a

File “D:\temp\csdn\a.py”, line 4, in

print(b.MODULE_NAME)

AttributeError: module ‘b’ has no attribute ‘MODULE_NAME’

python循环引用例子_魔幻离现实仅一步之遥:细说Python的循环调用、循环引用和循环导入...相关推荐

  1. 魔幻离现实仅一步之遥:细说Python的循环调用、循环引用和循环导入

    小时候,常被一些可笑的问题困扰--尽管成年以后面临的疑惑更多,但似乎是因为已经适应了在迷茫中前行,对于未解的问题反倒是失去了那种急于想知道答案的迫切感.比如,站在两面相对的镜子中间,会看到无数个自己吗 ...

  2. python赋值法例子_大佬们 我是刚开始学python的小白 遇到这种赋值方式 实在不懂这个a+b是赋值给谁的 求解...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 这个是python独有的赋值方法 萌新不懂很正常啦 这个叫做"元组赋值法" 他会把前后两个部分当成一个元组去操作 所以在赋值过程中值不 ...

  3. pythonfor循环语句例子_值得收藏!16段代码入门Python循环语句

    原标题:值得收藏!16段代码入门Python循环语句 导读:本文重点讲述for语句和while语句.for语句属于遍历循环,while语句属于当型循环.除了两个循环语句外,还介绍了break.cont ...

  4. 用python画奥迪标志_不知道不 OK!53 个 Python 经典面试题详解

    作者 | Chris 翻译 | 苏本如,责编 | 夕颜 头图 | CSDN付费下载自视觉中国 出品 | CSDN(ID:CSDNnews) 以下为译文: 本文列出53个Python面试问题,并且提供了 ...

  5. python考试题库开发_算法/开发 面试必看! 【python基础】面试题合集

    本合集整理了计算机专业相关算法/开发面试中遇到的[python基础]相关面试题,后续会持续更新,有需要的小伙伴可以点赞or收藏随时查阅哦!♥ Q:python不可变对象?可变对象?可变对象:list ...

  6. python 进程生命周期_计算客户生命周期价值的python解决方案

    python 进程生命周期 By Lisa Cohen, Zhining Deng, Shijing Fang, and Ron Sielinski 由丽莎·科恩,志宁邓,石井方和罗恩Sielinsk ...

  7. python 找到装饰器_[译] 12步轻松搞定python装饰器

    呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需要你了解一些函数式编程的概念,当然还有理解在python中定义和调用函数 ...

  8. python快速入门系列_十五分钟快速入门系列:Python基础

    Python是一种面向对象的解释型语言, 源码和解释器CPython遵循 GPL协议. 年份 事件 1989 荷兰人Guido van Rossum发明 1991 第一个公开发行版发行 当前版本 3. ...

  9. python生成word图表_工作汇报神技!用Python三步生成带有图表的word报表

    最近在项目中做了一个生成并导出word报表的功能,在这里分享给大家. 经过查看ESPC原有的生成报表代码和网上查阅的一些方法,解决方案的思路如下: 1. 利用pychartdir库生成图表,保存图片, ...

  10. 为什么说python是万能的_为什么说”人生苦短,我用python“

    首先我们要说明的是本文不扯什么大道理,只是先介绍Python的背景,然后从实用的角度出发举一两个真实栗子. 这里写图片描述 首先要想了解要一门语言的好坏,或者为什么招程序员喜欢(卧槽,原来程序员喜欢不 ...

最新文章

  1. Java 设计模式_代理模式(2016-08-19)
  2. Python-EEG工具库MNE中文教程(9)-参考电极应用
  3. python函数调用的例子_实例讲解Python中函数的调用与定义
  4. 毕业设计(论文)-c++小型ftp服务器系统【毕业论文】.doc,毕业设计(论文)-C++小型FTP服务器系统.doc...
  5. Pandas高级教程之:统计方法
  6. oracle+执行变量语句,ORACLE sql 语句的执行过程(SQL性能调整)
  7. NLP—6.数据不平衡处理
  8. ArrayList源码解读(jdk1.8)
  9. 将ListT转化成 DataTable--调整可空类型的转化错误
  10. 顺丰该不该开除删库的运维工程师?
  11. 哈夫曼树中压缩率到底是什么意思
  12. 3D游戏设计读书笔记二
  13. 新浪微博PC客户端(DotNet WinForm C# 版,C#调用新浪微博API代码,源码下载)—— 初探 (第二部分内置链接)
  14. 小红书年货热潮|品牌场景营销新套路
  15. 大厂项目经理分享 Code Review经验
  16. Analog circuit----电路耦合、有源/无源滤波电路
  17. 精读-软件测试的艺术之调试,极限测试和因特尔应用系统的测试
  18. Pacemaker部署
  19. 本地JSON格式化工具下载
  20. 优化二、XWPFDocument 生成doc、docx 功能优化(循环数据可不用手动添加)可在表格中插入图片

热门文章

  1. ETF:一种让美国“80后”趋之若鹜的投资工具
  2. Julia: 趣!,13579分别在一本168页书的页码中出现的次数
  3. Julia: Dict类型 与 Symbol
  4. 阿里云 DNS 运维服务的演进和实践
  5. 浙江大学公共管理学院与阿里云计算有限公司达成合作 | 凌云时刻
  6. 2017中国云计算开源优秀案例
  7. 【优化调度】基于matlab蚁群算法求解无等待流水线调度优化问题【含Matlab源码 1516期】
  8. 【图像去噪】基于matlab GUI DCT图像去噪【含Matlab源码 614期】
  9. 【语音合成】基于matlab比例重叠相加法信号分帧与还原【含Matlab源码 561期】
  10. 模拟tcp_TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?