初めてのPython(6章)

Pythonプログラミングにおいて重要なことに「型付けがダイナミック」ということがある。

この特性のおかげで

  • 宣言なしで変数を使える
  • 型の違うオブジェクトを同じ変数に代入できる

ダイナミックな型付けは以下のようにして実現される

  • 変数はオブジェクトへのリファレンスのみ保持
  • 型情報(値、可能な演算など)はオブジェクト自身が保持


変数とオブジェクトが独立して存在し、リファレンスによって結ばれる方式のため、
一つのオブジェクトを異なる変数から参照する状況が発生する(共有リファレンス)
共有リファレンスを扱う場合、オブジェクトがmutableな場合代入には注意が必要。


そして、各々のオブジェクトについて、
変数からのリファレンスが0になった段階で、自動的にオブジェクトが削除される(ガーベージコレクション)

変数とオブジェクトの関係

変数とオブジェクトは以下のような関係になっており、別々なメモリ領域に存在している。
リファレンスは、Cでいうポインタのようなもので、どんな値にも対応可能なvoid*。

例えば a = 3 のようなコードを書いた場合、次の3ステップの処理が実行されている。

  1. 値3を持つ整数型のオブジェクトが作成される
  2. 変数aが作成される(既に変数aが存在する場合は、作成されない)
  3. 変数aとオブジェクトがリンクされる[リファレンスが作成される]
変数

変数は、『サーチテーブルのエントリ』と『オブジェクトへのリンクのためのスペース』を持つ
サーチテーブルのエントリに"a"、リンクのためのスペースに"オブジェクトへのリファレンス"が入る。
サーチテーブルってのは、名前空間のことなのかな。
この辺のことを調べるにはどうしたら良いんだろうか。


オブジェクト

オブジェクトは、『オブジェクトの値』と2つのヘッダで構成される。
2つのヘッダは、

  1. リファレンスカウンタ
  2. 型情報を持つオブジェクトへのポインタ

ここでいう型情報を持つオブジェクトは今まで出てきたオブジェクトとは違うもの。
そうじゃないと、無限にポインタが参照を繰り返して何のことだかわからないものになってしまう。
オブジェクトには2種類あって、型情報を持つのはクラスオブジェクトかな。
で、今まで出てきたのはインスタンスオブジェクトのことを指してるんだろう。

  1. クラスオブジェクト
  2. インスタンスオブジェクト


リファレンスとガーベージコレクション

変数からのリファレンスが0になった時に、行われるガーベージコレクションは以下のようなイメージ
a = 3, b = a というコードを実行した時のメモリ上のイメージは

整数型のオブジェクト3に変数a, bからのリファレンスが作成される。
次に、a = 'spam' を実施したときは、

文字列型のオブジェクト'spam'に変数aからのリファレンスが作成されるが、変数bのリファレンスはそのまま。
そして、b = a を実施すると

変数bのリファレンスが整数型のオブジェクト3から文字列型のオブジェクト'spam'に変更される。
その結果、整数型のオブジェクト3は変数からのリファレンスがなくなり(リファレンスカウンタが0)、
ガーベージコレクションが実施され削除される。

共有リファレンスとオブジェクトの上書き

オブジェクトに共有リファレンスが存在する場合に、オブジェクトに変更を加える処理を行うとき、
オブジェクトが”mutable”か”immutable”かで処理結果が大きく異なる

immutableなオブジェクトの場合

immutableなオブジェクトに対して変更を加えると、

  • 演算結果を値として持つ新しいオブジェクトが作成される
  • 変数のリファレンスは新しいオブジェクトに向けられる

(演算前:a = 3, b = a)

(演算後:a = a + 2)

mutableなオブジェクトの場合

mutableなオブジェクトに対して変更を加えると、

  • 演算結果はもとのオブジェクトの値と置き換えられる
  • 変数のリファレンスは変わらない

(演算前:a = [2, 3, 4], b = a)

(演算後:a[0] = 10)

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が指し示すオブジェクトそのものが同じ(リファレンスが等しい)

(a == b の状態)

(a is 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
>>> 

これは、小さな数値や文字列のキャッシュによるもの。
小さな値は頻繁に使用されるため、その都度オブジェクトを作ると効率が悪くなってしまう。
そのため、キャッシュして使う動きになっている。
キャッシュして使うため、新しいオブジェクトを作るコードの動きが、
既に存在するオブジェクトへのリファレンスになり、同一のものと判定される。