python3 exception_不要在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抛出了异常,就会出现第三种情形,此时堆栈信息可能会混乱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python清理各种浏览器缓存垃圾有很多方法,这里列举几个常用的方法: 1. 使用第三方库selenium selenium是一个自动化测试工具,可以模拟浏览器的行为,包括清理浏览器缓存。使用selenium需要先安装浏览器驱动,比如ChromeDriver。 以下是一个使用selenium清理Chrome浏览器缓存的示例代码: ```python from selenium import webdriver options = webdriver.ChromeOptions() options.add_argument('--ignore-certificate-errors') options.add_argument("--test-type") options.add_argument("--headless") browser = webdriver.Chrome(chrome_options=options) browser.get('chrome://settings/clearBrowserData') browser.find_element_by_css_selector('* /deep/ #clearBrowsingDataConfirm').click() browser.quit() ``` 2. 使用第三方库pywin32 pywin32是一个Python模块,可以访问Windows API,包括清理临时文件、IE缓存等功能。 以下是一个使用pywin32清理IE缓存的示例代码: ```python import win32api import win32con import win32file path = r'C:\Users\username\AppData\Local\Microsoft\Windows\Temporary Internet Files' win32file.SetFileAttributes(path, win32con.FILE_ATTRIBUTE_NORMAL) win32api.DeleteFile(path + '\*.*', True) ``` 3. 直接删除缓存文件 每种浏览器的缓存文件存储路径不同,可以通过查找浏览器缓存文件路径,然后直接删除缓存文件的方法清理缓存。 以下是一个使用Python删除Chrome浏览器缓存文件的示例代码: ```python import os path = r'C:\Users\username\AppData\Local\Google\Chrome\User Data\Default\Cache' for file_name in os.listdir(path): file_path = os.path.join(path, file_name) try: if os.path.isfile(file_path): os.unlink(file_path) except Exception as e: print(e) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值