2019年1月15日火曜日

クラスの変数とインスタンス変数、selfのはなし

selfとは何か、また、クラス変数とインスタンス変数の違いについて、調べたことをメモする。

  • selfはインスタンスに関するものであるということ。メソッドの第一引数はインスタンスに作用することを明示している。変数として'self.(=自分自身のオブジェクト名.)'をつけるのは自分自身をインスタンシェートしたときにオブジェクト固有情報として扱うものであることを意味する。
  • クラス変数とは、クラス共通の変数。設計図であるクラスに共通なので、オブジェクト外部から変更を行った場合、変更は設計図の変更を意味し、そのクラスから作ったすべてのオブジェクトのクラス変数が参照渡しで動的に変更されるオブジェクト内部から変更を行った場合、すなわちメソッドで扱った場合、関数には値渡しされる、すなわちオブジェクト固有情報として複製が渡される。
  • インスタンス変数とはオブジェクト固有の変数で、インスタンシェート後に各々のオブジェクトで管理される変数。
  • グローバル変数のスコープはクラスも包括する。
  • クラス内ではローカル変数が使える。

code

01 Param = 'global' # global
02 class ParamTest(object):
03     Param = 'class' # class
04     def __init__(self):
05         pass
06 #        self.Param = 'instance'
07     def duplicate(self):
08         self.Param += 'Copy' # class's copy
09     def Get(self):
10         print(Param) # global
11     def Get2(self):
12         print(self.Param) # class or instance
13         
14 if __name__ == '__main__':
15     ParamTest.Param += '2'
16     ob1 = ParamTest()
17     ParamTest.Param += '3'
18     ob2 = ParamTest()
19     print(ob1.Param)
20     ob1.duplicate()
21     print(ob1.Param)
22     print(ob2.Param)
23     ob1.Get()
24     ob2.Get()
25     ob1.Get2()
26     ob2.Get2()
27     ParamTest.Get(ob1)
28     ParamTest.Get2(ob1)
実行結果

class23
class23Copy
class23
global
global
class23Copy
class23
global
class23Copy
  • 01:グローバル変数Paramを定義。
  • 03:クラス変数Paramを定義。
  • 06:コンストラクタに'コメントで'インスタンス変数Paramを定義。これをするとクラス変数が無効になる。
  • 08:クラス変数Paramに内部からアクセスしている。結果的にクラス変数Paramがインスタンス変数Paramに値渡しされる形で定義される。値渡しされていることが分かるように既存の変数に追記する形で定義している。クラス変数に内部からアクセスするときは参照のみ可能で、'self.'を付けて参照することで同名の('self.'付の)インスタンス変数が定義され、クラス変数の内容が値渡しされる。
  • 10:グローバル変数Paramを参照。01行目の定義がなければ参照先が無いためエラーとなる。
  • 12:duplicate実行後はインスタンス変数が参照され、実行前はクラス変数(のコピー)が参照される。
  • 15,17:ParamTestクラスのクラス変数Paramに外部からアクセスしている。クラス変数を変更すると定義済みのオブジェクトのクラス変数も変更されることが分かるように、16,18行目でオブジェクトをインスタンシェートしている。結果から分かるように、何れも'class23'となっており、参照渡しされている。
  • 20-22:duplicate実行によりクラス変数と同名のインスタンス変数が定義されることを確認している。ob1.Paramはインスタンス変数を参照しており、ob2.Paramはクラス変数を参照している。外部・内部に因らず、同名のクラス変数とインスタンス変数がある場合はインスタンス変数が優先される。25,26行目は内部からアクセスした場合で、結果は21,22行目と同じなのが分かる。
  • 27,28:メンバ関数(=インスタンス関数=クラス関数=メソッド)にクラスから直接アクセスしている。この書き方から、メソッドの第一引数をselfとしている理解が伺える。Pythonのメソッドはクラスから直接アクセスでき、第一引数にオブジェクト名を指定することで指定したオブジェクトのメソッドを実行することができる。

selfについて

クラスの関数(コンストラクタを含む)の第一引数は必ず自分自身を現す仮引数である必要がある(何も指定しないとエラーになる)。仮引数は(ここで仮引数と呼んでいるように仮引数なので)selfでなくてもよいが、可読性のための作法としてselfとする。
self第一引数の意味するところは、クラスをインスタンスしたときに、その関数がそのオブジェクトにのみ作用することを明示するためである。別の見方として、上の例の通り、クラスから直接メソッドにアクセスする手段があり、その際の第一引数にオブジェクト名を指定する。
変数の頭につけるselfも同様で、それがインスタンス変数であることを宣言している。

2019年1月7日月曜日

オブジェクト指向のはなし

オブジェクト指向

あくまで一つの理解の形として。
オブジェクト指向とは、ではなく、なぜオブジェクト指向言語なのか。
言わずとだが、理解に変化があれば随時書き直す。

プログラムはアルゴリズムとデータ構造からなる。
データ構造といえばスタックやキューなどの「データを保持する手法」、アルゴリズムといえばリニアサーチやバイナリサーチなどの「データを処理するための方法」。平たく言えば、①「データ」の管理方法と②「処理手法」で良いと思う。

これを③「制御構造」で繋ぎ合わせて機能を実現する。
これを②や③に着眼点をおいてまとめ上げる手法が構造化プログラミング(structured programming)。簡単な制御構造を多段階に組み合わせて②や③を実現することで、プログラム・パーツの流用性を良くし、大きな処理をパーツの組み合わせで簡単に組み上げることを利点とする。

オブジェクト指向とは、その名の通りオブジェクト(モノ)の単位でプログラムを構成する考え方のこと。すべてのオブジェクトは固有情報をもって独立していて、オブジェクトの中にアルゴリズム(処理)とデータ構造が存在する。プログラムの骨子ではこのオブジェクトをどのように扱うかを記述する(メッセージを送る)。

具体例をあげる。
ちなみにリストは型自体にプッシュ・ポップなどのデータ構造を扱うメソッドがあり、ソートなどの処理手法も持っていて、そう言った理論の実現方法を知らなくて良く、使うことに専念できる。人気のある言語を選ぶと、こういった機能がどんどん拡張される(ライブラリとして提供される)メリットがある。
(下記は比較するための例なので、あえて古典的なことをしている。)

リストを整列するプログラム

""" 前準備 """
バブルソート関数を定義する:装置X
""" データ """
10~19の数字のかかれた10個のボールをランダムに並べる:リストA
20~29の数字のかかれた10個のボールをランダムに並べる:リストB
""" 処置 """
リストAを装置Xに入れて戻り値をリストAに入れる(取り出す)
リストBを装置Xにいれて戻り値をリストBに入れる(取り出す)

リストを整列するプログラム(オブジェクト指向)

""" 前準備 """
クラスPを定義する。
・クラスPはリストQを保持する
・リストQは外から設定することができる
・リストQは外から参照することができる
・リストQをバブルソートさせる命令を外から与えることができる
""" データ """
クラスPからオブジェクトC、Dを作る(インスタンシェート)。
オブジェクトCに10~19の数字のかかれたボールを入れる
オブジェクトDに20~29の数字のかかれたボールを入れる
""" 処理 """
オブジェクトCにバブルソートするよう命令する
オブジェクトDにバブルソートするよう命令する
オブジェクトCのQを参照する
オブジェクトDのQを参照する

前者も後者もやっていることは同じである。 前者はデータ(リストA、リストB)と処理(装置X)という形でプログラムの構成要素を整理している。 後者はオブジェクト(リストと処理を内部に持つ)という形でプログラムの構成要素を整理している。

プログラムを書いていると、規模か膨らむにつれて変数の数が増え、その変数がどの処理に関係するものなのかがわかりにくくなっていくことがあるかと思う。関数も同様で、同じ関数をいろいろな関数で多重に呼び出すことでプログラム資産の流用性を良くすることがあると思うが、反面、一部の関数を変更すると呼び出し側の関数に影響してしまったり、まったく別の目的で同じ関数を使うことで用途と関数名が微妙にずれて見通しのわるいソースになったりしてしまうことがあると思う。

オブジェクト指向を使う理由は、変数や関数を使いまわすのではなくオブジェクト専用にすることで、プログラムの見通しをよくするためだと思う。

オブジェクトのもとになる型のことをクラスと呼ぶ。クラスは通常、変数と関数をもっている。これを使うには<インスタンス>.<変数や関数>のように指定する。Cの構造体に似ている。C++ではまさに構造体のメンバに関数が追加されたイメージで、変数をメンバ変数、関数をメンバ関数と呼ぶ。pythonではインスタンス変数、メソッドと呼び、細かい違いはあるが概ね同じ概念である。 HDLも似た概念になっていて、Verilog-HDLのモジュール(クラス)とインスタンス(オブジェクト)の関係に似ている。HDLでは同じ設計図(モジュール)を一つの回路に複数構成する場合、同じモジュールを別のインスタンス名でインスタンシエーションする。多くの(?)オブジェクト指向のプログラミング言語ではインスタンス変数にアクセスするのにメソッドを使う作法になっている(pythonは直接アクセス出来る)のに対し、HDLはここが逆のイメージで、ファンクションを動かすために変数をスイッチのように設定する(結果はI/Oに現れたり変数(レジスタ)に格納されたりする)。変数には固有のアドレスをオブジェクトの外側で定義することで、どのインスタンスの変数を活性化するかを区別している。

カプセル化、継承、ポリモーフィズムはオブジェクト指向の基本概念なので、pythonでも他の言語と同様である。(HDLを対比に挙げたが、こちらは正確にはオブジェクト指向というわけではないので、継承、ポリモーフィズムの”手法”はない。カプセル化はあるといえばある。概念として実装することはもちろん可能。SystemVerilogは別として。)


2019年1月3日木曜日

エンコーディングと文字列

エンコーディング

python2系のはなし。
プログラムファイル内の文字列をどの文字符号化方式(エンコーディング)としてデコードするかはファイルのヘッダ部で指定する。文字列を正しく扱うには、プログラムファイルの保存時に指定するエンコーディングとヘッダ部で指定するエンコーディングを一致させる必要がある。つまり、ファイル保存時の文字符号化方式が何かを処理系に教えるために使うのが、ヘッダ部のエンコーディング指定である。


encoding(cp932)

#! /usr/bin/env python
# -*- coding: cp932 -*-
encoding(utf-8)

#! /usr/bin/env python
# -*- coding: utf-8 -*-

windows環境ではデフォルトCP932で保存されるエディタが多く(?)、エンコーディングを気にせず保存するとCP932となる。この場合、ヘッダ部のエンコーディングはCP932にしないと実際の(ファイルの)エンコーディングと処理系に教えるエンコーディングが不一致となる。


文字列とユニコード文字列

やはりpython2系のはなし。
文字列型(str)とユニコード文字列型(unicode)の区別がある。
strをユニコードに変換することをデコード、逆をエンコードと呼ぶ。
違いは2つ(まだあるかもしれないが)。

  • 文字コードを扱う単位
  • ユニコードはIOへ出力する際自動で処理系に合わせてエンコードされる

'文字列'は文字列(str)型として扱われる。 これは、文字列がバイト列として扱われることを意味する。 例えば文字数を取得する際、バイト列で扱うと正しい文字数がカウントできない。 lenによるカウントはバイト列の数を返す。 printなどでIOに渡したとき、ヘッダで指定したエンコーディングでそのままIOに渡される。 処理系のエンコーディングとファイルのエンコーディングがあっていれば文字化けしないが、異なると文字化けする。

pythonにはユニコード文字列という型がある。 u'文字列'はユニコード文字列として扱われる。 これは、文字列がマルチバイトの集合として扱われることを意味する。 例えば文字数を取得する際、ユニコード文字列で扱うと正しい文字数がカウントできる。 lenによるカウントは文字数を返す。 printfなどでIOに渡したとき、自動で処理系に合わせてエンコードされるので文字化けしない。

cp932文字コード

A:41 #1バイト
ε:83C3 #2バイト
あ:82A0 #2バイト
utf-8文字コード

A:41 #1バイト
ε:ceb5 #2バイト
あ:e38182 #3バイト
cp932のプログラム

#! /usr/bin/env python
# -*- coding: cp932 -*-

if __name__ == '__main__':
    
    info = u'Aεあ'
    info2 = 'Aεあ'
    
    print info
    print len(info)
    print info2
    print len(info2)
    print info2.decode('cp932')
    print len(info2.decode('cp932'))
    print info2.decode('utf-8')
    print len(info2.decode('utf-8'))
cp932処理系での実行結果

Aεあ
3
Aεあ
5
Aεあ
3

Traceback (most recent call last):
  File "D:\learning\sample1.py", line 15, in 
    print info2.decode('utf-8')
  File "C:\Python27\lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x83 in position 1: invalid start byte
>>> 

cp932の処理系でcp932のエンコーディングで書いたプログラムを実行した結果。

  • infoはユニコードなので3文字
  • info2はcp932のstrなので5文字
  • info2をデコードしたものはユニコードになるので3文字
  • 最後はinfo2(cp932)をutf-8でデコードできずエラー

utf-8のプログラム

#! /usr/bin/env python
# -*- coding: utf-8 -*-

if __name__ == '__main__':
    
    info = u'Aεあ'
    info2 = 'Aεあ'
    
    print info
    print len(info)
    print info2
    print len(info2)
    print info2.decode('utf-8')
    print len(info2.decode('utf-8'))
    print info2.decode('cp932')
    print len(info2.decode('cp932'))

cp932処理系での実行結果

Aεあ
3
Aホオ縺
6
Aεあ
3

Traceback (most recent call last):
  File "D:\learning\sample1.py", line 15, in 
    print info2.decode('cp932')
UnicodeDecodeError: 'cp932' codec can't decode byte 0x82 in position 5: incomplete multibyte sequence
>>> 

cp932の処理系でutf-8のエンコーディングで書いたプログラムを実行した結果。

  • infoはユニコードなので3文字
  • info2はutf-8なのでcp932の処理系では文字化けする
  • info2はutf-8のstrなので6文字
  • info2をデコードしたものはユニコードになるので3文字
  • 最後はinfo2(utf-8)をcp932でデコードできずエラー