4

私たち(または少なくとも私)がこの回答で学んだように、不変の値のみを含む単純なタプルは、参照サイクルに決して関与できないことが判明すると、Pythonのガベージコレクターによって追跡されません。

>>> import gc
>>> x = (1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
False

名前付きフィールドを特徴とする collections モジュールのタプルのサブクラスであるnamedtupleの場合、なぜこれが当てはまらないのでしょうか?

>>> import gc
>>> from collections import namedtuple
>>> foo = namedtuple('foo', ['x', 'y'])
>>> x = foo(1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
True

これを防ぐ実装に固有の何かがありますか、それとも単に見落とされていましたか?

4

2 に答える 2

7

これに関する唯一のコメントgcmodule.cは、Python ソースのファイルにあります。

注:可変オブジェクトの追跡解除について。特定の種類のコンテナーは参照サイクルに参加できないため、ガベージ コレクターによって追跡される必要はありません。これらのオブジェクトの追跡を解除すると、ガベージ コレクションのコストが削減されます。ただし、どのオブジェクトが追跡されない可能性があるかを判断するのは無料ではなく、ガベージ コレクションのメリットとコストを比較検討する必要があります。

コンテナーの追跡をいつ解除するかについては、次の 2 つの戦略が考えられます。

  1. コンテナが作成されたとき。
  2. コンテナーがガベージ コレクターによって検査されるとき。

不変オブジェクトのみを含むタプル (整数、文字列など、および再帰的に不変オブジェクトのタプル) を追跡する必要はありません。インタープリターは多数のタプルを作成しますが、その多くはガベージ コレクションまで存続しません。したがって、作成時に適格なタプルを追跡解除する価値はありません。

代わりに、空のタプルを除くすべてのタプルが作成時に追跡されます。ガベージ コレクション中に、生き残ったタプルを追跡解除できるかどうかが判断されます。タプルのすべてのコンテンツが追跡されていない場合、そのタプルは追跡されない可能性があります。タプルは、すべてのガベージ コレクション サイクルで追跡解除のために検査されます。タプルの追跡を解除するには、1 サイクル以上かかる場合があります。

不変オブジェクトのみを含む辞書も追跡する必要はありません。ディクショナリは、作成時に追跡されません。追跡された項目がディクショナリに (キーまたは値として) 挿入されると、ディクショナリが追跡されます。フル ガベージ コレクション (すべての世代) の間、コレクターは内容が追跡されていないディクショナリを追跡しません。

このモジュールは、オブジェクトの現在の追跡ステータスis_tracked(obj)を返すpython 関数を提供します。その後のガベージ コレクションによって、オブジェクトの追跡ステータスが変更される場合があります。issue で特定のコンテナーの追跡解除が導入され、 issueに応じてアルゴリズムが改良されました。#4688#14775

(リンクされた問題を参照して、追跡を解除できるようにするために導入された実際のコードを確認してください)

このコメントは少しあいまいですが、どのオブジェクトを「追跡解除」するかを選択するアルゴリズムが汎用コンテナに適用されるとは述べていません。これは、コードがtuples ( およびdicts) のみをチェックし、そのサブクラスをチェックしないことを意味します。

これは、ファイルのコードで確認できます。

/* Try to untrack all currently tracked dictionaries */
static void
untrack_dicts(PyGC_Head *head)
{
    PyGC_Head *next, *gc = head->gc.gc_next;
    while (gc != head) {
        PyObject *op = FROM_GC(gc);
        next = gc->gc.gc_next;
        if (PyDict_CheckExact(op))
            _PyDict_MaybeUntrack(op);
        gc = next;
    }
}

PyDict_CheckExact、およびの呼び出しに注意してください。

static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
    PyGC_Head *gc = young->gc.gc_next;

  /* omissis */
            if (PyTuple_CheckExact(op)) {
                _PyTuple_MaybeUntrack(op);
            }

への呼び出しに注意してくださいPyTuple_CheckExact

また、 のサブクラスはtuple不変である必要はないことに注意してください。これは、このメカニズムを外部に拡張しtupleたいdict場合、汎用is_immutable関数が必要になることを意味します。これは、Python のダイナミズムが原因で可能な場合でも、非常tupleにコストがかかります (たとえば、クラスのメソッドは実行時に変更される可能性がありますが、組み込み型であるため、これは不可能です)。したがって、開発者はいくつかの特殊なケースのみに固執することを選択しました。


これは、非常に単純なクラスであるため、特殊なケースs も可能だと思います。たとえば、新しいクラスを作成しているnamedtupleときに呼び出すと、いくつかの問題が発生するため、GC はサブクラスをチェックする必要があります。そして、これは次のようなコードで問題になる可能性があります。namedtuple

class MyTuple(namedtuple('A', 'a b')):
    # whatever code you want
    pass

クラスは不変であるMyTuple必要はないため、GC はクラスが の直接のサブクラスであることを確認してnamedtuple安全を確保する必要があります。ただし、この状況には回避策があると確信しています。

namedtuples は python コアではなく標準ライブラリの一部であるため、おそらくそうではありませんでした。おそらく、開発者はコアを標準ライブラリのモジュールに依存させたくなかったのでしょう。

だから、あなたの質問に答えるには:

  • namedtupleいいえ、それらの実装には、本質的にs の追跡を妨げるものは何もありません
  • いいえ、彼らはこれを「単に見落とした」わけではないと思います。ただし、python 開発者だけが、それらを含めないことを選択した理由について明確な答えを出すことができました。私の推測では、彼らはそれが変更に十分な利益をもたらすとは考えておらず、コアを標準ライブラリに依存させたくなかったのです。
于 2013-11-04T15:50:13.507 に答える