第6章: Pythonicなオブジェクト指向プログラミング

Pythonのオブジェクト指向プログラミング(OOP)は、他の言語と考え方は似ていますが、よりシンプルで柔軟な構文を持っています。この章では、クラスの定義から継承、そしてPython特有の「マジックメソッド」まで、その基本を学びます。

`class`の定義とインスタンス化

Pythonでは、classキーワードを使ってクラスを定義します。JavaやC++のように波括弧{}は使わず、インデントでブロックを示します。非常にシンプルです。

クラスを定義したら、関数を呼び出すようにクラス名()と書くことで、そのクラスのインスタンス(オブジェクト)を生成できます。

>>> # 中身が何もない、最もシンプルなクラスを定義 >>> class User: ... pass ... >>> # Userクラスのインスタンスを生成 >>> user1 = User() >>> >>> # user1はUserクラスのオブジェクト(インスタンス)であることがわかる >>> user1 <__main__.User object at 0x10e85a4d0>

コンストラクタ (`__init__`) と `self`

インスタンスが生成される際に、特定の初期化処理を行いたい場合があります。そのために使われるのが__init__という特殊なメソッドで、他の言語におけるコンストラクタに相当します。

__init__メソッドの最初の引数には、慣習的にselfという名前を付けます。このselfは、生成されるインスタンス自身を指す参照で、JavaやJavaScriptのthisと同じ役割を果たします。selfを通じて、インスタンスに固有のデータを格納するインスタンス変数を定義します。

>>> class Dog: ... # インスタンス生成時に呼び出されるコンストラクタ ... def __init__(self, name, age): ... print(f"新しい犬のインスタンスが作られます!") ... # self.変数名 の形でインスタンス変数を定義 ... self.name = name ... self.age = age ... >>> # インスタンス化する際、__init__のself以外の引数を渡す >>> my_dog = Dog("ポチ", 3) 新しい犬のインスタンスが作られます! >>> >>> # インスタンス変数にアクセス >>> my_dog.name 'ポチ' >>> my_dog.age 3

インスタンス変数とクラス変数

Pythonのクラスには2種類の変数があります。

  • インスタンス変数: self.変数名のように__init__内などで定義され、各インスタンスに固有の値を持ちます。上の例のnameageがこれにあたります。
  • クラス変数: クラス定義の直下に書かれ、そのクラスから作られた全てのインスタンスで共有されます。
<!-- end list -->
>>> class Cat: ... # このクラスから作られるインスタンス全てで共有されるクラス変数 ... species = "ネコ科" ... ... def __init__(self, name): ... # このインスタンス固有のインスタンス変数 ... self.name = name ... >>> tama = Cat("タマ") >>> mike = Cat("ミケ") >>> >>> # インスタンス変数はそれぞれ異なる >>> tama.name 'タマ' >>> mike.name 'ミケ' >>> >>> # クラス変数は共有されている >>> tama.species 'ネコ科' >>> mike.species 'ネコ科' >>> >>> # クラス変数を変更すると、全てのインスタンスに影響する >>> Cat.species = "イヌ科?" >>> tama.species 'イヌ科?' >>> mike.species 'イヌ科?'

メソッドの定義

クラス内で定義された関数をメソッドと呼びます。メソッドは、そのクラスのインスタンスが持つべき振る舞いを定義します。

メソッドを定義する際も、最初の引数には必ずselfを指定する必要があります。これにより、メソッド内からselfを通じてインスタンス変数にアクセスできます。

>>> class Dog: ... def __init__(self, name): ... self.name = name ... ... # barkというメソッドを定義 ... # selfを介してインスタンス変数nameにアクセスする ... def bark(self): ... return f"{self.name}: ワン!" ... >>> pochi = Dog("ポチ") >>> >>> # メソッドを呼び出す >>> pochi.bark() 'ポチ: ワン!'

継承

継承は、あるクラス(親クラス、スーパークラス)の性質を、新しいクラス(子クラス、サブクラス)が引き継ぐ仕組みです。コードの再利用性を高めるOOPの重要な概念です。

Pythonではclass 子クラス名(親クラス名):という構文で継承を表現します。

子クラスで親クラスのメソッドを上書き(オーバーライド)したり、super()関数を使って親クラスのメソッドを呼び出したりすることもできます。特に、子クラスの__init__で親クラスの__init__を呼び出すのは一般的なパターンです。

>>> # 親クラス >>> class Animal: ... def __init__(self, name): ... print("Animalの__init__が呼ばれました") ... self.name = name ... ... def eat(self): ... return f"{self.name}は食事中です。" ... >>> # Animalクラスを継承した子クラス >>> class Dog(Animal): ... def __init__(self, name, breed): ... print("Dogの__init__が呼ばれました") ... # super()で親クラスの__init__を呼び出し、nameを初期化 ... super().__init__(name) ... self.breed = breed # Dogクラス独自のインスタンス変数を追加 ... ... # Dogクラス独自のメソッド ... def bark(self): ... return "ワン!" ... >>> # Dogクラスをインスタンス化 >>> my_dog = Dog("ポチ", "柴犬") Dogの__init__が呼ばれました Animalの__init__が呼ばれました >>> >>> # 親クラスのメソッドも使える >>> my_dog.eat() 'ポチは食事中です。' >>> >>> # もちろん子クラス独自のメソッドも使える >>> my_dog.bark() 'ワン!' >>> >>> # 親クラスと子クラス両方で初期化されたインスタンス変数を持つ >>> my_dog.name 'ポチ' >>> my_dog.breed '柴犬'

基本的なマジックメソッド (`__str__`, `__repr__`)

__init__のように、__(ダブルアンダースコア)で囲まれたメソッドはマジックメソッド(または特殊メソッド)と呼ばれます。これらは、特定の操作(print()での表示や演算子の使用など)に対応して、Pythonが裏側で自動的に呼び出す特別なメソッドです。

特に重要なマジックメソッドとして__str____repr__があります。

  • __str__: print()関数やstr()でオブジェクトが呼ばれたときに使われます。人間が読みやすい、非公式な文字列表現を返すことを目的とします。
  • __repr__: repr()関数やREPL環境で変数を評価したときに使われます。開発者向けで、そのオブジェクトを再生成できるような、公式で曖昧さのない文字列表現を返すことが理想です (eval(repr(obj)) == objとなるのが望ましい)。
<!-- end list -->
>>> class Person: ... def __init__(self, name, age): ... self.name = name ... self.age = age ... ... # print()で表示したときの振る舞いを定義 ... def __str__(self): ... return f"名前: {self.name}, 年齢: {self.age}" ... ... # REPLでの評価やrepr()での振る舞いを定義 ... def __repr__(self): ... return f"Person(name='{self.name}', age={self.age})" ... >>> bob = Person("ボブ", 30) >>> >>> # print()は__str__を呼び出す >>> print(bob) 名前: ボブ, 年齢: 30 >>> >>> # str()も__str__を呼び出す >>> str(bob) '名前: ボブ, 年齢: 30' >>> >>> # REPLで変数をそのまま評価すると__repr__が呼び出される >>> bob Person(name='ボブ', age=30) >>> >>> # repr()も__repr__を呼び出す >>> repr(bob) "Person(name='ボブ', age=30)"

もし__str__が定義されていない場合、print()は代わりに__repr__を呼び出します。両方を定義しておくことで、オブジェクトの使われ方に応じた適切な文字列表現を提供できます。