試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

オブジェクト指向についての覚え書きとPython上のクラスと組み込みオブジェクトについて

Pythonの基本的なメモ集(言語・文法・型・文字列など)」「Pythonの基本的なメモ集(del文・条件分岐/if文・辞書・タプル/例外/関数の例)」のときと同様、高度な内容は扱わず、オブジェクト指向に詳しくない自分向けの覚え書きという位置付け。
(2014/11/5)ドキュメントのリンク先を修正し、サンプルコードもPython 3に対応する形に修正した。

  1. オブジェクト指向を使用する理由について
  2. 用語地獄
    1. オブジェクトの設計図と実体
    2. メンバ変数とメンバ関数の呼び方
    3. クラスの継承
  3. クラス名の慣習
  4. どのようなクラスをどのように作ればよいのか
  5. Pythonでのオブジェクト
    1. 扱い方
    2. テスト
    3. Pythonの組み込みオブジェクトについて

オブジェクト指向を使用する理由について

wikipedia:オブジェクト指向プログラミング「特徴」に書かれている「オブジェクトの区別」「背景」あたりが、何故オブジェクト指向を使用するのかという疑問に対する参考となった。
(2009/3/19)Wikipediaの参考箇所が変わっていたので修正・下のほうにも同様の修正をしている
wikipedia:オブジェクト指向のページは抽象的で、よく分からなかった。
なお、例え話も個人的には分かりにくくてダメだった。

用語地獄

新しい概念と同時に専門用語が色々と出てくる上、同じものを示す単語が複数存在するのは、理解の助けにならない。

オブジェクトの設計図と実体
オブジェクトは、一度その設計図として

  • メンバ変数: オブジェクトの持つデータ
  • メンバ関数: オブジェクトに関する*1操作

を含んだ*2「クラス」を定義し、個別の実体(インスタンスと呼ばれる)を必要に応じて作り出す*3という形で扱う。これにより、wikipedia:オブジェクト指向プログラミング「オブジェクトの区別」「背景」の途中に書かれている問題に対処する。

メンバ変数とメンバ関数の呼び方

  • メンバ変数は「プロパティ」「属性」などとも呼ばれる
  • メンバ関数は「メソッド」とも呼ばれる

個人的には「メンバ変数/メンバ関数」が分かりやすい。

クラスの継承

  • あるクラスをもとにして、(そのメンバ変数とメンバ関数を引き継いで)それを拡張したり一部を置き換えたりした子クラスを作ることができる
  • 親クラスのメンバ関数を子クラスで別の内容のものに置き換える(再定義する)ことができるが、元々あったものの上に重ねてそちらを優先させる*4という意味で「オーバーライド(override)」とも呼ばれる
  • 子クラスはサブ*5クラス/導出クラス/派生クラスなどとも呼ばれる
  • 親クラスはスーパー*6クラス/基底クラス/基本クラスなどとも呼ばれる

個人的には「親クラス」「子クラス」が分かりやすい。
(2009/3/27)用語「オーバーライド」についての記述を修正

クラス名の慣習

クラス名では「TestClass」のように、全ての単語の先頭の文字を大文字にし、他を小文字にすることが多い。

どのようなクラスをどのように作ればよいのか

クラスを扱ったサンプルコード的なものは色々なところにあり、下で行っているテストも含めて、どのような動作をするのかについてを知ることは難しくないのだが、wikipedia:オブジェクト指向プログラミング「オブジェクトの区別」「背景」の途中に書かれている問題に対処するためには、どのような「データと手続き」をどのようにまとめるのかといったことに関してはよく分からない。作り方によっては意味をなさないこともありえるし、間違った継承の使い方をした場合にもおかしなことになってしまいそうだが、このあたりは、コードを書く人が色々な練習を重ねて習得するものらしい(「google:クラス設計」という言葉があるようで、色々調べてみると、簡単なものではないようだ)。
また、「クラス1つ1つは小さなものであるとよい」と書かれているものも目にしたので、メモしておく。

Pythonでのオブジェクト

扱い方
具体的には
http://docs.python.jp/3/tutorial/classes.html#a-first-look-at-classes
や下の例を参照。

  • クラス定義は「class [名前]:」の中に、字下げをしてメンバ変数/関数の定義を記述する形
  • インスタンス生成は「[インスタンス] = [クラス名]()」の形
  • メンバ変数/関数はインスタンス名の後ろに「.」を付けた後に続ける
  • __init__()というメンバ変数*7を記述すると、インスタンス生成時に初期化処理として自動的に実行される

メンバ関数の1つ目の引数は、Pythonにおけるオブジェクト指向の実装上の理由で、そのクラスのインスタンスを引数に指定することになっている。慣習としては「self」と書くことになっていて、テキストエディタの色付け表示でもキーワードとして色が付くことが多い。

テスト
以下の例では、「a」という変数が

  • グローバルな変数a
  • TestClassクラスのメンバ変数としてのa
  • メンバ関数内でのみ有効な変数a

の3種類あり、それらがどのように扱われるかを実験している。

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

from __future__ import print_function

a = 1

class TestClass:
  a = 10         # メンバ関数からは「self.a」で扱われる
  def __init__ (self):
    """オブジェクトの初期化処理・constructorとも"""
    a = 99       # __init__()内でのみ有効
    #self.a = a  # この代入を実行するとメンバ変数aの値は初期化時に99になる
  def meth1 (self):
    print ('meth1(): a = {0}'.format (a))  # グローバルな「a」
  def meth2 (self):
    a = 50
    print ('meth2(): a = {0}' .format (a))  # meth2()内でのみ有効な「a」
  def get_a (self):
    return self.a
  def set_a (self, new_a):
    self.a = new_a

# インスタンスを作成
test = TestClass ()
# メンバ変数を直接参照している 値は10
print ('main: test.a = {0}'.format (test.a))
# グローバルな「a」を参照 値は1
test.meth1 ()
# meth2()内でのみ有効な「a」を参照 値は50
test.meth2 ()
# メンバ変数を間接的に読み込む 得られる値は10
print ('main: test.get_a() = {0}'.format (test.get_a ()))
# メンバ変数を間接的に書き込む 新しい値は20
print ('main: set_a(20)'); test.set_a (20)
# メンバ変数を間接的に読み込む 得られる値は20
print ('main: test.get_a() = {0}'.format (test.get_a ()))
# メンバ変数を直接読み込む 得られる値は20
print ('main: test.a = {0}'.format (test.a))

実行結果

main: test.a = 10
meth1(): a = 1
meth2(): a = 50
main: test.get_a() = 10
main: set_a(20)
main: test.get_a() = 20
main: test.a = 20

実行結果(__init__()内の「self.a = a」のコメントを外した場合)

main: test.a = 99
meth1(): a = 1
meth2(): a = 50
main: test.get_a() = 99
main: set_a(20)
main: test.get_a() = 20
main: test.a = 20

下の例では、上のTestClassを継承したクラスTestClass2を作成している。

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

from __future__ import print_function

a = 1

class TestClass:
  a = 10
  def __init__ (self):
    a = 99
    #self.a = a
  def meth1 (self):
    print ('meth1(): a = {0}'.format (a))
  def meth2 (self):
    a = 50
    print ('meth2(): a = {0}'.format (a))
  def get_a (self):
    return self.a
  def set_a (self, new_a):
    self.a = new_a

class TestClass2 (TestClass):  # TestClassを継承する
  b = 100
  def meth1 (self):
    print ('overridden meth1()')  # オーバーライドされたmeth1()
  def meth3 (self):
    print ('meth3(): a = {0}, b = {1}'.format (self.a, self.b))  # メンバ変数aとb

# 継承後のクラスのインスタンスを作成
test = TestClass2 ()
print ('main: test.a = {0}'.format (test.a))
test.meth1 ()  # TestClass2によりオーバーライドされている
test.meth2 ()
test.meth3 ()  # メンバ変数aとbを間接的に読み込む 得られる値は10と100
print ('main: test.get_a() = {0}'.format (test.get_a()))
print ('main: set_a(20)'); test.set_a (20)
print ('main: test.get_a() = {0}'.format (test.get_a()))
print ('main: test.a = {0}'.format (test.a))

(2009/3/27)一部コメントを変更
実行結果

main: test.a = 10
overridden meth1()
meth2(): a = 50
meth3(): a = 10, b = 100
main: test.get_a() = 10
main: set_a(20)
main: test.get_a() = 20
main: test.a = 20

Pythonの組み込みオブジェクトについて
Pythonでは、文字列などがオブジェクトとして存在していて、これに対する操作を行うメソッドが用意されている。文字列オブジェクトの場合は
http://docs.python.jp/3/library/stdtypes.html#string-methods
に一覧がある。

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

from __future__ import print_function

# 元の文字列
teststr = 'abcdefg   '
# rstrip()は元の文字列から後ろのスペースや改行を除いた複製を返す
print ('[{0}]'.format (teststr.rstrip ()))

実行結果

[abcdefg]

これ以外にも、ファイルオブジェクトなど、色々な種類の組み込みオブジェクトが存在する。

関連URL:

参考URL:

*1:オブジェクトと無関係の処理はメンバ関数にはできない

*2:メンバ関数を含まない使い方もある

*3:複数作ることができる

*4:上書き(overwrite)とは概念が微妙に異なるがそのようにも呼ばれることがある

*5:下の

*6:上の

*7:用語としてはコンストラクタと呼ばれるもの