27

入ってくるasyncioパッケージのソースコードを読んでいます。self = Noneメソッドの最後にステートメントがあることに注意してください。それは何をするためのものか?

def _run(self):
    try:
        self._callback(*self._args)
    except Exception as exc:
        msg = 'Exception in callback {}{!r}'.format(self._callback,
                                                    self._args)
        self._loop.call_exception_handler({
            'message': msg,
            'exception': exc,
            'handle': self,
        })
    self = None  # Needed to break cycles when an exception occurs.

インスタンスが消去されると思いましたが、次のテストではそうではありません。

class K:
    def haha(self):
        self = None

a = K()
a.haha()
print(a) # a is still an instance
4

2 に答える 2

27

へのローカル参照を単純にクリアしself、例外が発生した場合に渡された参照がself._loop.call_exception_handler()唯一の残りの参照であり、循環が作成されていないことを確認します。

ローカル名前空間は例外トレースバックによって参照されるため、これはここでも必要です。まだ生きているローカルへの参照があるため、関数が終了してもクリアされません。

これは、sys.exc_info()関数のドキュメントに警告とともに記載されています。

警告:例外を処理している関数内のローカル変数にトレースバックの戻り値を代入すると、循環参照が発生します。これにより、同じ関数内のローカル変数またはトレースバックによって参照されるものがガベージ コレクションされるのを防ぐことができます。ほとんどの関数はトレースバックにアクセスする必要がないため、最善の解決策はexctype, value = sys.exc_info()[:2]、例外の型と値のみを抽出するようなものを使用することです。トレースバックが必要な場合は、使用後に必ず削除するか (try ... finallyステートメントで行うのが最適です) exc_info()、例外を処理しない関数を呼び出すようにしてください。

ハンドラーは基本的なフレームワーク クラスを形成するため、コードは代わりにローカル名前空間からtulip削除することでトレースバックの循環参照ケースを処理します。self_callbackcall_exception_handler

CPython では、参照カウントが 0 になるとオブジェクトは破棄されますが、循環参照 (サイクル内で自分自身を参照する一連のオブジェクト) では参照カウントが 0 になることはありません。ガベージ コレクターはそのような循環を破ろうとしますが、常にこれを行うことができないか、十分に速くありません。参照を明示的にクリアすると、サイクルの作成が回避されます。

たとえば、__del__メソッドがある場合、ガベージ コレクターはサイクルを安全に中断する順序がわからないため、サイクルを中断しません。

メソッドが存在しない場合でも__del__(フレームワーク クラスは絶対にそうではないと想定すべきです)、最終的にサイクルをクリアするガベージ コレクターに依存しないことが最善です。

于 2014-03-07T08:27:45.050 に答える
1

この行は、Guido によってリビジョン 496で導入されていることに注意してください。

このリビジョンでは、対応する関数_runrunです:

def run(self):
    try:
        self._callback(*self._args)
    except Exception:
        tulip_log.exception('Exception in callback %s %r',
                            self._callback, self._args)
    self = None  # Needed to break cycles when an exception occurs.

tulip_logは単なる通常のロガーです: logging.getLogger("tulip").

内部ではinLogger.exceptionの結果を保存しますが、レコード オブジェクトは呼び出し後に保持されません。sys.exc_info()LogRecordexception

logging.exceptionそれが参照サイクルを引き起こさないことを確認するために、次の実験を行いました。

import time

import logging

class T:
    def __del__(self):
        print('T.__del__ called')

    def test(self):
        try:
            1 / 0
        except Exception:
            logging.exception("Testing")


def run():
    t = T()
    t.test()
    # t is supposed to be garbaged collected


run()

time.sleep(10) # to emulate a long running process

結果は次のとおりです。

$ python test.py 
ERROR:root:Testing
Traceback (most recent call last):
  File "test.py", line 11, in test
    1 / 0
ZeroDivisionError: integer division or modulo by zero
T.__del__ called

オブジェクトtは期待どおりにガベージ コレクションされます。

self = Noneしたがって、ここでは代入は必要ないと思います。

于 2014-03-08T10:00:26.103 に答える