【デザインパターン with Python】1-4. Classを使ってみよう: アクセス修飾子とクラスメソッド

記事タイトルとURLをコピーする

はじめに

こんにちは。孔子の80代目子孫兼アプリケーションサービス部の孔です。前回の記事ではClassの定義方法、__init__、そしてselfについてみてみました。今回は前回に引き続き、Classの使い方をあれこれを見てみましょう。

今回紹介する概念はアクセス修飾子とクラスメソッドになります。

アクセス修飾子とは…の前に、まずはカプセル化とは

アクセス修飾子の話をする前に、まずは「カプセル化」とは何かを説明します。カプセル化とは、「外部に詳細のロジックなどを公開せずに隠蔽すること」を指します。前回作ったのTableクラスを見てみましょう。

import time
 
class Table:
    # テーブルごとの注文に関するオブジェクト
    def __init__(self, table_num: int):
        # テーブル番号, 注文した料理, 滞在時間のデータを作る
        self.table_num = table_num
        self.order = []
        self.start_time = time.time()
 
    def add_order(self, order: list):
        self.order.extend(order)
        return None
 
    def get_stay_time(self):
        return time.time() - self.start_time
 
    def billing(self):
        amount = self.__calculate_amount()
        return amount
 
    def __calculate_amount(self):
        menu = Menu.get_menu()
        amount = 0
        for ordered in self.order:
            amount += menu[ordered]
        return amount
 
class Menu:
    # メニューに関するオブジェクト
    @classmethod
    def get_menu(cls):
        menu = {
            "beef": 1000,
            "chicken": 800,
            "pork": 900
        }
        return menu

この中で、billingメソッドは「お客様が注文した合計金額を返す」処理をしています。現実世界を考えてみましょう。ある店員がお会計のときに、「10番テーブルのお客様が注文した合計金額を知りたい!」となったときに、合計金額を取得する方法は2つあります。

  1. Menuという別のクラスの中にget_menuというメソッドがある。そこから各メニューの金額を取得する。メニューはdict型で実装されているので、get_menuからreturnされたデータに注文されたメニューをキーとして渡し、それぞれのメニューの金額を確認する。確認が終わったら金額を全部足す。その値が合計金額となる。取得完了。
  2. billingメソッドを実行する。取得完了。

どちらの方法で合計金額を取得している店員さんが嬉しいかは一目瞭然です。このように、合計金額を取得するためのメソッドが実装されていれば、その裏のロジックはどのようになっているのか知らなくてもとりあえず「テーブルの注文の合計金額を知りたければbillingメソッドを実行すること」だけ覚えておけば良いわけです。

このように、裏のロジックを外に出さず、複雑な処理を隠蔽することを「カプセル化」と言います。そしてそのカプセル化を実現するために利用されるのが「アクセス修飾子(Access Modifier)」となります。

アクセス修飾子(Access Modifier)とは

アクセス修飾子とは、「ある属性やメソッドがどこからアクセス可能かを制御するもの」になります。先ほど話したbilling__calculate_amountですが、店員はbillingだけ知っていればよくて、__calculate_amountメソッドは知らなくても良いわけですね。なのでbillingは外の世界(店員がアクセスできる世界)に公開して、__calculate_amountは外の世界からは見えないところに隠蔽したいです。

billingのように、どこからでもアクセス可能なメソッドや属性をpublicといい、__calculate_amountなど、外には後悔しないで隠蔽したいものをprivateと言います。(また、protectedというものもありますが、後ほど説明します)

publicprivateの大きな違いは、「隠蔽されているかどうか」になります。そのため、privateなメソッドや属性は外からは直接呼び出せるようにしたくないですね。その実装をする際に使用するのがアンダースコアになります。アンダースコアが先頭に二つ付いた属性やメソッドはprivateになり、外からはアクセスできないようになります。以下のコードをみてください。

class Test:
    def __init__(self, public_prop, private_prop):
        self.public_prop = public_prop
        self.__private_prop = private_prop
 
    def get_pricate_prop(self):
        return self.__private_prop
 
test = Test("public", "private")
 
test.public_prop  # "public"。アクセス可能
test.get_pricate_prop()  # "private"。get_pricate_propはpublicなメソッドなので、アクセス可能
test.__private_prop  # AttributeError: 'Test' object has no attribute '__private_prop':そんな属性ないよ!と言われる

簡潔に整理すると以下になります。

アクセス修飾子 アクセス可能範囲 Pythonでの実装方法
public どこからでもアクセス可能 先頭にアンダースコアをつけない
private クラスの中からしかアクセスできない 先頭にアンダースコアを2つ付ける(__method_name, __prop_nameなど)
protected 該当クラス及び継承クラスからしかアクセスできない
(ただし、Pythonにはprotectedの概念がないため、どこからでもアクセス可能です)
先頭にアンダースコアを1つ付ける

余談ですが、privateで使用するアンダースコア2つのことを、ダンダー(dunder)と言います。例えば、「ダンダー関数」とは、アンダースコア2つ(__)から始まる関数を言います。ダンダーは「Double Underscore」を略して「Dunder」という名前になりました。

クラスメソッド(Class Method)とは

今までメソッドを使うときは、インスタンスを作成してからメソッドを呼び出していました。しかし、ものによってはインスタンスを作成しなくても良いケースがあります。まさにメニューを返す処理がそうですね。テーブル注文コードを再度召喚します。

import time
 
class Table:
    (省略)
 
class Menu:
    # メニューに関するオブジェクト
    @classmethod
    def get_menu(cls):
        menu = {
            "beef": 1000,
            "chicken": 800,
            "pork": 900
        }
        return menu

インスタンスを作る理由の一つは、インスタンスごとに異なるデータを保持させることができることからです。その観点から、Menuクラスのget_menuメソッドは、インスタンスごとに違う値を返すことはないですね。だとしたら、インスタンスを生成してからメソッドを呼び出さなくても良さそうです。このようなときに使用するのがクラスメソッドになります。

クラスメソッドを定義する方法は、コードのように関数の定義の1行上に@classmethodを追加するだけで良いです。クラスメソッドの第一引数には、インスタンスでselfにインスタンス自身が入るように、クラス自信が入ります。これもself同様、classkurasuなどなんでも良いですが、慣習的にclsが使われています。

最後に

次回は、デザインパターンを学ぶ上で必須知識となる、「継承」と「オーバーライド」を紹介します。それでは、次回もお楽しみに!

シリーズ一覧

孔 允培 (執筆記事の一覧)

アプリケーションサービス部 ディベロップメントサービス課

孔子の80代目子孫