{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(ch:immutable)=\n", "# 可変と不変" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pythonのオブジェクトには不変な(immutable)ものと、可変な(mutable)なものがある。\n", "\n", "+ 不変: 整数、浮動小数点数、文字列、タプルなど\n", "+ 可変: リスト、辞書、集合など" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 不変なオブジェクト" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "整数値が不変とはどういうことか? 実際に変数`x`に整数値`1`を代入して、変数`x`が参照しているオブジェクトの識別値を`id`関数で調べてみる。なお、オブジェクトの識別値は、(Pythonインタプリタの実装にもよるが)オブジェクトが格納されているメモリ空間上のアドレスに相当する。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "x = 1" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549744" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "続いて、変数`x`に1を加算すると、変数`x`の識別値も変化する。" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x += 1\n", "x" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549776" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2つのオブジェクトに異なる識別値が割り当てられた場合、それらのオブジェクトの実体(メモリ空間上で格納されている場所)は異なることを意味する。先ほどのコードでは、変数`x`が参照しているオブジェクトの値(`1`)が変更されたのではなく、変数`x`の参照先が整数`2`を表すオブジェクトに変更されたことを表している。このように、不変なオブジェクトを参照している変数の値を変更する時は、オブジェクトの内容を変更するのではなく、その変数の参照先が更新後の値を表すオブジェクトに変更される。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "以上の動作は、単なる代入でも同様である。代入文の実行後に変数`x`の識別値が変化する。" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549712" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 0\n", "id(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "代入文の本質的な動作は「左辺の変数の参照先を右辺のオブジェクトに変更する」ことである。例えば、以下の代入文では変数`y`と変数`x`は同一のオブジェクトを参照することになる。ゆえに、変数`x`と`y`の識別値は一致し、変数`x`と`y`の評価結果は同じになる。" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "y = x" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549712" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549712" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`x`と`y`の値は等しい。" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x == y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`is`演算子は2つのオブジェクトの識別値が等しいかどうかを評価する。以下の結果からも、`x`と`y`は同一のオブジェクトを参照していることが分かる。" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x is y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ここで、変数`y`に値をインクリメントすると、変数`x`と`y`の参照先が同じなので、変数`x`の値も変更されてしまうのではないかと思うかもしれない。ところが、`y`が参照しているオブジェクト(=`x`が参照しているオブジェクト)が不変であるため、変数`y`が参照しているオブジェクトの値を変更するのではなく、変数`y`の参照先がインクリメント後の計算結果を格納するオブジェクトに変更される。" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "y += 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "したがって、`y += 1`を実行したことにより、変数`x`と`y`は異なるオブジェクトを参照するようになる。これにより、変数`x`の値は変更されず、変数`y`の値が変更される、という(我々が通常期待する通りの)動作になる。" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549712" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283206549744" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(y)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x == y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`x`と`y`の識別値は異なるので、以下の評価結果も偽となる。" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x is y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 可変なオブジェクト" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "オブジェクトが可変のときはどのようになるのか? 実際に変数`x`にリストを代入して、変数`x`の識別値を調べてみる。" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = [1]\n", "x" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283101596992" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "続いて、変数`x`に要素を追加してみる。先ほどの不変のオブジェクトに対する操作とは異なり、変数`x`の識別値は変化しない。" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1]" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x += [1]\n", "x" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283101596992" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これは、変数`x`が参照しているオブジェクトの内容が直接変更されたことを意味している。このように、可変なオブジェクトを参照している変数の値を変更する操作を行うと、その変数の参照先は変更されず、オブジェクトの中身が変更される。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "先ほども説明したとおり、代入文の本質的な動作は「左辺の変数の参照先を右辺のオブジェクトに変更すること」である。例えば、以下の代入文では変数`y`と変数`x`は同一のオブジェクトを参照することになる。ゆえに、変数`x`と`y`の識別値は一致し、変数`x`と`y`の評価結果は同じになる。" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "y = x" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x == y" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x is y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ここで、変数`y`に要素を追加すると、変数`x`と`y`の参照先が同じなので、変数`x`の値も変更されたように見える。実際には、変数`x`と`y`は全く同一のオブジェクトを指しており、そのオブジェクトの内容を更新したので、当然の結果と言える。同じオブジェクトの内容を変数`x`や`y`を通して評価しているだけである。" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "y += [1]" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 1]" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283101596992" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 1]" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283101596992" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(y)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x == y" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x is y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "変数`x`が参照しているリストのコピーを作成し、変数`z`に代入する。`x`と`z`の内容は等しいが、`x`と`z`の識別値は異なる。" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "z = x[:]" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 1]" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283101596992" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(x)" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 1]" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "z" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "140283101495936" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(z)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`x`と`z`の内容(要素)は等しいが、`x`と`z`は同一のオブジェクトを指しているわけではない。" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x == z" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x is z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 関数への参照渡しと不変・可変" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pythonでは、関数の引数は**すべて参照渡し**となる。関数にオブジェクトの参照を渡すと、原理上はその関数内でオブジェクトへの内容を変更できる。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ところが、数値や文字列などの不変なオブジェクトを関数の引数として変数で渡した場合、関数内で引数として渡された変数を変更しても、呼び出し元には影響が波及しない。" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "def add(x):\n", " x += 1" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = 1\n", "add(x)\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "比較のため、以上のコードをC言語風に書くと、次のようになる。これを読む限りは、呼び出し元の変数`x`の値が変更されそうに思える。\n", "\n", "```c\n", "void add(int *x)\n", "{\n", " (*x) += 1;\n", "}\n", "\n", "int main()\n", "{\n", " int x = 1;\n", " add(&x);\n", " printf(\"%d\\n\", x);\n", " return 0;\n", "}\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "しかしながら、変数`x`のオブジェクトは不変であるため、`add`関数内で`x`の値を更新する際に、変数`x`の識別子が変化する。これは、先ほど説明した不変なオブジェクトへの値の変更の動作と一貫している。この動作のため、関数に不変なオブジェクトを参照渡ししても、関数内でそのオブジェクトの値が変更されることなく、値の変更の影響が関数内に留まる。" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "更新前の識別値 140283206549744\n", "更新後の識別値 140283206549776\n", "呼び出し元の識別値 140283206549744\n" ] }, { "data": { "text/plain": [ "1" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def add(x):\n", " print('更新前の識別値', id(x))\n", " x += 1\n", " print('更新後の識別値', id(x))\n", " \n", "a = 1\n", "add(a)\n", "print('呼び出し元の識別値', id(a))\n", "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これに対し、リストを参照渡しで関数に与えると、その関数内でそのリストの内容を変更できるし、その変更結果は関数の呼び出し元にも波及する。" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "def add(x):\n", " x += [1]" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1]" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = [1]\n", "add(x)\n", "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "これは、変数`x`のオブジェクト(リスト)は可変であるため、`add`関数内で`x`に要素を追加する際に、変数`x`の識別子が変化しない。これも、先ほど説明した可変なオブジェクトへの変更の動作と一貫している。この動作のため、関数に可変なオブジェクトを参照渡しをした場合、関数内でそのオブジェクトの内容が変更されると、その影響が関数の呼び出し元にも波及する。" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "更新前の識別値 140283101599232\n", "更新後の識別値 140283101599232\n", "呼び出し元の識別値 140283101599232\n" ] }, { "data": { "text/plain": [ "[1, 1]" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def add(x):\n", " print('更新前の識別値', id(x))\n", " x += [1]\n", " print('更新後の識別値', id(x))\n", " \n", "a = [1]\n", "add(a)\n", "print('呼び出し元の識別値', id(a))\n", "a" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "remove-cell" ] }, "source": [ "---\n", "\n", "[Python早見帳](https://chokkan.github.io/python/) © Copyright 2020-2024 by [岡崎 直観 (Naoaki Okazaki)](https://www.chokkan.org/). この作品はクリエイティブ・コモンズ 表示 - 非営利 - 改変禁止 4.0 国際 ライセンスの下に提供されています。\"クリエイティブ・コモンズ・ライセンス\"" ] } ], "metadata": { "@context": { "CreativeWork": "http://schema.org/CreativeWork", "Organization": "http://schema.org/Organization", "Person": "http://schema.org/Person", "author": "http://schema.org/author", "copyrightHolder": "http://schema.org/copyrightHolder", "copyrightYear": "http://schema.org/copyrightYear", "license": "http://schema.org/license", "name": "http://schema.org/name", "title": "http://schema.org/name", "url": "http://schema.org/url" }, "@type": "CreativeWork", "author": [ { "@type": "Person", "name": "Naoaki Okazaki", "url": "https://www.chokkan.org/" } ], "copyrightHolder": [ { "@type": "Person", "name": "Naoaki Okazaki", "url": "https://www.chokkan.org/" } ], "copyrightYear": 2024, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" }, "license": "https://creativecommons.org/licenses/by-nc-nd/4.0/deed.ja", "title": "Python早見帳" }, "nbformat": 4, "nbformat_minor": 4 }