6.3 継承
- 使いたい既存のクラスを指定し、追加、変更したい一部だけを定義する新しいクラスを作る。(継承)
- 新クラスでは追加、変更したい部分だけを定義する。するとこの定義が実際に使われ、上書きされた古いクラスの動作は使われない。これをオーバーライドという。
- 元のクラスを親、スーパークラス、基底クラス、新しいクラスは子、サブクラス、派生クラスと呼ばれる。
- 子クラスは親クラスを専門特化したものである。
>>> class car():
... pass
...
# 親クラスの継承。()に親クラス名を入れる。
>>> class yugo(car):
... pass
...
>>> give_me_a_car=car()
>>> give_me_a_yugo=yugo()
>>> class car():
... def exclaim(self):
... print("I am a car!")
...
>>> class yugo(car):
... pass
...
>>> give_me_a_car=car()
>>> give_me_a_yugo=yugo()
>>> give_me_a_car.exclaim()
I am a car!
>>> give_me_a_yugo.exclaim()
I am a car!
メソッドのオーバーライド
- 新しいクラスは親クラスから全てのものを継承する。
# carクラスを定義
>>> class car():
... def exclaim(self):
... print("I am a car")
...
# yugoクラスを定義
>>> class yugo(car):
... def exclaim(self):
... print("I am a Yugo! Much like a Car,but more Yugo-ish")
...
# オブジェクトを作成
>>> give_me_a_car=car()
>>> give_me_a_yugo=yugo()
# (オブジェクト.メソッド)でインスタンスを呼び出し
>>> give_me_a_car.exclaim()
I am a car
>>> give_me_a_yugo.exclaim()
I am a Yugo! Much like a Car,but more Yugo-ish
>>> class Person():
... def __init__(self,name):
... self.name=name
...
# Personクラスを継承して親と同じ引数を取っているが、オブジェクトに格納されているnameの値をオーバーライドしている。
>>> class MDPerson(Person):
... def __init__(self,name):
... self.name="Docter"+name
...
>>> class JDPerson(Person):
... def __init__(self,name):
... self.name=name+"Esquire"
...
# オブジェクト作成
>>> person=Person("Fudd")
>>> docter=MDPerson("Fudd")
>>> lowyer=JDPerson("Fudd")
>>> print(person.name)
Fudd
>>> print(docter.name)
DocterFudd
>>> print(lowyer.name)
FuddEsquire
6.5 メソッドの追加
- 子クラスは親クラスになかったメソッドも追加できる。
>>> class car():
... def exclaim(self):
... print("I am a car")
...
>>> class yugo(car):
... def exclaim(self):
... print("I am a Yugo! Much like a Car,but more Yugo-ish")
# 子クラスだけにneed_a_push()メソッドを追加
... def need_a_push(self):
... print("A little help here?")
...
>>> give_me_a_car=car()
>>> give_me_a_yugo=yugo()
>>> give_me_a_yugo.need_a_push()
A little help here?
# 汎用的なCarオブジェクトは反応できない。
>>> give_me_a_car.need_a_push()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'car' object has no attribute 'need_a_push'
6.6 superによる親への支援要請
- 子クラスから親メソッドを呼び出すことができる。
# 親クラス定義
>>> class Person():
... def __init__(self,name):
... self.name=name
...
# 子クラス定義
# 子クラスで__init__()を定義するということは親クラスの__init__()メソッドを置き換えようとしており、親クラスバージョンは自動的に呼び出されなくなる。そのため、親クラス場0ジョンを呼び出すためには明示的に呼び出さなければならない。
# email引数を追加していることに注意
# super()が親クラスのPersonの定義を取り出す。
>>> class EmailPerson(Person):
... def __init__(self,name,email):
... super().__init__(name)
... self.email=email
...
# EmailPersonクラスのオブジェクトを作成
>>> bob=EmailPerson("Bob Frapples","bob@flapples.com")
>>> bob.name
'Bob Frapples'
>>> bob.email
'bob@flapples.com'
- super()を使ってPersonにただのPersonオブジェクトと同じ仕事をさせている。
6.7 selfの自己弁護
# carオブジェクトのクラス(Car)を探し出す。
# Carクラスのexclaim()メソッドにself引数ととしてcarオブジェクトを渡す。
>>> car=car()
>>> car.exclaim()
I am a car
# 直接Carクラスにexclaimメソッドでcarオブジェクトを渡すことで上記の結果と同じ結果が得られる。
>>> Car.exclaim(car)
I am a car
6.8 プロパティによる属性値の取得と設定
-
オブジェクト指向言語の中には、外部から直接アクセスできない非公開というオブジェクト属性をサポートしているものがある。非公開属性の値を読み書きできるようにするためには、セッター、ゲッターメソッドが必要。
-
ゲッターとセッターについて以下のように理解した。
- ゲッター→値を取得するもの
- セッター→値を代入するときに使うもの
# 属性としてhidden_nameという値をモノだけを持つDuckクラスを定義
>>> class Duck():
... def __init__(self,input_name):
... self.hidden_name=input_name
... def get_name(self):
... print("inside the getter")
... return self.hidden_name
... def set_name(self,input_name):
... print("inside the setter")
... self.hidden_name=input_name
# 二つのメソッドをnameというプロパティのセッター、ゲッターとして定義。これによりDuckオブジェクトのnameを参照すると実際にはget_name()メソッドが呼び出されるようになる。
# propertyを使うと、属性にアクセスされた時の動きをコントロールできる。
... name=property(get_name,set_name)
...
>>> fowl=Duck("Howard")
>>> fowl.name
inside the getter
'Howard'
# 直接get_name()の呼び出しも可能
>>> fowl.get_name()
inside the getter
'Howard'
# nameプロパティに値を代入すると、set_nameメソッドが呼び出される。
>>> fowl.name="Duffy"
inside the setter
>>> fowl.name
inside the getter
'Duffy'
# 直接set_name()の呼び出しも可能
>>> fowl.set_name("Duffy")
inside the setter
>>> fowl.name
inside the getter
'Duffy'
- プロパティはデコレータで定義可能。
- @propertyはゲッターメソッドの前につけるデコレータ
- @メソッド名.setterはセッターメソッドの前につけるデコレータ
- ゲッターとセッターのメソッド名は同じでなければならない。
>>> class Duck():
... def __init__(self,input_name):
... self.hidden_name=input_name
# @propertyを使うことで属性nameを使わずに実装でき、属性への参照ができるようになる。
... @property
... def name(self):
... print("inside the getter")
... return self.hidden_name
... @name.setter
... def name(self,input_name):
... print("inside the setter")
... self.hidden_name=input_name
...
>>> fowl=Duck("Howard")
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name="Donald"
inside the setter
>>> fowl.name
inside the getter
'Donald'
# プロパティは計算された値も参照できる。
>>> class Circle():
... def __init__(self,radius):
... self.radius=radius
... @property
... def diameter(self):
... return 2 * self.radius
...
>>> c=Circle(5)
>>> c.radius
5
>>> c.diameter
10
# radius属性はいつでも書き換え可能
>>> c.radius=7
>>> c.diameter
14
# プロパティのセッターを指定しなければ外部からプロパティの値を書き換えることはできない。(読み取り専用に便利)
>>> c.diameter=20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
6.9 非公開属性のための名前のマングリング
- クラスの定義の外からは見えないようにすべき属性の命名方法がある。(属性の先頭に二つのアンダースコア(__)をつける)
>>> class Duck():
... def __init__(self,input_name):
... self.__name=input_name
... @property
... def name(self):
... print("inside the getter")
... return self.__name
... @name.setter
... def name(self,input_name):
... print("inside the setter")
... self.__name=input_name
...
>>> fowl=Duck("Howard")
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name="Donald"
inside the setter
>>> fowl.name
inside the getter
'Donald'
# __name属性にはアクセスできない
>>> fowl.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute '__name'
# 名前のマングリング
>>> fowl._Duck__name
'Donald'
6.10 メソッドのタイプ
- 一部のデータ(属性)と関数(メソッド)はクラス自体の一部であり、それ以外のデータと関数がクラスから作られたオブジェクトの一部である。
- クラス定義の中でメソッドの第一引数がselfになっていたらそれはインスタンスメソッドである。メソッドが呼び出されると、Pythonはメソッドにオブジェクトを与える。
- クラスメソッドはクラス全体に影響を与える。クラスに加えた変更は全てのオブジェクトに影響を与える。クラス定義の中で、@classmethodというデコレータを入れると、その次の関数はクラスメソッドになる。このメソッドの第一引数はクラス自体となる。Pythonの伝統では、この引数をclsと呼ぶことになっている。
- 静的メソッドはクラスにもオブジェクトにも影響を与えない。@staticmethodデコレータを付けることで定義できる。第一引数としてselfやclsを取らない。
# self.count(これでは、オブジェクトインスタンスの属性になってしまう)ではなく、A.count(クラス属性)を参照していることに注意。kids()メソッドではcls.countを使ったが、A.countでも良い。
>>> class A():
... count=0
... def __init__(self):
... A.count+=1
... def exclaim(self):
... print("I am an A!")
... @classmethod
... def kids(cls):
... print("A has",cls.count,"little objects.")
...
>>> e=A()
>>> b=A()
>>> w=A()
>>> A.kids()
A has 3 little objects.
# 静的メソッドの定義
>>> class CoyoteWeapon():
... @staticmethod
... def commercial():
... print("This CoyoteWeapon has been brought to you by Acme")
...
# クラスからオブジェクトを作らずに実行できる。
>>> CoyoteWeapon.commercial()
This CoyoteWeapon has been brought to you by Acme
6.11 ダックタイピング
>>> class Quote():
... def __init__(self,person,words):
... self.person=person
... self.words=words
... def who(self):
... return self.person
... def says(self):
... return self.words + "."
...
# 初期化の方法は親クラスと変わらないので__init__()メソッドのオーバーライドはしていない。
>>> class QuestionQuote(Quote):
... def says(self):
... return self.words + "?"
...
>>> class ExclamationQuote(Quote):
... def says(self):
... return self.words + "!"
...
>>> hunter=Quote("E","I")
>>> print(hunter.who(),"says:",hunter.says())
E says: I.
>>> hunted1=QuestionQuote("E","I")
>>> print(hunted1.who(),"says:",hunted1.says())
E says: I?
>>> hunted2=ExclamationQuote("E","I")
>>> print(hunted2.who(),"says:",hunted2.says())
E says: I!
# Pythonはwho(),says()メソッドを持ちさえすればどのようなオブジェクトであっても(継承なしでも)共通のインターフェイスを持つオブジェクトとして扱うことができる。
>>> class BBrook():
... def who(self):
... return "Brook"
... def says(self):
... return "Babble"
...
>>> brook=BBrook()
>>> def who_says(obj):
... print(obj.who(),"says",obj.says())
...
>>> who_says(hunter)
E says I.
>>> who_says(hunted1)
E says I?
>>> who_says(hunted2)
E says I!
>>> who_says(brook)
Brook says Babble
6.12 特殊メソッド
- init()は渡された引数を使ってクラス定義から新しく作成されたオブジェクトを初期化する。
>>> class Word():
... def __init__(self,text):
... self.text = text
... def equals(self,word2):
... return self.text.lower() == word2.text.lower()
...
>>> first=Word("ha")
>>> second=Word("HA")
>>> third=Word("eh")
>>> first.equals(second)
True
>>> first.equals(third)
False
# equals()メソッドを__eq__()という特殊名に変更する。
>>> class Word():
... def __init__(self,text):
... self.text = text
... def __eq__(self,word2):
... return self.text.lower() == word2.text.lower()
...
>>> first=Word("ha")
>>> second=Word("HA")
>>> third=Word("eh")
>>> first==second
True
>>> first==third
False
>>> first=Word("ha")
>>> first
<__main__.Word object at 0x10ddeec50>
>>> print(first)
<__main__.Word object at 0x10ddeec50>
# Wordクラスに__str__()または __repr__()メソッドを追加して表示を見やすくする。
>>> class Word():
... def __init__(self,text):
... self.text = text
... def __eq__(self,word2):
... return self.text.lower() == word2.text.lower()
... def __str__(self):
... return self.text
... def __repr__(self):
... return "Word(" + self.text + ")"
...
>>> first=Word("ha")
# __repr__()を使う
>>> first
Word(ha)
# __str__()を使う
>>> print(first)
ha
- 比較のための特殊メソッド
| メソッド | 意味 |
|:-----------------|------------------:|:------------------:|
|_qe_(x,y) | x == y |
|_ne_(x,y) | x != y |
|_lt_(x,y) | x < y |
|_gt_(x,y) | x > y |
|_le_(x,y) | x <= y |
|_ge_(x,y) | x >= y |
- 算術計算のための特殊メソッド
| メソッド | 意味 |
|:-----------------|------------------:|:------------------:|
|_add_(x,y) | x + y |
|_sub_(x,y) | x - y |
|_mul_(x,y) | x * y |
|_floordiv_(x,y) | x // y |
|_truediv_(x,y) | x / y |
|_mod_(x,y) | x % y |
|_pow_(x,y) | x ** y |
- その他の特殊メソッド
- 対話型インタープリタは__repr__()関数を使って変数をエコー出力している。
- str()または repr()を定義し忘れるとPythonが定義しているオブジェクトのデフォルトの文字列バージョンが使われる。
| メソッド | 意味 |
|:-----------------|------------------:|:------------------:|
|_str_(x) | str(x)|
|_repr_(x) | repr(x) |
|_len_(x) | len(x) |
6.13 コンポジション
- 継承よりもコンポジションや集約の方が理にかなっている場合がある。
# bill(クチバシ)、tail(尻尾)オブジェクトを作り、それを新しいDuckクラスに与える。
>>> class Bill():
... def __init__(self,description):
... self.description=description
...
>>> class Taill():
... def __init__(self,length):
... self.length=length
...
>>> class Duck():
... def __init__(self,bill,tail):
... self.bill=bill
... self.tail=tail
... def about(self):
... print("This duck has a",self.bill.description,"bill and a",self.tail.length,"tail")
...
>>> tail=Taill("long")
>>> bill=Bill("wide orange")
>>> duck=Duck(bill,tail)
>>> duck.about()
This duck has a wide orange bill and a long tail
6.14 モジュールではなくクラスとオブジェクトを使うべきなのはいつか。
- クラスは継承をサポートするが、モジュールはサポートしない。
- 動作(メソッド)は同じだが、内部状態(属性)は異なる複数のインスタンスを必要とするときにはオブジェクトが最も役に立つ。
- 何か一つだけ必要とするときには、モジュールが良い。Pythonモジュールはプログラムに何度参照されても、1個のコピーしかロードされない。
- 複数の値を持つ変数があり、これらを複数の関数に引数として渡せるときに、それをクラスとして定義した方が良い場合がある。
6.14.1 名前付きタプル
-
名前付きタプルはタプルのサブクラスで、位置([offset])だけでなく名前の(.name)でも値にアクセスできる。
-
namedtuple関数には、二つの引数を渡す。
- 名前
- 空白区切りのフィールド文字列
-
名前付きタプルはPythonが自動的に供給するデータ構造ではないので、使うためにはモジュールをダウンロードしなければならない。
-
名前付きタプルの長所
- イミュータブルなオブジェクトのように振る舞う
- オブジェクトよりも空間的、時間的に効率がよい、
- 辞書スタイルの角かっこではなく、ドット記法で属性にアクセスできる。
- 辞書のキーとして使える。
>>> from collections import namedtuple
>>> Duck=namedtuple("Duck","bill tail")
>>> duck=Duck("wide orange","long")
# 名前付きタプル作成
>>> duck
Duck(bill='wide orange', tail='long')
# (.name)で値にアクセスできる。
>>> duck.bill
'wide orange'
>>> duck.tail
'long'
# 名前付きタプルは辞書からでも作れる。
>>> parts={"bill":"Wide orange","tail":"long"}
# キーワード引数の辞書化により、parts辞書のキーと値を抽出してDuck()に引数として渡す。
>>> duck2=Duck(**parts)
>>> duck2
Duck(bill='Wide orange', tail='long')
# 名前付きタプルはイミュータブルだが、1個以上のフィールド交換した別の名前付きタプルを返すことができる。
>>> duck3=duck2._replace(tail="M",bill="X")
>>> duck3
Duck(bill='X', tail='M')
# duckは辞書としても定義できる。
>>> duck_dict={"bill":"A","tail":"I"}
>>> duck_dict
{'bill': 'A', 'tail': 'I'}
# 辞書にはフィールドを追加できる。
>>> duck_dict["color"]="green"
>>> duck_dict
{'bill': 'A', 'tail': 'I', 'color': 'green'}
# 名前付きタプルには追加できない。
>>> duck_dict.color="green"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'color'
6.15 復習課題
6-1 中身のないThingというクラスを作り、表示しよう。次にこのクラスからexampleというオブジェクトを作り、これも表示しよう。表示される値は同じか、異なるか。
>>> class Thing():
... pass
...
>>> example=Thing()
>>> print(Thing)
<class '__main__.Thing'>
>>> print(example)
<__main__.Thing object at 0x10de692d0>
6-2 Thing2という新しいオブジェクトを作り、lettersというクラス属性に"abc"という値を代入して、lettersを表示しよう。
>>> class Thing2():
... letters="abc"
...
>>> print(Thing2.letters)
abc
6-3 更にもう一つクラスを作ろう。名前はThing3。今度は、lettersというインスタンス(オブジェクト)属性に "xyz"を代入し、lettersを表示しよう。
>>> class Thing3():
... def __init__(self):
... self.letters="xyz"
...
>>> something=Thing3()
>>> print(something.letters)
xyz
6-4 name,symbol,numberというインスタンス属性を持つElementというクラスを作り、"Hydogen","H","1"という値を持つこのクラスのオブジェクトを作ろう。
>>> class Element():
... def __init__(self,name,symbol,number):
... self.name=name
... self.symbol=symbol
... self.number=number
...
>>> H=Element("Hydogen","H","1")
6-5 "name":"Hydrogen","symbol":"H","number":"1"というキー/値ペアを持つ辞書を作ろう。次にこの辞書を使ってElementクラスのhydrogenオブジェクトを作ろう。
>>> dictionary={"name":"Hydrogen","symbol":"H","number":"1"}
>>> hydrogen=Element(dictionary["name"],dictionary["symbol"],dictionary["number"])
>>> hydrogen.name
'Hydrogen'
>>> hydrogen.symbol
'H'
>>> hydrogen.number
'1'
# 辞書のキー名が__init()__の引数と一致しているので、辞書から直接オブジェクトを初期化できる。
>>> hydrogen=Element(**dictionary)
>>> hydrogen.name
'Hydrogen'
>>> hydrogen.symbol
'H'
>>> hydrogen.number
'1'
6-6 Elementクラスのために、オブジェクト属性(name,symbol,number)の値を表示するdump()というメソッドを定義しよう。この新しい定義からhydrogenオブジェクトを作り、dump()を使って属性を表示しよう。
>>> class Element():
... def __init__(self,name,symbol,number):
... self.name=name
... self.symbol=symbol
... self.number=number
... def dump(self):
... print("name=%s,symbol=%s,number=%s"%(self.name,self.symbol,self.number))
...
>>> hydrogen=Element("A","B","C")
>>> hydrogen.dump()
name=A,symbol=B,number=C
6-7 print(hydrogen)を呼び出そう。次に、Elementの定義の中でdumpというメソッド名を__str__に変更し、新しい定義のもとでhydrogenオブジェクトを作って print(hydrogen)をもう一度呼び出そう。
>>> print(hydrogen)
<__main__.Element object at 0x10de9e510>
>>> class Element():
... def __init__(self,name,symbol,number):
... self.name=name
... self.symbol=symbol
... self.number=number
... def __str__(self):
... return ("name=%s,symbol=%s,number=%s"%(self.name,self.symbol,self.number))
...
>>> hydrogen=Element("A","B","C")
# print関数は、オブジェクトの__str__()メソッドを呼び出して文字列表現を手に入れる。これがなければ、<__main__.Element object at 0x10de9e510>をのような文字列を返す。
>>> print(hydrogen)
name=A,symbol=B,number=C
6-8 Elementを書き換え、name,symbol,number属性を非公開にしよう。そしてそれぞれについて値を返すゲッターを定義しよう。
>>> class Element():
... def __init__(self,name,symbol,number):
... self.__number=number
... self.__symbol=symbol
... self.__name=name
... @property
... def name(self):
... return self.__name
... @property
... def symbol(self):
... return self.__symbol
... @property
... def number(self):
... return self.__number
...
>>> hydrogen=Element("A","B","C")
>>> hydrogen.name
'A'
>>> hydrogen.symbol
'B'
>>> hydrogen.number
'C'
6-9 Bear,Rabbit,Octothorpeの3つのクラスを定義しよう、唯一のメソッド、eats()を定義する。
>>> class Bear():
... def eats(self):
... return "berries"
...
>>> class Rabbit():
... def eats(self):
... return "clover"
...
>>> class Octothorse():
... def eats(self):
... return "campers"
...
# オブジェクト作成は関数のように呼び出し
>>> a=Bear()
>>> b=Rabbit()
>>> c=Octothorse()
>>> print(a.eats())
berries
>>> print(b.eats())
clover
>>> print(c.eats())
campers
6-10 Laser,Claw,SmartPhoneクラスを定義しよう。3つのクラスは唯一のメソッドとしてdoes()を持っている。
>>> class Laser():
... def does(self):
... return "disintegrate"
...
>>> class Claw():
... def does(self):
... return "crush"
...
>>> class SmartPhone():
... def does(self):
... return "ring"
...
# コンポジション
>>> class Robot():
... def __init__(self):
... self.laser=Laser()
... self.claw=Claw()
... self.smartphone=SmartPhone()
... def does(self):
... return """I have many attachments:
... My laser,to %s.
... My claw,to %s.
... My smartphone,to %s.""" %(
... self.laser.does(),
... self.claw.does(),
... self.smartphone.does())
...
>>> ro=Robot()
>>> print(ro.does())
I have many attachments:
My laser,to disintegrate.
My claw,to crush.
My smartphone,to ring.
>>>
感想
セッター、ゲッターが本だけでは分からなかったが、調べて理解できた。
クラスとオブジェクトは重要単元であると感じていたためしっかり理解するために時間を結構かけた。
明日からまた頑張ろう。
参考文献
「Bill Lubanovic著 『入門 Python3』(オライリージャパン発行)」