在Python 3中缓存Exception对象会造成什么后果?

目录

​​Python 3有一个不太容易被注意到的改进:异常对象现在有了一个新的属性__traceback__。这个属性自动保存了traceback列表,当每次这个异常被重新raise出来的时候,会自动在__traceback__中追加一条记录。这个功能对于异步编程来说非常有帮助:在另一个线程或者协程中抛出的异常,被捕获、传输到其他地方,再重新抛出来的时候,不仅最初的traceback得以保留,每次被重新抛出的记录也都会保留下来,这样异常的traceback就可以提供很细致的信息。

说完了好处,再来说这个新功能导致的问题:exception对象现在是一个可变的对象了,每次raise都会修改这个对象。如果将一个exception对象抛出多次,就会保留每次抛出的traceback,可能导致的结果包括:错误的堆栈信息;意外地破坏了运行数据;内存泄漏等等。

举例:

堆栈信息错误很好理解,举一个意外破坏运行数据的例子:

import asyncio
import unittest

async def it(raise_, ignore=True):
    yield 1
    if not ignore:
        await asyncio.sleep(0.01)

    try:
        raise ValueError('test2') from raise_
    except Exception:
        import traceback
        traceback.print_exc()
        if ignore:
            pass
        else:
            raise
    await asyncio.sleep(0.1)
    yield 2

class MyTest(unittest.TestCase):
    def test(self):
        try:
            raise ValueError('testerror')
        except ValueError as e:
            exc = e
        async def task1():
            with self.assertRaises(ValueError):
                async for i in it(exc, False):
                    print("task1: ", i)

        async def task2():
            async for i in it(exc, True):
                print("task2: ", i)

        async def main():
            t1 = asyncio.ensure_future(task1())
            t2 = asyncio.ensure_future(task2())
            await t1
            await t2
        asyncio.get_event_loop().run_until_complete(main())

if __name__ == '__main__':
    unittest.main()

运行这段代码,你会很惊讶地发现本来应该很快结束的程序居然卡住了。原因在于assertRaises这个unittest库中的函数,为了在保存单元测试结果的过程中不要占用太多内存,所以在保存异常时强制清空了异常堆栈中的locals变量。然而因为这个异常同时在两个Task中被使用,assertRaises捕获到的异常堆栈中,还有尚未退出的协程的堆栈,清空了这个协程的堆栈会导致这个协程没有办法继续正常执行下去,进一步导致相应的Future没有人设置,等待这个Future的过程就无法正常结束了。

再举一个内存泄漏的例子:这个例子不在用户代码里,而在PyPy3 6.0版本的解释器里。在PyPy3中,为了提高运行速度,关闭一个生成器使用的GeneratorExit对象被解释器缓存了起来,这导致每次调用生成器的close方法时,当前close的生成器的frame都被保存到了这个global对象里,导致了生成器对象和frame对象都无法被GC回收,产生了严重的内存泄漏。

下面这段代码在PyPy 3中会迅速耗尽系统内存,而在CPython 3中则没有问题:

def test():
    yield 1

while True:
    t = test()
    t.close()

结论:

在Python 3中,最佳实践是:

  • 永远不要持久保存一个已经被抛出过的Exception对象
  • 每个被捕获的Exception对象,至多被重新raise一次(不管经过怎样的过程)
  • 在需要将同一个异常广播到多个过程时(例如:多个过程等待了同一个异步过程),最好每次都重新复制整个Exception对象,或者为每个过程创建一个新的Exception对象。通过Python 3的新语法raise ... from ...,可以在新的Exception中保留老的traceback。

第三点的一个特例是asyncio中的Future对象,如果一个Future被await了多次,而这个Future抛出了异常,就会出现第三种情形,此时堆栈信息可能会混乱。

到此这篇关于在Python 3中缓存Exception对象会造成什么后果?的文章就介绍到这了,更多相关不要在Python 3中缓存Exception对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python 获取异常(Exception)信息的几种方法

    异常信息的获取对于程序的调试非常重要,可以有助于快速定位有错误程序语句的位置.下面介绍几种 Python 中获取异常信息的方法,这里获取异常(Exception)信息采用 try-except- 程序结构. 如下所示: try: print(x) except Exception as e: print(e) 1. str(e) 返回字符串类型,只给出异常信息,不包括异常信息的类型,如: try: print(x) except Exception as e: print(str(e)) 打印结

  • 关于Python中异常(Exception)的汇总

    前言 Exception类是常用的异常类,该类包括StandardError,StopIteration, GeneratorExit, Warning等异常类.python中的异常使用继承结构创建,可以在异常处理程序中捕获基类异常,也可以捕获各种子类异常,python中使用try...except语句捕获异常,异常子句定义在try子句后面. Python中的异常处理 异常处理的语句结构 try: <statements> #运行try语句块,并试图捕获异常 except <name1&

  • 在Python 3中缓存Exception对象会造成什么后果?

    目录 ​​Python 3有一个不太容易被注意到的改进:异常对象现在有了一个新的属性__traceback__.这个属性自动保存了traceback列表,当每次这个异常被重新raise出来的时候,会自动在__traceback__中追加一条记录.这个功能对于异步编程来说非常有帮助:在另一个线程或者协程中抛出的异常,被捕获.传输到其他地方,再重新抛出来的时候,不仅最初的traceback得以保留,每次被重新抛出的记录也都会保留下来,这样异常的traceback就可以提供很细致的信息. 说完了好处,

  • python基础中的文件对象详解

    目录 一.python读取和写入文件内容 二.文件对象的写入 三.实现文件内容的拷贝 四.通过文件对象cmd.exe对命令行工具进行复制 总结 一.python读取和写入文件内容 任务:在cmd默认登陆目录中建立一个命名为test.txt的文件并写入内容“welcome python” 打开文件的三个步骤 1.建立文件对象-打开冰箱门 2.读取文件-把大象拿出来 f = open("C:\\Users\\Administrator\\test.txt","rb")#

  • Python的Twisted框架中使用Deferred对象来管理回调函数

    首先抛出我们在讨论使用回调编程时的一些观点: 激活errback是非常重要的.由于errback的功能与except块相同,因此用户需要确保它们的存在.他们并不是可选项,而是必选项. 不在错误的时间点激活回调与在正确的时间点激活回调同等重要.典型的用法是,callback与errback是互斥的即只能运行其中一个. 使用回调函数的代码重构起来有些困难. Deferred Twisted使用Deferred对象来管理回调函数的序列.有些情况下可能要把一系列的函数关联到Deferred对象上,以便在

  • Python Asyncio中Coroutines,Tasks,Future可等待对象的关系及作用

    目录 前记 1.Asyncio的入口 2.两种Coroutine调用方法的区别 3.Task与Future 3.1.Future 3.2.Task 4.总结 前记 上一遍文章<Python中Async语法协程的实现>介绍了Python是如何以生成器来实现协程的以及Python Asyncio通过Future和Task的封装来实现协程的调度,而在Python Asyncio之中Coroutines, Tasks和Future都属于可等待对象,在使用的Asyncio的过程中,经常涉及到三者的转换和

  • Python中的数据对象持久化存储模块pickle的使用示例

    Python中可以使用 pickle 模块将对象转化为文件保存在磁盘上,在需要的时候再读取并还原.具体用法如下: pickle是Python库中常用的序列化工具,可以将内存对象以文本或二进制格式导出为字符串,或者写入文档.后续可以从字符或文档中还原为内存对象.新版本的Python中用c重新实现了一遍,叫cPickle,性能更高. 下面的代码演示了pickle库的常用接口用法,非常简单: import cPickle as pickle # dumps and loads # 将内存对象dump为

  • 浅析Python 中整型对象存储的位置

    在 Python 整型对象所存储的位置是不同的, 有一些是一直存储在某个存储里面, 而其它的, 则在使用时开辟出空间. 说这句话的理由, 可以看看如下代码: a = 5 b = 5 a is b # True a = 500 b = 500 a is b # False 由上面的代码可知, 整型 5 是一直存在的, 而整型 500 不是一直存在的. 那么有哪些整数是一直存储的呢? a, b, c = 0, 0, 0 while a is b: i += 1 a, b = int(str(i)),

  • 全面了解python中的类,对象,方法,属性

    python中一切皆为对象,所谓对象:我自己就是一个对象,我玩的电脑就是对象,坐着的椅子就是对象,家里养的小狗也是一个对象...... 我们通过描述属性(特征)和行为来描述一个对象的.比如家里的小狗,它的颜色,大小,年龄,体重等是它的属性或特征.它会汪汪叫,会摇尾巴等是它的行为. 我们在描述一个真实对象(物体)时包括两个方面: 它可以做什么(行为) 它是什么样的(属性或特征). 在python中,一个对象的特征也称为属性(attribute).它所具有的行为也称为方法(method) 结论:对象

  • JSP中内建exception对象时出现500错误的解决方法

    本文实例讲述了JSP中内建exception对象时出现500错误的解决方法.分享给大家供大家参考,具体如下: 尝试使用JSP的内建exception对象,写了下面三个文件.思路很简单,文件index若提交字串为空,则get抛出异常,交由error.jsp处理.但实际却不能正常运行,会出现IE的500错误页面.环境为Tomcat 5.5,IE6.0. 在sun的论坛上有人贴出了原因,是IE的某个设置.Tomcat5.0以后的版本error page在处理时会返回error code 500.而IE

  • 使用jQuery在对象中缓存选择器的简单方法

    当使用像jQuery这样的库时,开发者通常会使用选择器来访问和操作DOM中的元素.当一个选择在页面上被反复的访问时,把它缓存起来以获得更好的性能是个不错的想法. 让我们看一个例子, jQuery(document).ready(function() { jQuery('#some-selector').on('hover', function() { jQuery(this).fadeOut('slow').delay(400).fadeIn(); console.log(jQuery(this

  • 浅谈Python中的可变对象和不可变对象

    什么是可变/不可变对象 不可变对象,该对象所指向的内存中的值不能被改变.当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址. 可变对象,该对象所指向的内存中的值可以被改变.变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变. Python中,数值类型(int和float).字符串str.元组tuple都是不可变类型.而列表list.字典dict.集合

随机推荐