3

注意:如果您知道任何(非详细)库代码可以满足我的要求,请启发 C/C++ 程序员,我会接受它作为答案。

我有一个全局变量设置为以下类的实例。其目的是允许我设置一些手动中断点,以便printf在 scrapy 蜘蛛中放置一些快速而肮脏的调试点(我特别需要在满足某些条件来调整解析器时中断,有一些极其罕见的输入数据异常) ——改编自此

操作系统是 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

编辑:

Delian 对 seth 的回答的评论让我明白我需要使用类似 Cmain()的函数,或任何其他作为根函数占主导地位的函数/生成器并在那里初始化上下文。这样,当进程停止__exit__时,上下文管理器的方法将被调用。而且我不必在每次wait()通话时重新编程终端流。

尽管重新编程的成本可能无关紧要,但很高兴知道如何在 python 中使用这些基本的 C/C++ 语义。

编辑2:

与标准输入混淆时,Twisted(scrapy 使用)会变得很糟糕。所以我不得不解决文件IO的问题。

4

2 回答 2

6

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

AFAIK 任何类实例都应该在导入代码之前被销毁,不是吗?以声明/定义的相反顺序。

那是C++。忘了它。Python 不关心这一点,事实上它甚至不关心大多数需要做的事情。整个 Python 语言中没有声明之类的东西,模块级变量存储在本质上是一个无序关联数组中。变量不存储对象,它们存储引用(不是C++ 引用,它们基本上是没有指针算术的指针)——对象在堆上,不知道关于变量、绑定、语句或语句顺序的事情.

此外,当一个对象被垃圾收集时,它是否完全被 gc'd是未定义的。由于引用计数,您在 CPython 中获得了大部分确定性的图片,但即使在您有周期的那一秒,它也会下降。结果是它__del__可能在任何时间点被调用(包括当模块的一半已经被拆除时)或根本不被调用。定义__del__相互引用的多个对象也很麻烦,尽管一些 GC 努力做正确的事情。

底线是,你可以在__del__运行时假设很少,所以你不能做很多。您可以最后一次处理本应通过另一种方法清理但没有清理的资源,仅此而已。经验法则:永远不要依赖它来做任何强制性的事情。

相反,创建一个上下文管理器并通过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方法成为免费功能。

于 2012-08-10T01:21:19.207 回答
4

如果__del__被调用,它会在对象的引用计数为零之后的某个时间发生,并且可能直到程序结束时才会发生,并且不会以任何特定的顺序发生。您也不能依赖__del__.

termios在您的情况下,python在调用之前清理了对模块的引用DebugWaitKeyPress.__del__。这就是你收到'NoneType' object has no attribute 'tcsetattr'消息的原因。termiosNone在您尝试使用它的时候。

我猜你最好实现一个上下文管理器,然后把你的__del__代码放在__exit__.

然后你就可以这样说:

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

object.__del__文档

由于调用 __del__() 方法的不稳定环境,在执行期间发生的异常将被忽略,而是向 sys.stderr 打印警告。此外,当 __del__() 被调用以响应模块被删除时(例如,当程序执行完成时),__del__() 方法引用的其他全局变量可能已经被删除或正在被拆除(例如进口机器关闭)。出于这个原因, __del__() 方法应该做维护外部不变量所需的绝对最小值。从 1.5 版开始,Python 保证名称以下划线开头的全局变量会在其他全局变量被删除之前从其模块中删除;如果不存在对此类全局变量的其他引用,

于 2012-08-10T01:09:50.537 回答