3

注:私が望むことを行う(精巧ではない)ライブラリコードを知っている場合は、C / C ++プログラマーを啓発してください。それを答えとして受け入れます。

次のクラスのインスタンスに設定されたグローバル変数があります。その目的は、いくつかの手動中断ポイントを設定printfして、スクレイピースパイダーにいくつかの迅速で汚いスタイルのデバッグポイントを配置できるようにすることです (特に、パーサーを調整するために特定の基準が満たされたときに中断する必要があります。非常にまれな入力データの異常があります)。 -これから適応。

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

私が推測するオブジェクトの寿命の仕組みについて何かが欠けていますか? 状況をどのように改善しますか。私の知る限り、インポートされたコードが破棄される前にクラスインスタンスを破棄する必要がありますか? 宣言/定義の逆順。

プロセスが終了した後に端末が台無しにならなかった場合、私はこれを無視します:D

編集:

セスの答えに対するデリアンのコメントにより、Cmain()のような関数、またはルート関数として支配し、そこでコンテキストを初期化する他の関数/ジェネレーターを使用する必要があることがわかりました。このように、プロセスがダウンする__exit__と、コンテキストマネージャーのメソッドが呼び出されます。wait()また、呼び出しごとに端末ストリームを再プログラムする必要はありません。

再プログラミングのコストは潜在的に重要ではありませんが、Python でこれらの重要な C/C++ セマンティクスをどのように使用するかを知っておくとよいでしょう。

編集2:

Twisted (scrapy が使用する) は、stdin をいじると大騒ぎします。そのため、ファイル IO の問題を解決する必要がありました。

4

2 に答える 2

6

簡単__del__に言うと、この目的には役に立ちません(そして、他のほとんどの目的には役に立ちません。おそらく、それが存在することを忘れるべきです)。確定的なクリーンアップが必要な場合は、コンテキストマネージャーを使用してください。

インポートしたコードを破棄する前に、クラスインスタンスを破棄する必要があります。宣言/定義の逆順。

それはC++です。忘れてください。Pythonはこれを気にしません。実際、Pythonはそれを行うための要件であるほとんどのことを気にしません。Python言語全体での宣言のようなものはなく、モジュールレベルの変数は、本質的に順序付けられていない連想配列に格納されます。変数はオブジェクトを格納せず、参照を格納します(C ++参照ではなく、基本的にポインター演算のないポインターです)-オブジェクトはヒープ上にあり、変数、バインディング、ステートメント、またはステートメントの順序については何も知りません。 。

さらに、オブジェクトがガベージコレクションされる場合、およびオブジェクトがgcされるかどうかは未定義です。参照カウントにより、CPythonでほとんど決定論的な画像が得られますが、それでも、サイクルがあるとすぐに落ちてしまいます。その結果__del__、いつでも呼び出される可能性があり(モジュールの半分がすでに破棄されている場合を含む)、まったく呼び出されない可能性があります。__del__相互参照を定義する複数のオブジェクトも問題ですが、一部のGCは正しいことを実行しようと努力します。

結論としては、実行時にほとんど想定__del__できないため、多くのことを行うことはできません。別の方法でクリーンアップする必要があったが、クリーンアップされなかったリソースを破棄する最後のショットを取得します。これでほぼ完了です。経験則:必須事項については、絶対に信頼しないでください。

代わりに、コンテキストマネージャーを作成し、を介して使用しwithます。オブジェクトの存続期間を気にすることなく、決定論的なクリーンアップを取得できます。なぜなら、言うまでもなく、オブジェクトの存続期間とリソースの存続期間は2つのまったく異なるものであり、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__、オブジェクトの参照カウントが 0 になった後、おそらくプログラムが終了するまで発生せず、特定の順序でも発生しません。で利用可能な外部 (特にグローバル) に依存することもできません__del__

あなたの場合、python はtermiosモジュールへの参照を呼び出す前にクリーンアップしましたDebugWaitKeyPress.__del__。だからこそ、あなたは'NoneType' object has no attribute 'tcsetattr'メッセージを受け取っています。termiosあなたがNoneそれを使おうとする時までです。

コンテキスト マネージャーを実装して、__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 に答える