【问题标题】:How does wrapping an unsafe python method (e.g os.chdir) in a class make it thread/exception safe?在类中包装不安全的 python 方法(例如 os.chdir)如何使其线程/异常安全?
【发布时间】:2009-11-10 17:35:56
【问题描述】:

在问题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 )

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

【问题讨论】:

    标签: python exception-handling


    【解决方案1】:

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

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

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

    所以你提供的代码 sn-p 的想法是确保在出现异常的情况下,程序将返回到一个明确定义的状态。在这种情况下,无论 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)
    

    【讨论】:

    • 最好使用 with 语句(Python 2.5+)
    【解决方案2】:

    这个问题的直接答案是:它没有,发布的代码太可怕了。

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

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

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

    【讨论】:

    • os.chdir(newPath) 可能不应该出现在try 语句中 - 如果它失败,则当前工作目录没有更改,因此无需重置它。 (在这种情况下,它是无害的,因为 os.chdir(os.getcwd()) 是无操作的,但如果 saved_path 是相对的,这将是一个麻烦)
    【解决方案3】:

    __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 做同样的事情吗?)

    【讨论】:

      【解决方案4】:

      仅此代码既不是线程安全的,也不是异常安全的。实际上,我不确定您所说的异常安全是什么意思。想到以下代码:

      try:
          # something thrilling
      except:
          pass
      

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

      【讨论】:

      • 我认为“异常安全”他的意思是:“即使由于引发异常而失去控制,我们也保证返回到原始目录”。
      • 你说它不是异常安全的,然后承认你不知道异常安全是什么意思,这有点好笑。
      • 不管异常安全是什么意思;您能说“仅此代码是异常安全的”吗?通过代码,我的意思是问题中的 Chdir 类。
      • Shylent - 没错。这就是我所说的“异常安全”。
      猜你喜欢
      • 1970-01-01
      • 2012-11-07
      • 2016-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-06
      • 1970-01-01
      相关资源
      最近更新 更多