2012-08-10 76 views
3

注意:如果您知道我想要的任何(非精细)库代码,请启发C/C++程序员,我将接受该答案作为答案。Python对象生命周期特征

我有一个全局变量设置为以下类的一个实例。它的目的是允许我设置一些手动中断点,以便在scrapy蜘蛛中放置一些快速且脏的样式调试点(当特定标准符合调谐解析器时,我特别需要中断,还有一些非常罕见的输入数据异常) - 改编自this

Os是OS X 10.8。

import termios, fcntl, sys, os 

class DebugWaitKeypress(object): 
    def __init__(self): 
     self.fd = sys.stdin.fileno() 
     self.oldterm = termios.tcgetattr(self.fd) 
     self.newattr = termios.tcgetattr(self.fd) 
     self.newattr[3] = self.newattr[3] & ~termios.ICANON & ~termios.ECHO 
     termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr) 

     self.oldflags = fcntl.fcntl(self.fd, fcntl.F_GETFL) 
     fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK) 

    def wait(self): 
     sys.stdin.read(1) 

    def __del__(self): 
     print "called del" 
     termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.oldterm) 
     fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags) 

当我按下Ctrl-C和过程展开我得到以下异常:

Exception AttributeError: "'NoneType' object has no attribute 'tcsetattr'" in <bound method DebugWaitKeypress.__del__ of <hon.spiders.custom_debug.DebugWaitKeypress object at 0x108985e50>> ignored 

我失去了一些东西有关对象的寿命我猜的机制?如何补救这种情况。 AFAIK任何类实例应该在导入的代码之前销毁,否?按照声明/定义的相反顺序。

我会忽略这一点,如果在进程退出后的终端并没有搞砸了:d

编辑:

提洛对Seth的回答评论使我明白,我需要使用类似于函数,或者作为根函数支配并在那里初始化上下文的任何其他函数/生成器。这种方式当进程正在关闭时,上下文管理器的__exit__方法将被调用。我不需要在每个wait()调用上重新编程终端流。

尽管重新编程的代价可能并不重要,但知道Python中这些基本的C/C++语义如何是很好的。

编辑2:用标准输入搞乱当

扭曲(其scrapy用途)变为apeshit。所以我必须用文件IO来解决问题。

回答

5

长话短说:__del__是无用的这个目的(和几乎任何其他目的;你应该忘记它的存在)。如果您需要确定性清理,请使用上下文管理器。

AFAIK任何类实例应该在导入的代码之前销毁,否?按照声明/定义的相反顺序。

这就是C++。算了吧。 Python并不关心这一点,实际上它甚至不关心大多数需要这样做的事情。在整个Python语言中没有这样的声明,模块级变量存储在本质上是无序的关联数组中。变量不存储对象,它们存储引用(它们是而不是 C++引用,它们基本上是指针,没有指针算术) - 对象位于堆上,不知道变量,绑定,语句或顺序的陈述。

此外,当一个对象被垃圾收集,并且不管它是否全部为,都是未定义的。由于引用计数,你会在CPython中获得大多数为确定性图片,但即使在那里,它也会在你有周期的秒钟内下降。其结果是__del__可能会在任何时间点(包括模块的一半已被拆除)或根本不会被调用。定义__del__引用对象的多个对象也很麻烦,尽管有些GC试图做正确的事情。底线是,您可以在__del__运行时承担很少的工作,所以您不能做太多。你可以通过另一种方法获得应该清理的资源,但不是这样的资源。经验法则:从不依靠它为任何东西强制性。

取而代之,创建一个context manager and use it via with。你得到确定性的清理,而不用担心对象的生命周期。因为,被告知的事实,对象生命周期和资源生命周期是两个完全不同的事情,并且只是纠缠在C++中,因为它是在该环境中执行资源管理的最佳方式。在Python,RAII不适,相反,我们有这样的:

with <context manager> as var: 
    # do something 
# "context closed", whatever that means - for resources, usually cleanup 

顺便说一句,你可以远远通过contextlib更方便地定义它(从您的版本很快音译,可能包含错误或丑陋):

from contextlib import contextmanager 


@contextmanager 
def debug_wait_keypress(): 
    fd = sys.stdin.fileno() 
    oldterm = termios.tcgetattr(fd) 
    newattr = termios.tcgetattr(fd) 
    newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO 
    termios.tcsetattr(fd, termios.TCSANOW, newattr) 
    oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) 
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) 
    try: 
     yield 
    finally: 
     termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) 
     fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) 

您的wait方法成为一项免费功能。

3

如果__del__被调用,它发生一段时间后该对象的引用计数为零,可能直到节目结束,而不是在任何特定的顺序。你也不能依靠任何外部(特别是全局变量)在__del__中可用。

在你的情况下,python在调用DebugWaitKeyPress.__del__之前清除了对termios模块的引用。这就是为什么你得到'NoneType' object has no attribute 'tcsetattr'消息。 termiosNone到您尝试使用它。

我想你会更好地执行context manager,并把你的__del__代码__exit__

然后你就可以说是这样的:

with DebugWaitKeypress(...) as thing: 
    do_something_with_it(thing) 
# here, __exit__() is called to do cleanup 

object.__del__ docs

由于被 调用其下__del __()方法岌岌可危的情况下,期间发生的异常他们的执行被忽略,并且 改为向sys.stderr发送警告。另外,当__del __()为 以响应模块被删除而调用时(例如,当程序完成时执行 )时,由__del __()方法 引用的其他全局变量可能已经被删除或正在被删除拆除 (例如进口机械关闭)。出于这个原因,__del __() 方法应该尽最大可能保持外部不变量。从版本1.5开始,Python保证在删除其他全局变量之前,从其 模块中删除名称以单个下划线开头的全局变量 ;如果不存在其他对 这样的全局变量的引用,这可能有助于确保导入的模块 在__del __()方法被调用时仍然可用。

+0

嗯有没有其他的上下文管理器可能的结构?该文档似乎表明该构造可用于语句块。这意味着每次我想要打断点时,我必须重新配置终端两次。 – 2012-08-10 01:30:30

+0

@HassanSyed你似乎低估了它。作为*任何*(甚至friggin的导入和类定义,尽管你很难找到一个好的用例)可以进入上下文管理器,例如,你可以将调用包装到你的'main'函数中一个,它会在发生任何事情之前配置终端,并且仅当'main'离开时重置配置(通常由于异常导致*或*)。就像你可以在C++的'main'中的堆栈上创建'DebugWaitKeypress'一样。 – delnan 2012-08-10 01:33:10

+0

嗯,这是我认为我需要的洞察力。我可以在作为生成器实现的scrapy的'parse'方法中初始化一个全局类,我在生成器的顶部启动上下文,然后当我用完url时返回生成器超出范围,它在scrapy之前开始关闭。 – 2012-08-10 01:42:48