如何在类中包装不安全的python方法(例如os.chdir)使其线程/异常安全?

时间:2009-11-10 17:35:57

标签: python exception-handling

在问题How do I "cd" in python中,接受的答案建议在类中包装os.chdir调用以使返回原始目录异常安全。以下是推荐的代码:

class Chdir:         
  def __init__( self, newPath ):  
    self.savedPath = os.getcwd()
    os.chdir(newPath)

  def __del__( self ):
    os.chdir( self.savedPath )

有人可以详细说明这是如何使不安全的呼叫异常安全的吗?

4 个答案:

答案 0 :(得分:7)

线程安全和异常安全根本不是一回事。将os.chdir调用包含在这样的类中是为了使其异常安全非线程安全

异常安全是您经常听到的C ++开发人员所谈论的内容。在Python社区中几乎没有谈到它。来自Boost的Exception-Safety in Generic Components文档:

  

非正式地,例外安全   组件意味着它展示   异常时的合理行为   在执行期间抛出。对于   大多数人,“合理”一词   包括所有通常的期望   用于错误处理:该资源   不应该泄露,那个   程序应保留在   定义良好的状态,以便执行   可以继续。

因此,您提供的代码段中的想法是确保在异常的情况下,程序将返回到定义良好的状态。在这种情况下,进程将在它开始的目录中返回,无论os.chdir本身是否失败,或者是什么导致抛出异常并删除“Chdir”实例。

这种使用仅用于清理的对象的模式是“Resource Acquisition Is Initialization”或“RAII”的形式。这种技术在C ++中非常流行,但在Python中并不那么受欢迎,原因如下:

  • Python有try ... finally,它的用途几乎相同,是Python中比较常见的习惯用法。
  • Python中的析构函数(__del__)在某些实现中不可靠/不可预测,因此以某种方式使用它们有点不鼓励。在cpython中,只要不涉及循环(即:当通过引用计数处理删除时),它们恰好是非常可靠和可预测的,但在其他实现中(Jython和我相信也是IronPython)当垃圾收集器到达时会发生删除它,可能会更晚。 (有趣的是,这并不能阻止大多数Python程序员依赖__del__来关闭他们打开的文件。)
  • Python有垃圾收集,所以你不需要像在C ++中那样小心清理。 (我并不是说你根本不必小心,只是在常见情况下你可以依靠gc为你做正确的事。)

编写上述代码的更“pythonic”方式是:

saved_path = os.getcwd()
os.chdir(new_path)
try:
    # code that does stuff in new_path goes here
finally:
    os.chdir(saved_path)

答案 1 :(得分:5)

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

以下内容可能是合理的,使其“异常安全”(但更好的是避免使用chdir并使用完整路径):

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

这种精确的行为也可以写入上下文管理器。

答案 2 :(得分: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__()

也就是说,除非我遗漏了某些东西。

(顺便说一句,你不能使用contextmanager做同样的事情吗?)

答案 3 :(得分:-2)

仅此代码既不是线程安全的,也不是异常安全的。实际上我并不确定你的例外是什么意思。以下代码浮现在脑海:

try:
    # something thrilling
except:
    pass

这是一个糟糕的主意。例外不是为了防范。编写良好的代码应该catch exceptions并对它们做一些有用的事情。