将不安全的Python方法(如os.chdir)封装在类中如何使其线程/错误安全?

5 投票
4 回答
4607 浏览
提问于 2025-04-15 15:52

在这个问题 如何在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 个回答

1

__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__()

当然,前提是我没有漏掉什么。

(顺便问一下,难道你不能用上下文管理器来做到同样的事情吗?)

5

直接回答这个问题就是:它并没有,发布的代码很糟糕。

像下面这样的代码可能会让它变得“安全一些”(但更好的做法是避免使用chdir,直接用完整路径会更好):

  saved_path = os.getcwd()
  try:
    os.chdir(newPath)
    do_work()
  finally:
    os.chdir(saved_path)

而这种具体的行为也可以写成一个上下文管理器。

8

线程安全和异常安全其实是两回事。把 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)

撰写回答