05. オブジェクト指向プログラミング

5.1 関数

5.1.1 組み込み関数

Pythonインタプリタには数多くの関数と型が標準的に組み込まれています。なぜ「関数や型」とこの2つを同様に扱えるかというと,オブジェクト指向プログラミングの見地からすると,関数も型もオブジェクトだからです。

まずは簡単な組み込み関数(built-in functions)の例としてデータ型を確認するtype 関数を使ってみましょう。前回作成した like というオブジェクトは,Python インタプリタの終了とともに消えてしまっているので,第4回のコード18を再度実行してから,下記の コード1 を実行しましょう。同様に,必要だと考えるオブジェクトはあらかじめ作成しておきましょう。

コード 1

type(like)

インデックスの項目のときに作ったn の型は何だったでしょうか。下記は,コード2です。

コード 2

n = ["1番","2番","3番"]
type(n)

Pythonの標準ライブラリ(standard library)には,type()のような組み込み関数から組み込み型,そしてmath のような標準モジュールまでが含まれています。

例えば math というモジュールにはsqrt という関数が含まれていて,平方根を計算したい場合には math モジュールの sqrt を実行することになります。ライブラリを使用するには import による読込が必要です。下記は,コード3です。

コード 3

import math
math.sqrt(36)

モジュールには Python に組み込まれている標準ライブラリのモジュールの他に,外部ライブラリ(third-party library)のモジュールがあります。外部ライブラリを使用する場合にはライブラリの読込の前にライブラリのインストールが必要です。

5.1.2 ライブラリの読込

Pythonでは,インタープリタのセッションを再開するたびに自分の使いたいライブラリ,パッケージやモジュールを読み込みます。ライブラリはパッケージやモジュールから構成されていて,パッケージはモジュールの集合体です。

外部ライブラリは,読み込む前にpipコマンドでインストールしなければならないのですが,よく使われるライブラリは標準的にColabにプリインストールされています。たとえば,pandasというColabに組み込まれているライブラリの場合,もっとも簡素な読込の形が下記のコード4です。

コード 4

import pandas

しかし,一般的には別名をつけておきます。たとえば,pandasにはpdという別名をつけて読み込みむのが,下のコード5です。

コード 5

import pandas as pd

本章あたりの段階ででPythonコードの実行が上手くいかない場合,原因は大抵,以下のどれかです:

  1. スペルミス(特に全角になってしまっている記号に気をつける)
  2. 必要なオブジェクトを作り直す必要がある
  3. 必要なライブラリが読み込まれていない

5.1.3 モジュール関数

モジュール内のクラスや関数を使うときのドットの使い方です。たとえば,pandas モジュールで定義されているExcelの生データのようなデータの型であるDataFrame クラスをpandas.DataFrame で呼び出して用いることができます。

また,モジュール内の関数 read_csvpandas.read_csv で呼び出せば,その関数を使って csv ファイルを読み込みます。いちいち pandas と入力するのは面倒なので,あらかじめ付けておいた別名を使い,pd.DataFramepd.read_csv と短く表記するのがふつうです。

5.1.4 関数の定義

関数を自分で作成するときには,def 文を用いて括弧 () 内に仮引数を入れて定義し, return で戻り値を指定します。

日本の小学生の年次を引数に与えると年度はじめの年齢に換算するage という関数を自分で作るとしましょう。関数を定義するための def 文,それに続くのが新しく定義する関数名,それに続く (丸括弧) 内のものは仮引数といいます。仮のものですので,わかりやすければどんな名前でも構いません。

下記のコード6では,ageyear が自らが付けた任意の名前で,age が関数,year が仮引数です。インデントされた行の中で,仮引数を使って関数を定義します。

コード 6

def age(year):
    age = year + 5 
    return age

これで age という関数が定義できたので,コード7で引数を与えてみましょう。このように実際に関数を使用するときに与える引数のことを実引数といいます。

コード 7

age(4)

下記 コード8 のように,print で結果を表示させたい場合には,return は必要ありません。

コード 8

def age(year):
    age = year + 5
    print(f"小学{year}年生は{age}歳です。")

今度はコード9で2という引数を関数に渡してみましょう。関数に引数を渡す場合には,仮引数=実引数という書き方もよく使われます。これをキーワード引数といいます。キーワード引数がよく使われるのは,それにより引数の順番を変えたり,複数ある引数の一部だけを指定したりすることができるからです。

コード 9

age(year = 2)

5.2 オブジェクトとクラス

5.2.1 オブジェクトとクラス

関数には,単独で使える print のような関数と,オブジェクトに属する関数があり,後者をメソッドといい,オブジェクトにドットと関数を付けて使います。

メソッドでよく使われるのが,たとえばappend です。リストの関数で,リストに要素を追加(append)するメソッドです。

たとえば,下記 コード10 の2行目の n.append は,2つの要素を持つリストを格納した n という変数に .append とつけてメソッドを呼び出し,n の要素に(引数)を追加します。

コード 10

n = ["1番","2番"]
n.append("3番")
print(n)

5.2.2 クラスとインスタンス

組み込みデータ型のリストには append という関数が定義されていましたが,自分で クラスを定義 してデータと動作を組み合わせることができます。

下記 コード11 を実行すると People というクラスが定義されます。クラス内で作るオブジェクトをクラスオブジェクトと呼び,標準の組み込み型を参照する属性参照とインスタンス化の操作が可能になっています。クラスオブジェクトとして関数を作成する コード11 では,def を用いて lottery などの関数オブジェクトを作成しています。

コード 11

class People():
    def __init__(self, id, name, age): 
        self.id = id   
        self.name = name
        self.age = age
    def lottery(self):
        print(self.name + "さんに宝くじが当たりました! ")
    def bee(self):
        print(self.name + "さんは蜂に刺されました。")

上記 コード11 を実行後,type 関数でクラスオブジェクトである People.lottery の型を確かめると,属性参照により function と表示されるはずです。

次に インスタンス化 を下記 コード12 で見てみます。インスタンスを作るには,クラス名を関数のように丸カッコをつけて生成します。クラスで __init__(self) メソッドを定義しておけば,self に続く引数が,インスタンス化の丸カッコ内の引数を受け取ってくれます。インスタンス化により生成されたオブジェクトを代入した person1 等がインスタンスです。

コード 12

person1 = People("001", "ささき", "30")
person2 = People("002", "たけいし", "45")

インスタンスオブジェクトが理解する属性参照にはデータとメソッドがあります。type 関数で person1.name の型を調べると,データが文字列であることがわかるはずです。下記の コード13では,インスタンスがクラスの関数オブジェクトを参照しています。

コード 13

person1.lottery() 
person2.bee()

コード13 の実行後,type 関数で person1.lottery の型を調べると,メソッドであることがわかるはずです。person1.id はインスタンス変数で,各インスタンスの固有のデータのものですが,下記 コード14People.total はクラス変数です。

コード 14

class People():
    total = 0
    def __init__(self, id, name, age): self.id = id
        self.name = name
        self.age = age
        People.total += 1
    def lottery(self):
        print(self.name + "さんに宝くじが当たりました! ")
    def bee(self):
        print(self.name + "さんは蜂に刺されました。")
    def number(self):
        print(f"人数は{People.total}人です 。")

下記は コード15 です。

コード 15

person1 = People("001", "ささき"", "30") 
person1.number()

下記は コード16 です。

コード 16

pperson2 = People("002", "たけいし","45") 
person2.number()

下記は コード17 です。

コード 17

person1.number()

5.2.3 名前空間とスコープ

オブジェクトに続いてドット(.)の後に続くのは属性の名前です。属性参照によってその名前で参照する範囲(Scope)にある辞書のことを名前空間といいます。上記のクラス定義の後に,Local名前空間を locals() で確認してみてください。名前空間が辞書であるという感覚がわかると思います。辞書というのは,コロンの前後で,名前(キー)とその名前に格納されているデータなどの属性の中身(値)を対応させている,キー:値という構造です。

People というクラス内で total という属性を作成したとしても,そのクラスの外部からは total の中身を参照できません。それは total がクラス内のLocalな名前空間に追加されているからです。このように,名前から属性を参照する範囲には,内側から,Local, Enclosing, Global, Built-inがあります。Pythonを立ち上げなおすとモジュール内を参照範囲とするGlobalがLocalであるレベルに戻り,それ以下の,自分で定義した People のようなクラスは削除されています。

5.2.4 継承・カプセル化・ポリモーフィズム

すでに Human というクラスがあるとしましょう。People クラスを定義する際,class People ():ではなく,以下のようにするとHuman クラスを People クラスに受け継ぐことができます。このPythonの特徴を 継承 といいます。

class People(Human):

次は,カプセル化についてです。下記 コード18 では,名前の前にアンダースコアが1つ付いているものと2つ付いているものがあります。これらは name mangling と呼ばれ,そもそも名前を上書きしてしまう事故を防ぐために設計されたものですが,この機能は変数をプライベート変数にしたいことを表現するカプセル化にも使われます。ただし,どちらも完全なアクセス防止にはならないことに注意してください。

コード 18

class People():
      def __init__(self, id, name, age):
          self.id = id 
          self._name = name 
          self.__age = age

下記は コード19 です。

コード 19

pcerson1 = People("001", "ささき", 30) 
print(person1.id) 
print(person1._name) 
print(person1.__age)

さて,ポリモーフィズムについてです。以下の コード20 では,同じ関数 year_age を使いながら,.grade メソッドを通じてインスタンス(pupil1 など)ごとに異なる機能を持たせています。この性質をポリモーフィズムと呼びます。

コード 20

class School:
    def __init__(self, name, year): 
        self.name = name
        self.year = year

    def grade(self):  
        return f"{self.name}は{self.year}年生"

class Elementary(School): 
    def grade(self):
        age = self.year + 5 
        return super().grade() + f"で{age}歳です。"

class Juniorhigh(School): 
    def grade(self):
        age = self.year + 11 
        return super().grade() + f"で{age}歳です。"

class High(School): 
    def grade(self):
        age = self.year + 14 
        return super().grade() + f"で{age}歳です。"

def year_age(x): 
    print(x.grade())

pupil1 = Elementary("はる", 3)
pupil2 = Juniorhigh("なつ", 3)
pupil3 = High("あ き", 3)

year_age(pupil1) 
year_age(pupil2) 
year_age(pupil3)