Top
page
Pythonの変数型 (上級者向)
・
pythonでは、変数、関数、クラス、変数型、ファイル
(モジュール) まで、全てオブジェクトです
- オブジェクト (Object):
複数の属性 (attribute, property) と 操作 (method) を結び付けた変数。
pythonの場合は、属性と操作をいずれもattributeと呼ぶ。
- クラス (class):
オブジェクトの定義
- インスタンス (instance):
オブジェクトを変数として実体化させたもの
- オブジェクト変数の属性へのアクセス
インスタンス変数名.属性名インスタンス変数名.操作名(引数)
- オブジェクト指向プログラミング:
オブジェクト間の関係と相互作用を中心とするプログラミングスタイル
- 手続き型プログラミング:
Cのように、関数、サブルーチン(手続き: procedure)で構造化したプログラミングスタイル
・
pythonの変数へのアクセスは、全て参照型です
- プログラム言語における変数へのアクセス方法の分類
(1) 値型:
変数が参照するメモリアドレスには変数の値が格納されている。
このアドレスには変数型に対応するサイズの領域を確保している。
プログラム中で変数を参照すると、値が返ってくる
(2) ポインタ型 (アドレス型):
変数が参照するメモリアドレスには、値が入っているメモリ領域へのアドレスが格納されている。
値が入っているメモリ領域変数の値が入っている。
プログラム中で変数の値を参照すると、アドレスが返ってくる
ポインタ変数に対応する値を参照するには、参照演算子を使う必要がある
(Cの例: ポインタ変数を p とすると、値は
*p で受け取る )
(3) 参照型:
コンピュータの内部的にはポインタ型だが、
プログラム中で変数の値を参照すると、値が返ってくる
(参照演算子を使う必要はない)
- 関数の引数への変数の渡し方
(1) 値渡し:
引数変数へは、値がコピーされる。
元の変数のアドレスと引数変数のアドレスは異なるため、関数内で引数変数の内容を変更しても、元の変数には反映されない。
(2) ポインタ渡し (アドレス渡し):
引数変数へは、アドレスが渡される。
元の変数のアドレスと引数変数のアドレスが同じため、関数内で引数変数の内容を変更すると、元の変数も変更される。
関数内でアドレスの値を参照するためには、参照演算子を使う必要がある。
(3) 参照渡し:
内部的には引数変数へはアドレスが渡される。
関数内でも参照型でアクセスするので、アドレスの値を参照するために参照演算子を使う必要はない。
関数内で変数の値を変更すると、関数の呼び出しもとの変数も変更される。
(4) 参照の値渡し:
pythonで使われている方法。内部的には引数変数へはアドレス
(変数へのポインタ) のコピーが渡される。
関数内でも参照型でアクセスするので、アドレスの値を参照するために参照演算子を使う必要はない。
関数内で変数の値を変更しても、関数の呼び出しもとの変数は変更されない。
・
一般的な言語では、大きなサイズの変数を関数に渡すときは、ポインタあるいは参照型を渡します
- C/C++, Fortran, perl
など、多くのプログラム言語では、変数サイズの小さい
整数型、論理型、浮動小数点型、文字型
(文字列型ではない)では値渡しを使います。
- 大きなサイズの変数を渡すときは、ポインタ渡し、あるいは参照渡しを使います
(大きな変数のコピーにより、実行時間、使用メモリ量のロスが発生するため)
そのため、文字列型、リスト、タプル、マップ、オブジェクトのインスタンスなどは、ポインタ渡し、参照渡しをする場合が多いのです。
・
pythonでは全ての変数は参照型ですが、整数型、浮動小数点型、論理型などの基本変数型は、関数内部で変更しても、元の変数に影響を与えません
関数内で基本変数型の引数への代入が関数の呼び出しもとに影響を与えないことは他の言語と同じですが
(代入の副作用は同じ)、
pythonでは、python特有の仕様で実現しています。興味のある方は以下の説明を読んでください。
引数変数で渡された整数型などを関数内で変更する場合、例えば以下のようになります。
def func(i):
i = 3
i = 5
print("1: i=", i)
func(i)
print("2: i=", i)
他の多くのプログラム言語と同様
(初期のBASICなどは例外です)、1:の i は 5 で、2: の i も5
のままで、func()中での変更は反映されません。
これは、他のプログラム言語では関数への引数は値渡しをしているため、関数内の変数の実体は関数の呼び出し元の変数とは異なるためです。
一方、上記の通りpythonでは基本変数型を含めて全て参照の値渡しをしており、下記のように変数への代入では代入する変数・値の参照に置き換えることで
値を変えています。この機構により、関数内での値の変更は関数の呼び出しもとの値に影響を与えないようになっています。
例えば、上記の i = 5 の i のアドレスを XXXX とします。
func(i) 中での i = 3 では、3 のアドレスを YYYY とすると、i
のアドレスは YYYY に置き換わります。この i
は、func(i)の局所変数です。
func() の外部の i はfunc(i)の局所変数 i
と別ですので、値は変わりません。
- この機構により、整数型などもオブジェクトであり参照(の値)渡しをするにもかかわらず、関数内での変更が関数外に影響を与えません。
- 他のオブジェクト、文字列、リスト、タプルなども参照渡しをしますので、上記のように「変数の代入」をした場合は元の変数の値に影響を与えません。
- 一方、オブジェクト、リストの属性の値を関数内で変更しても変数のアドレスは変わらない(指しているオブジェクトは変わらない)
ため、
元の変数の内部の値も変更されます。
- 変数への代入はアドレスの代入ですので、代入元の変数のアドレス(つまり変数の実体)
と 代入先の変数のアドレスは同じになります。
例:
l = [1, 2]
m = l
m[0] = 3
とすると、mとlは同じ変数を指していますので、m[0]への変更はl[0]への変更と同じになります。
・ ミュータブルとイミュータブル
- イミュータブル:
変数の属性を変更できない変数型
整数型、論理型、浮動小数点型、文字列型、タプル
など
- ミュータブル:
変数の属性を変更可能な変数型
リスト、マップ、一般的なクラスなど
ただし、イミュータブルなオブジェクトのすべての内部の値を変更できないわけではありません。
タプルはイミュータブルなので
a = (1, 2, 3)
a[0] = 3
はエラーになりますが、
a = ([], [],. [])
a[0].append(3)
は問題ありません。変更できないのは タプル ()
の要素のアドレスです。
オブジェクトの例 (仮想コード)
catオブジェクト: propertyとして、color, weight, methodとして move(), position()などが考えられる。
# クラスの定義 (概念的なプログラムリストなので以下のままでは動かない):
class cat():
#
pythonでは、 # 以降は行末までがコメント文として、無視される
#
関数は def文で定義し、: で終わる
#
変数は 変数名
= 初期値 とすることで、初期値を設定できる
def __init__(self, color = 'white',
weight = 20.0):
color = color
#
ややこしいけど注意。左のcolorはcatのattribute変数
#
右辺のcolorは__init__関数の引数変数
weight = weight
x = 0.0 #
位置変数 x,y,zは 0.0 で初期化しておく
y = 0.0
z = 0.0
def
move(dx, dy, dz): #
位置をdx,dy,dzだけ移動させる
…
def position():
return
x, y, z # 位置を
[x,y,z]リストで返すメソッド
インスタンスの生成(変数の作成)
nyanko = cat() # catクラスで定義されたオブジェクトのインスタンス変数nyankoをつくる。
# 引数を指定していないので、初期値が使われ、
# naynako.color は 'white'、nyanko.weightは 20kgになる。
nyanko = cat(color = 'black')
# catクラスで定義されたオブジェクトのインスタンス変数nyankoをつくる。
# 引数で指定されたcolor変数は初期値から変更され、naynako.color は 'black' になる
nyanko.move(3, -1, 0)を実行すると、nyanko.x,
nyanko.y, nyanko.zはそれぞれ 3, -1, 0 になる
nyanko.move(-1, 3, 2)を実行すると、nyanko.x,
nyanko.y, nyanko.zはそれぞれ 2, 2, 2 になる