【问题标题】:Python order of executionPython 执行顺序
【发布时间】:2010-01-29 15:36:28
【问题描述】:

我想知道 Python 在某些代码元素的执行顺序方面是否存在与 C 类似的问题。

例如,我知道在 C 中有时会说不能保证某个变量在另一个变量之前初始化。或者仅仅因为一行代码在另一行之上,不能保证它在它下面的所有代码之前实现。

Python 也一样吗?就像我打开一个数据文件,读入数据,关闭文件,然后做其他事情我是否确定在我关闭文件后的行执行之前文件已关闭??

我问的原因是因为我正在尝试读取一个大数据文件 (1.6GB) 并使用这个 python 模块特定于我对数据所做的工作。当我运行此模块时,我收到以下错误消息:

    File "/glast01/software/ScienceTools/ScienceTools-v9r15p2-SL4/sane/v3r18p1/python/GtApp.py", line 57, in run
    input, output = self.runWithOutput(print_command)
  File "/glast01/software/ScienceTools/ScienceTools-v9r15p2-SL4/sane/v3r18p1/python/GtApp.py", line 77, in runWithOutput
    return os.popen4(self.command(print_command))
  File "/Home/eud/jmcohen/.local/lib/python2.5/os.py", line 690, in popen4
    stdout, stdin = popen2.popen4(cmd, bufsize)
  File "/Home/eud/jmcohen/.local/lib/python2.5/popen2.py", line 199, in popen4
    inst = Popen4(cmd, bufsize)
  File "/Home/eud/jmcohen/.local/lib/python2.5/popen2.py", line 125, in __init__
    self.pid = os.fork()
OSError: [Errno 12] Cannot allocate memory
>>> 
Exception exceptions.AttributeError: AttributeError("Popen4 instance has no attribute 'pid'",) in <bound method Popen4.__del__ of <popen2.Popen4 instance at 0x9ee6fac>> ignored

我假设它与我读入的数据的大小有关(它有 17608310 行和 22 列)。 我想也许如果我在读入数据后立即关闭我打开的文件,这会有所帮助,但它没有。这让我想到了代码行的执行顺序,因此我提出了问题。

谢谢

【问题讨论】:

  • 我认为您需要更好地解释自己。在 C 中,给定 f(); g();f() 保证在 g() 之前被评估。在a = f() + g(); 中,C 中未指定求值顺序。Python 保证从左到右求值:docs.python.org/reference/expressions.html#evaluation-order
  • “问题”是指“规则”,对吧?
  • 是的,我知道我没有很好地解释自己,抱歉,这是因为我没有完全理解这一切,但也在尝试:) Alok:是的,这基本上就是我的想法。我认为它也适用于文件可能关闭的时间。我知道我可以说定义两个不同的函数并从第一个调用第二个(第二个我只是指在代码中较低),所以从这个意义上说,它也不一定按顺序完成。 kevindtimm:是的,我的意思是规则。我想我只是使用了工作问题,因为它给我带来了麻烦:)
  • @Jamie:请不要在您自己的问题上写大量、难以阅读的 cmets。请解决您的问题并删除您的 cmets。问题归您所有,您可以对其进行编辑,直到明确为止。
  • 为什么关闭文件会影响内存消耗?如果您一次将 1.6G 的数据全部读取到您设计的任何内存结构中(据我们所知,这可能是非常不理想的),释放一个小文件句柄不会改变任何事情。您真的必须将整个文件加载到内存中,还是可以逐行处理文件?

标签: python execution


【解决方案1】:

我唯一能想到的可能会让一些人感到惊讶的是:

def test():
    try:
        return True
    finally:
        return False

print test()

输出:

False

finally 子句实际上是最后执行的,即使 return 语句在它们之前也是如此。但是,这并不特定于 Python。

【讨论】:

    【解决方案2】:

    对于实际语句,C 的执行当然是顺序的。甚至还有定义sequence points 的规则,因此您可以了解各个表达式的计算方式。

    【讨论】:

      【解决方案3】:

      CPython 本身的编写方式是,您提到的任何影响都被最小化了;代码始终从上到下执行,除非在编译期间进行文字评估,对象在引用计数达到 0 时立即被 GC,等等。

      【讨论】:

        【解决方案4】:

        cpython vm 中的执行是非常线性的。我不认为你有什么问题与执行顺序有关。

        在 Python 而不是 C 语言中你应该注意的一件事是:异常可以在任何地方引发,所以仅仅因为你在对应的 open() 调用下面看到一个 close() 调用并不意味着调用实际上已经到达。在任何地方使用try/finally(或在足够新的pythons中使用with语句)来确保打开的文件被关闭(以及其他可以显式释放的资源被释放)。

        如果您的问题是内存使用问题,而不是其他类型的资源,则调试可能会更困难。在 python 中不能显式释放内存。 cpython vm(您最有可能使用它)确实会在对它的最后一个引用消失后立即释放内存,但有时无法释放被困在具有__del__ 方法的对象的循环中的内存。如果您有任何自己的 __del__ 方法或使用具有这些方法的类,这可能是您的问题的一部分。

        但是,如果不查看更多代码,您的实际问题(内存问题,而不是执行顺序问题)很难回答。这可能是显而易见的事情(或者至少有一些明显的方法可以减少您需要的内存量)。

        【讨论】:

          【解决方案5】:

          “如果我打开一个数据文件,读入数据,关闭文件,然后做其他事情,我是否确定在我关闭文件后的行执行之前文件已关闭??”

          关闭是的。

          从记忆中释放。不。不能保证何时会发生垃圾回收。

          此外,关闭文件并没有说明您创建的所有其他变量以及您留下的附加到这些变量的其他对象。

          不存在“操作顺序”问题。

          我敢打赌,你有太多的全局变量和太多的数据副本。

          【讨论】:

            【解决方案6】:

            如果数据由列和行组成,为什么不使用内置的文件迭代器一次获取一行?

            f = open('file.txt')
            first_line = f.next()
            

            【讨论】:

              【解决方案7】:

              popen2.py:

              class Popen4(Popen3):
                  childerr = None
              
                  def __init__(self, cmd, bufsize=-1):
                      _cleanup()
                      self.cmd = cmd
                      p2cread, p2cwrite = os.pipe()
                      c2pread, c2pwrite = os.pipe()
                      self.pid = os.fork()
                      if self.pid == 0:
                          # Child
                          os.dup2(p2cread, 0)
                          os.dup2(c2pwrite, 1)
                          os.dup2(c2pwrite, 2)
                          self._run_child(cmd)
                      os.close(p2cread)
                      self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
                      os.close(c2pwrite)
                      self.fromchild = os.fdopen(c2pread, 'r', bufsize)
              

              man 2 fork:

              fork() 函数可能会在以下情况下失败:

              [ENOMEM]
              可用存储空间不足。

              os.popen4 最终调用open2.Popen4.__init__,它必须fork 才能创建您尝试读取/写入的子进程。此基础调用失败,可能是由于资源耗尽。

              您可能在其他地方使用了过多的内存,导致 fork 尝试使用超过为您的用户提供的 RLIMIT_DATA 或 RLIMIT_RSS 限制。根据Python memory profiler - Stack Overflow 的建议,Heapy 可以帮助您确定是否是这种情况。

              【讨论】:

                猜你喜欢
                • 2019-09-11
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-05-17
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多