python入門: 8. オブジェクト指向とは
以前, pythonはオブジェクト指向である, と学んだが, ここでその意味を説明する。まず, 以下の例をやってほしい。
pythonシェル(ipython3)で以下を打とう:
z=2+3j z z*z
このz=2+3jは複素数2+3iを表している。え!? なぜiのところがjになってるの!? と思うだろう。実は虚数単位は普通の数学ではiだが, 情報工学ではjと書く慣習なのだ(彼らにとって, iは電流を表す記号なのでかわりにjを使うのだ)。
z*zは(2+3i)(2+3i)=4+12i-9=-5+12iであり, 実際, その結果が(-5+12j)と表示されただろう。
では, このzの型を調べてみよう:
type(z)
すると"complex"と出てきた。complex number(複素数)の略である。
複素数には実部と虚部がある。それぞれを以下のように取り出すことができる:
z.real z.imag
それぞれ2.0と3.0というふうに表示されただろう。(realは実部, imagはimaginaryの略で虚部)
複素数には「複素共役」という概念がある。虚部の符号を逆転させた複素数だ。上の例の複素数zの複素共役を求めるにはこうすればよい:
z.conjugate()
すると2-3jと出てくるだろう。conjugateは「共役」を意味する英語だ。なぜ()をつけるかは後で説明する。
ここで注意して欲しいのは 君はz=2+3jと打ったことでzを定義したのだが, そのときz.realやz.imagやz.conjugate()というような命令は定義していなかった。なのにpythonは最初から知っていたかのようにそれらを実行したということだ。
どういうことかというと, pythonはあらかじめ「複素数」という概念を知っているのだ。つまり, 複素数は2つの実数から構成されるということだけでなく, その片方を実部(real), もう片方を虚部(imag)というとか, 複素共役(conjugate)とは虚部の符号を逆転させて新たな複素数を作ることだ, とかを知っているのだ。そのような属性(部品的な情報; この場合は実部の値や虚部の値)や操作(この場合は複素共役をとること等)をひっくるめてひとつの「複素数」という概念としてpythonは理解しているのだ。そのような概念(属性と操作をひとまとめにしたもの)をオブジェクトとよぶ。
上の話では, z=2+3jと打った時点でPythonは「zという, 実部は2で虚部は3の複素数のオブジェクトを作れと言われてるな」と判断してそのとおりの仕事をするのだ。
オブジェクトという概念を中心に据えて計算機言語を設計することをオブジェクト指向と呼ぶのだ。
ここで我々も複素数をどう理解しているか振り返ってみよう。複素数の定義は「2つの実数x, yについてx+yiと表される数」である(python的に書くなら, x+yj)。ところがそれだけでは複素数に付随する様々な概念を網羅していない。たとえば
- 複素数どうしの四則演算(和, 差, 積, 商)
- 複素数の絶対値
- 複素数の偏角
- 複素共役
- 複素数の極型式
などである。これらを理解してこそ「複素数を理解した」と言えるのだ。それを素直に計算機で表現・実現したものが「オブジェクト」である。扱う対象(ここでは複素数)の最もシンプルな実体(ここでは実部と虚部という2つの実数)だけでなく, それらの扱い方も含めて複素数という概念として一緒に計算機に教えておくのである。それが「オブジェクト」の発想である。
この, オブジェクトの中のデータや操作を使うには、ドット"."をつけてその後に何かを書き足す, という書式をする。上の例では, zというオブジェクトに.realをつけてz.realとすることで, 「zの実部」というデータが取り出せたし, .conjugate()をつけてz.conjugate()とすることで, 「zの複素共役を求める」という操作ができた。
このように, pythonでは, ドット"."でつないでどんどん書き足すことで, いろんなことができるようになる。
クラスとインスタンス
複素数というオブジェクトは, 丁寧に考えると,
- 複素数という概念の定義
- そのような定義にあてはまる具体的なもの
という2つの意味がある。pythonは前者を既に知っているが, 後者はpythonを使う人がその都度, 作るのである。
z=2+3j
と打った時に作られるzというものは後者である。前者は
type(z)
と打った時に現れる"complex"である。"complex"という概念のひとつの具体なものとして, z=2+3jが作られるのだ。
このような考え方において, 前者(概念の定義)を「クラス」と呼び, 後者(その概念にあてはまる具体的なもの)を「インスタンス」と呼ぶ。
- complexはクラス
- z=2+3jはそのインスタンス
である。
実は, これまで出てきた, intやfloat, str, listなどの型は, クラスなのだ。なぜ最初からクラスと呼ばなかったか? そう呼ぶと諸君が混乱すると思ったからである(実際, 多くのテキストでは, intやfloatはクラスと呼ばれる前に, 型と呼ばれる)。クラスは型を拡張したものだと思ってよい。
いま使っている変数のクラスについて詳細を知るのに, help()という命令がある。以下を試してみよう:
z=2+3j help(z)
すると, 以下のような表示が出るだろう(終わるにはqを押す):
Help on complex object: class complex(object) | complex(real[, imag]) -> complex number | | Create a complex number from a real part and an optional imaginary part. | This is equivalent to (real + imag*1j) where imag defaults to 0. | | Methods defined here: | | __abs__(self, /) | abs(self) ...
メソッドとデータ
複素数というオブジェクトは, 丁寧に考えると,
- 実部と虚部というデータ
- 複素共役をとる, 絶対値をとる, などの操作
というふうに, データと操作という2種類の概念で構成される。前者を「データ」と呼び(そのままやん!), 後者を「メソッド」と呼ぶ。z.realやz.imagはzというオブジェクト(complexクラスのインスタンス)のデータであり, z.conjugate()はzというオブジェクト(complexクラスのインスタンス)のメソッドである。
なお, メソッドやデータは, 次のようにインスタンスに直接つけることもできる(やってみよう!):
(2+3j).real (2+3j).conjugate()
[レポート課題8-1] 以下のようにxを定義する:
x=[1,2,1,2,3,2,5,4]
- xはどのようなクラスのインスタンスか?
- 以下を打ってみよ。その結果を見て, x.count()というメソッドとx.sort()というメソッドは, それぞれ何をするものか考えよ。
x.count(1) x.count(2) x.count(3) x.sort() x
[レポート課題8-2] 'I have a pen.'はstrクラスのインスタンスである。以下を打ち, upper(), lower(), replace()というメソッドがそれぞれ何をするものか考えよ。
'I have a pen.'.upper() 'I have a pen.'.lower() 'I have a pen.'.replace('a pen', 'an apple')
Pythonのオブジェクト志向
「Pythonでは全てがオブジェクト」と言われるが, 実はそうでもない。正確には, データや, データを扱う道具として, それぞれひとまとまりにできるものは全てがオブジェクトである。たとえば,
- モジュール
- パッケージ
- クラス
- インスタンス
- 関数
- 数
- 文字列
- ファイル
などはオブジェクトである。
しかし, if, for, whileなどは"キーワード" (予約語)というもので, オブジェクトではない。+, -, *, /などの演算記号もオブジェクトではない。(, ), [, ], {, }などの区切り文字もオブジェクトではない。
Pythonにおけるオブジェクトは, 何かのクラスのインスタンスである。
- 例: 関数はfunctionクラスのインスタンス。
- 例: モジュールはmoduleクラスのインスタンス。
- 例: クラスはtypeクラスのインスタンス。(注: typeクラスはただのクラスではなく「メタクラス」と呼ばれるもの。typeクラス自体がtypeクラスのインスタンスである。)
そういう意味では, オブジェクトとインスタンスはほぼ同義であると言えよう。しかし「インスタンス」という語は, そのクラスとの対応関係を意識した文脈で使われる。
- 例: "abc"というオブジェクトはstrクラスのインスタンスである。
Keyword(s):
References:[python入門]