web-dev-qa-db-ja.com

「[]が[]」で「{}が{}」の場合に「()is()」がTrueを返すのはなぜですか?

私が知っていることから、_[], {}_または_()_を使用してオブジェクトをインスタンス化すると、それぞれ_list, dict_またはTupleの新しいインスタンスが返されます。 新しいアイデンティティを持つ新しいインスタンスオブジェクト。

実際にテストしてみると、これは私にはかなりはっきりしていて、_() is ()_が実際にTrueの代わりにFalseを返すことがわかりました。

_>>> () is (), [] is [], {} is {}
(True, False, False)
_

予想どおり、この動作は list()dict() および Tuple() それぞれ:

_>>> Tuple() is Tuple(), list() is list(), dict() is dict()
(True, False, False)
_

Tuple() =のドキュメントで見つけることができる唯一の関連情報:

[...]たとえば、Tuple('abc')は_('a', 'b', 'c')_を返し、Tuple([1, 2, 3])は_(1, 2, 3)_を返します。 引数が指定されていない場合、コンストラクターは新しい空のタプル_()_。を作成します

これは私の質問に答えるには十分ではありません。

では、なぜ空のタプルは同じアイデンティティを持っているのに、リストや辞書のような他のタプルはそうでないのですか?

要するに:

Pythonは、最初の要素に空のタプルを含むタプルオブジェクトのCリストを内部的に作成します。 Tuple()または_()_を使用するたびに、Pythonは前述のCリストに含まれている既存のオブジェクトを返し、新しいオブジェクトを作成しません。

そのようなメカニズムは、逆に、毎回最初から再作成されるdictまたはlistオブジェクトには存在しません。

これは、(タプルなどの)不変オブジェクトが変更できないため、実行中に変更されないことが保証されているという事実に関連している可能性が高いです。 frozenset() is frozenset()Trueを返すことを考慮すると、これはさらに固まります。 _()_のように空のfrozensetCPythonの実装ではシングルトンと見なされます 。変更可能なオブジェクトでは、そのような保証は適切ではありません。したがって、ゼロ要素インスタンスをキャッシュするインセンティブはありません(つまり、IDが同じでコンテンツが変更される可能性があります)。

注意してください:これは依存すべきものではありません。つまり、空のタプルをシングルトンと見なすべきではありません。ドキュメントではそのような保証は明示的に行われていないため、実装に依存していると想定する必要があります。


それが行われる方法:

最も一般的なケースでは、CPythonの実装は、2つのマクロ _PyTuple_MAXFREELIST_ および _PyTuple_MAXSAVESIZE_ を正の整数に設定してコンパイルされます。これらのマクロの正の値により、サイズ_PyTuple_MAXSAVESIZE_の Tupleオブジェクトの配列 が作成されます。

_PyTuple_New_がパラメーター_size == 0_で呼び出されると、リストにまだ存在しない場合は、必ずリストに 新しい空のタプルを追加 します。

_if (size == 0) {
    free_list[0] = op;
    ++numfree[0];
    Py_INCREF(op);          /* extra INCREF so that this is never freed */
}
_

次に、新しい空のタプルが要求された場合、 このリストの最初の位置 にあるタプルが、新しいインスタンスの代わりに返されます。

_if (size == 0 && free_list[0]) {
    op = free_list[0];
    Py_INCREF(op);
    /* rest snipped for brevity.. */
_

これをインセンティブにするもう1つの理由は、関数呼び出しが、使用される位置引数を保持するためのタプルを構成するという事実です。これは、_load_args_の _ceval.c_ 関数で確認できます。

_static PyObject *
load_args(PyObject ***pp_stack, int na)
{
    PyObject *args = PyTuple_New(na);
    /* rest snipped for brevity.. */
_

同じファイルで _do_call_ によって呼び出されます。引数の数naがゼロの場合、空のタプルが返されます。

本質的に、これは頻繁に実行される操作である可能性があるため、空のタプルを毎回再構築しないことが理にかなっています。


参考文献:

さらにいくつかの回答が、CPythonのイミュータブルを使用したキャッシング動作を明らかにします。

  • 整数の場合、ソースを掘り下げる別の答えが見つかります here
  • 文字列の場合、いくつかの回答が herehere および here で見つかります。