Python ダックタイピング

Python学習【365日チャレンジ!】83日目のマスターU(@Udemy11)です。

ダックだかタッグだかわかりませんが、どんどん内容が難しくなってくるので、酒井さんの講座だけじゃなく、Webからどんどん情報を収集して学習内容をデコレーションしないと理解が追いつかなくなってきました。

シリコンバレーの現役エンジニア酒井さんの講座はこちら

Udemy講師 酒井さん プログラミング Python

学校の学習にしても、社会人の仕事に関しても、一つの情報源から答えを導き出すのではなく、いくつかの情報源を調べて答えを導き出す必要がありますよね。

集めた情報を噛み砕いて理解するための【情報編集力】が大切なので、しっかりと自分で考える力をつけていかないといけませんね。

それではさっそくPython学習をはじめましょう!

昨日の復習

昨日は、クラスをデータ構造体として取り扱う際の注意点について学習しました。

クラス内で定義した変数への外部からのアクセスをコントロールするために@propertyを使って、クラス変数を設定しましたが、クラスの外からクラス変数を直接上書きしてしまえるので、注意が必要でした。

class Person:
    def __init__(self, name='male'):
        self._gender = name

    @property
    def gender(self):
        return self._gender

p = Person()
print(p._gender)
print(p.gender)
p._gender = 'female'
print(p._gender)
print(p.gender)

出力結果

male
male
female
female

3行目、7行目までのように、_(アンダーバー)が一つであれば、クラス外からもアクセス可能でしたが、@propertyを使うことで元のgenderを読み込み属性にすることができました。

なので、クラス変数のgenderは、外部からは書き換えることはできません。
ただ、直接_genderを書き換えてしまうと、genderも上書きされてしまいましたね。

クラス外部から読み込みができないようにするためには、__(アンダーバーアンダーバー)をつけることでクラス変数にアクセスできなくなりました。

とはいっても、アンダーバー一つのときと同じように、直接__genderのように指定して変数を定義すると上書きされてしまいましたね。

通常は、インスタンスを作成したあとにクラス変数を作ることはあまりないのですが、上書き可能なので、すでにあるクラス変数を上書きしないように注意する必要がありました。

詳しい内容は、昨日の記事を参考にしてください。

それでは、本日のダックタイピングに入りましょう!

ダックタイピングとは

最初、タッグタイピングだと思ったんですよね。

違う別のクラス同士がタッグを組んでいるから【タッグタイピング】。

そうじゃなくて、ダック(Duck)タイピングで、アヒルさんやカモさんのことなんですよね。

Python ダックタイピング

名前がダックタイピングというくらいなので、もとは個別的・特殊的な事例から一般的・普遍的な規則・法則を見出そうとする帰納法の一つである【ダック・テスト】に関係しています。

ダック・テストとは

「農場のそばを歩いている鳥を見たとしよう。鳥は「鴨」という名札をつけてはいない。しかし、鳥は確かに鴨のように見える。また、その鳥は池に入り、鴨のように泳ぐ。そこでその鳥はくちばしを開き、鴨のように鳴く。ここまで来れば、鳥が名札をつけていようがいなかろうが、その鳥が鴨であるという結論にはとっくに達したであろう。」
という、ある者が認識可能な事象から、未知のものの真の姿を推し量ることができるというものである。

もうこの時点でわけがわからなくなってきちゃいますが、ここで思考停止に陥ってはいけません!

ダックテストをより単純にすると、

もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルに違いない

ということで、これをPythonのオブジェクトやメソッドに当てはめてみると、

もしもそのオブジェクトがDuckオブジェクトのようにWalkメソッドを使えたり、DuckオブジェクトのようにCryメソッドを使えたりするのなら、そのオブジェクトはDuckオブジェクトに違いない

これでもイマイチよくわからないと思うので、実際にコードを書いていきましょう。

コードを書いてみる

まずは、Carクラスに変数personを引数にしたrideメソッドを定義して、person.drive()メソッドを実行するコードを書きます。

class Car:
    def ride(self, person):
        person.drive()

次にdriveメソッドを定義したPersonクラスを上部に付け加えます。

class Person:
    def __init__(self, age=1):
        self.age = age

    def drive(self):
        if self.age >= 18:
            print("Yes! You can do it.")
        else:
            print("No! You can't drive")

class Car:
    def ride(self, person):
        person.drive()

この時点で、Personクラスを元に、オブジェクトであるpersonインスタンスを生成して、person.drive()を実行すると、No! You can't driveが出力されます。

person = Person()
person.drive()

素人的には、personインスタンスを生成してから、Carクラスのオブジェクトとして、carインスタンスを生成して、car.ride()を実行すれば、rideメソッドで指定している引数に、personが入って、No! You can't driveが出力されるかと思ってしまいました。

person = Person()
car = Car()
car.ride()

しかし、この時点で、Personオブジェクトのpersonと、rideメソッドの引数であるpersonはつながっていないので、ride(person)と記述して、引数にpersonオブジェクトをわたしてやる必要があります。

person = Person()
car = Car()
car.ride(person)

出力結果

No! You can't drive

これで人というオブジェクトが車に乗れるかどうかという判断を行えるようになりました。

ここで、Personクラスから、次のような2つのオブジェクトbabyインスタンスとadultインスタンスを生成して、car.ride()の引数にbabyadultを渡して実行すると、どうなるでしょう?

baby = Person(1)
adult = Person(18)
car = Car()
car.ride(baby)
car.ride(adult)

出力結果

No! You can't drive
Yes! You can do it.

たぶん、これでもすでにダックタイピングということになると思います。

つまり、Carの引数であるpersonがどのような引数であっても、人は人(DuckはDuck)としてdriveメソッドを扱えるということなので、この段階ですでにbabyadultも人というオブジェクトとして扱っているということです。

続いて、Personクラスを継承して、違う条件を持ったクラスを追加してみます。

違う条件のPersonを作る

18歳以上のAdultクラスと18歳以下のKidsクラスとを作ります。

class Person(object):
    def __init__(self, age=1):
        self.age = age

    def drive(self):
        if self.age >= 18:
            print("Yes! You can do it.")
        else:
            print("No! You can't drive.")

class Kids(Person):
    def __init__(self, age=1):
        if age < 18:
            super().__init__(age)
        else:
            raise ValueError

class Adult(Person):
    def __init__(self, age=18):
        if age >= 18:
            super().__init__(age)
        else:
            raise ValueError

class Car:
    def ride(self, person):
        person.drive()

tom = Kids()
mark = Adult()

car = Car()
car.ride(tom)
car.ride(mark)

出力結果

No! You can't drive.
Yes! You can do it.

Personクラスを継承したKidsクラスとAdultクラスを定義して、それぞれのインスタンスを作成し、car.ride()メソッドに引数として渡しています。

これが、Personクラスから作ったインスタンスを使う場合と何が違うかというと、条件を個別に指定できるかどうかの違いだと思うのですが、もし間違いだったら、コメントで教えてもらえると嬉しいです。

じっくり読み解く

酒井さんのダックタイピングに関するレクチャーを受講したときは、全く意味がわかりませんでした。

ほんと一度見ただけでは理解できないんです。

今回、最終的に、私が理解したダックタイピングは、

クラスが違っても、オブジェクトを切り替えて、同じメソッドを使える

というコードの書き方という感じです。

何がどうなっているのか、何度も見て、部分部分に分けて理解していくことで、なんとなく理解できるようになります。

さらに同じレクチャーを繰り返し受講し、理解を深めることが大切です。

まさに、【継続は力なり】ですね。

それでは、明日もGood Python!