将不安全的Python方法(如os.chdir)封装在类中如何使其线程/错误安全?
在这个问题 如何在Python中使用"cd" 中,大家普遍接受的答案建议把os.chdir这个调用放在一个类里,这样可以确保在出错时能安全地返回到原来的目录。下面是推荐的代码:
class Chdir:
def __init__( self, newPath ):
self.savedPath = os.getcwd()
os.chdir(newPath)
def __del__( self ):
os.chdir( self.savedPath )
有人能详细解释一下这个方法是怎么让不安全的调用变得安全的吗?
4 个回答
__del__
是在实例快要被销毁时被调用的。所以当你创建这个类的实例时,当前的工作目录会被保存到实例的一个属性里,然后调用了 os.chdir 来改变目录。当这个实例被销毁(不管是什么原因),当前目录会被改回之前的值。
我觉得这看起来有点不太对。根据我的理解,你在重写的 __del__
方法里必须调用父类的 __del__
,所以应该更像这样:
class Chdir(object):
def __init__(self, new_path):
self.saved_path = os.getcwd()
os.chdir(new_path)
def __del__(self):
os.chdir(self.saved_path)
super(Chdir, self).__del__()
当然,前提是我没有漏掉什么。
(顺便问一下,难道你不能用上下文管理器来做到同样的事情吗?)
直接回答这个问题就是:它并没有,发布的代码很糟糕。
像下面这样的代码可能会让它变得“安全一些”(但更好的做法是避免使用chdir,直接用完整路径会更好):
saved_path = os.getcwd()
try:
os.chdir(newPath)
do_work()
finally:
os.chdir(saved_path)
而这种具体的行为也可以写成一个上下文管理器。
线程安全和异常安全其实是两回事。把 os.chdir
这个调用放在一个类里,是为了让它在出现异常时更安全,但这并不意味着它是线程安全的。
异常安全这个概念,C++开发者经常提到,但在Python社区里讨论得就没那么多了。根据Boost的《通用组件中的异常安全性》文档,异常安全的意思是:
简单来说,组件的异常安全性意味着当执行过程中抛出异常时,它能表现出合理的行为。对大多数人来说,“合理”包括了处理错误时的常见期望:资源不应该泄露,程序应该保持在一个明确的状态,以便可以继续执行。
所以你提供的代码片段的想法是,确保在出现异常的情况下,程序能够回到一个明确的状态。在这个例子中,无论 os.chdir
本身是否失败,或者其他原因导致异常被抛出并删除了“Chdir”实例,程序都会回到它开始时的目录。
使用一个仅仅存在于清理过程中的对象,这种模式叫做“资源获取即初始化”,简称“RAII”。这个技巧在C++中非常流行,但在Python中不太常见,原因有几个:
- Python有
try
...finally
,这个结构几乎可以实现同样的目的,而且在Python中更常用。 - Python中的析构函数(
__del__
)在某些实现中不太可靠,所以不太建议这样使用。在CPython中,只要没有循环引用,它们是非常可靠和可预测的,但在其他实现(比如Jython和我认为的IronPython)中,删除操作是在垃圾回收器处理时进行的,这可能会晚很多。(有趣的是,这并没有阻止大多数Python程序员依赖__del__
来关闭他们打开的文件。) - Python有垃圾回收机制,所以在清理方面不需要像C++那样小心。(我并不是说你完全不需要小心,只是说在常见情况下,你可以依赖垃圾回收器为你处理好这些事情。)
用更“Pythonic”的方式来写上面的代码可以是:
saved_path = os.getcwd()
os.chdir(new_path)
try:
# code that does stuff in new_path goes here
finally:
os.chdir(saved_path)