初めてのPython(6章)
Pythonプログラミングにおいて重要なことに「型付けがダイナミック」ということがある。
この特性のおかげで
- 宣言なしで変数を使える
- 型の違うオブジェクトを同じ変数に代入できる
ダイナミックな型付けは以下のようにして実現される
- 変数はオブジェクトへのリファレンスのみ保持
- 型情報(値、可能な演算など)はオブジェクト自身が保持
変数とオブジェクトが独立して存在し、リファレンスによって結ばれる方式のため、
一つのオブジェクトを異なる変数から参照する状況が発生する(共有リファレンス)
共有リファレンスを扱う場合、オブジェクトがmutableな場合代入には注意が必要。
そして、各々のオブジェクトについて、
変数からのリファレンスが0になった段階で、自動的にオブジェクトが削除される(ガーベージコレクション)
変数とオブジェクトの関係
変数とオブジェクトは以下のような関係になっており、別々なメモリ領域に存在している。
リファレンスは、Cでいうポインタのようなもので、どんな値にも対応可能なvoid*。
例えば a = 3 のようなコードを書いた場合、次の3ステップの処理が実行されている。
- 値3を持つ整数型のオブジェクトが作成される
- 変数aが作成される(既に変数aが存在する場合は、作成されない)
- 変数aとオブジェクトがリンクされる[リファレンスが作成される]
変数
変数は、『サーチテーブルのエントリ』と『オブジェクトへのリンクのためのスペース』を持つ
サーチテーブルのエントリに"a"、リンクのためのスペースに"オブジェクトへのリファレンス"が入る。
サーチテーブルってのは、名前空間のことなのかな。
この辺のことを調べるにはどうしたら良いんだろうか。
リファレンスとガーベージコレクション
変数からのリファレンスが0になった時に、行われるガーベージコレクションは以下のようなイメージ
a = 3, b = a というコードを実行した時のメモリ上のイメージは
整数型のオブジェクト3に変数a, bからのリファレンスが作成される。
次に、a = 'spam' を実施したときは、
文字列型のオブジェクト'spam'に変数aからのリファレンスが作成されるが、変数bのリファレンスはそのまま。
そして、b = a を実施すると
変数bのリファレンスが整数型のオブジェクト3から文字列型のオブジェクト'spam'に変更される。
その結果、整数型のオブジェクト3は変数からのリファレンスがなくなり(リファレンスカウンタが0)、
ガーベージコレクションが実施され削除される。
共有リファレンスとオブジェクトの上書き
オブジェクトに共有リファレンスが存在する場合に、オブジェクトに変更を加える処理を行うとき、
オブジェクトが”mutable”か”immutable”かで処理結果が大きく異なる
immutableなオブジェクトの場合
immutableなオブジェクトに対して変更を加えると、
- 演算結果を値として持つ新しいオブジェクトが作成される
- 変数のリファレンスは新しいオブジェクトに向けられる
mutableなオブジェクトの場合
mutableなオブジェクトに対して変更を加えると、
- 演算結果はもとのオブジェクトの値と置き換えられる
- 変数のリファレンスは変わらない
mutableなオブジェクトの場合、共有リファレンスを使うのではなく、オブジェクトの『コピー』を使う。
コピーのやり方は、シーケンスだけで使えるスラシング、ディクショナリだけで使えるcopy()メソッド、全オブジェクトで使えるcopyモジュールがある
(シーケンスの場合)
[kobakoba0723@fedora13-intel64 ~]$ python Python 2.6.4 (r264:75706, Apr 1 2010, 02:55:51) [GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> a = [2, 3, 4] >>> b = a[:] >>> a [2, 3, 4] >>> b [2, 3, 4] >>>
(ディクショナリの場合)
[kobakoba0723@fedora13-intel64 ~]$ python Python 2.6.4 (r264:75706, Apr 1 2010, 02:55:51) [GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> a = {'spam':1, 'egg':2} >>> a {'egg': 2, 'spam': 1} >>> b = a.copy() >>> a {'egg': 2, 'spam': 1} >>> b {'egg': 2, 'spam': 1} >>>
(copyモジュールの場合)
[kobakoba0723@fedora13-intel64 ~]$ python Python 2.6.4 (r264:75706, Apr 1 2010, 02:55:51) [GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import copy >>> list_a = [2, 3, 4] >>> dict_a = {'spam':1, 'egg':2} >>> list_b = copy.copy(list_a) >>> dict_b = copy.copy(dict_a) >>> list_a [2, 3, 4] >>> list_b [2, 3, 4] >>> dict_a {'egg': 2, 'spam': 1} >>> dict_b {'egg': 2, 'spam': 1} >>>
copyモジュールには2つのコピーメソッドがある
メソッド名 | メソッドの内容 |
---|---|
copy() | コピー対象のトップレベルのみコピー(シャローコピー) |
deepcopy() | コピー対象にネストされたオブジェクトを含めて全てコピー(ディープコピー) |
「同等」と「同一」
Pythonではオブジェクトが「同じ」かどうかを比較する2つの方法がある。
関係は、同等⊂同等
方法 | 演算子 | 意味 |
---|---|---|
同等 | a == b | aとbが指し示すオブジェクトの値が同じ |
同一 | a is b | aとbが指し示すオブジェクトそのものが同じ(リファレンスが等しい) |
例外的に、小さな数値や文字列の場合は同等と同一が同じに見えてしまうことがある。
[kobakoba0723@fedora13-intel64 ~]$ python Python 2.6.4 (r264:75706, Apr 1 2010, 02:55:51) [GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> a = 1 >>> b = a >>> c = 1 >>> d = 2 >>> a == b True >>> a == c True >>> a == d False >>> a is b True >>> a is c True >>> a is d False >>>
これは、小さな数値や文字列のキャッシュによるもの。
小さな値は頻繁に使用されるため、その都度オブジェクトを作ると効率が悪くなってしまう。
そのため、キャッシュして使う動きになっている。
キャッシュして使うため、新しいオブジェクトを作るコードの動きが、
既に存在するオブジェクトへのリファレンスになり、同一のものと判定される。