【レッスン5-03】カプセル化を使おう|ゲッターとセッターで内部データを守る方法|Python学習

ながみえ
【Python学習記事のアイキャッチ画像】Lesson5-3 カプセル化を理解しよう

一つ前のLessonではメソッドの基本について学習しました。

今回はカプセル化について見ていきましょう。

Lesson1:基礎文法編
Lesson2:制御構造編
Lesson3:関数とスコープ編

Lesson4:データ構造編
Lesson5:オブジェクト指向編
・Lesson5-1:クラスの基本を理解しよう
・Lesson5-2:メソッドの基本を理解しよう
・Lesson5-3:カプセル化を理解しよう ◁今回はココ
・Lesson5-4:プロパティを理解しよう
・Lesson5-5:クラスの継承を理解しよう
・Lesson5-6:メソッドのオーバーライドを理解しよう
・Lesson5-7:静的メソッドを理解しよう
・Lesson5-8:モジュールを使いこなそう
・Lesson5-9:抽象クラスを理解しよう
・Lesson5-10:ミックスインを理解しよう
・Lesson5-11:データクラスを理解しよう
・練習問題5-1:モンスター捕獲ゲームを作ろう
・練習問題5-2:モンスターとのバトルゲームを作ろう
次のステップ:Pythonを用いたアプリ開発

<<前のページ

【Python学習記事のアイキャッチ画像】Lesson5-2 メソッドの基本を理解しよう

Pythonの記事一覧

Python学習カテゴリの親ページ用アイキャッチ画像(テキスト&問題集)、記事一覧へのリンク案内

次のページ>>

【Python学習記事のアイキャッチ画像】Lesson5-4 プロパティを理解しよう

カプセル化とは?|クラス設計におけるデータ保護の基本

ここからカプセル化の説明に入ります。

ページ下部にはこのページで出てくる用語の意味をまとめた 用語集 もありますので、分からない言葉が出てきたらそちらも参考にしてください。

【Python】勉強猫がノートパソコンを前にして学習を始める様子。記事内の学習スタート用イラスト

オブジェクト指向プログラミングの重要な概念の一つに「カプセル化」があります。

カプセル化はオブジェクトのデータ(属性)を外部から直接アクセスできないようにし、データの保護や管理を容易にするための手法です。

Pythonでは変数やメソッドのアクセス制御を簡単に行うことができます。今回はこのカプセル化の概要について解説します。

カプセル化とは

カプセル化とはクラス内のデータ(属性)やメソッドを他のクラスや外部コードから見えないようにし、データの保護を行う手法です。

これにより外部から誤ってデータが変更されることを防ぎ、クラスの内部動作を隠蔽することができます。

Pythonでは属性の名前の前にアンダーバー( _ )を付けることで、直接アクセスを避けるように促す「慣習的なプライベート属性」を定義できます。

またアンダーバーを2つ( __ )にすることで、名前修飾を行い、さらに強力な保護を提供します。

慣習的プライベート属性(アンダーバー1つ)

アンダーバー1つ( _ )を属性やメソッド名の前に付けると、その属性やメソッドは「慣習的にプライベート」と見なされます。

つまり外部からアクセスすることは可能ですが、アクセスを控えるように促すサインです。

class MyClass:					# MyClassクラスの定義
    def __init__(self, value):	# コンストラクタの定義
        self._variable = value  # 慣習的にプライベートな属性

この例では_variableという属性はアンダーバー1つで定義されており、外部から直接アクセスすることが可能です。

しかし、可能な限りアクセスは避けるべき、という意味を込めてアンダーバーが1つついています。

名前修飾によるプライベート変数(アンダーバー2つ)

アンダーバー2つ( __ )を使うことで、Pythonは名前修飾を行い、クラス外部からのアクセスをより厳しく制限します。

この方法はクラス内部での属性やメソッドを保護するために使用されます。

class MyClass:
    def __init__(self, value):
        self.__private_variable = value  # 名前修飾されたプライベートな属性

この例では__private_variableは名前修飾され、クラス外部から直接アクセスしようとするとエラーが発生します。

名前修飾により外部からのアクセスが制限され、より強力なカプセル化が実現されます。

Q
コラム:変数と属性の違い

コラム:変数と属性の違いと見分け方

Pythonを学んでいると、「変数」と「属性」という言葉が登場します。

どちらも「名前に値をひもづける」ものですが、役割と意味ははっきりと違います。

変数とは?|データを保管する箱

変数はあるスコープ(範囲)で値に名前をつける仕組みです。

最も身近なのは関数内のローカル変数や、ファイルの先頭にあるグローバル変数です。

x = 10        # x は変数
name = "Bob"  # name も変数

これらは「どこに何があるか」を示す、プログラム全体の中で使える “名前札” のようなものです。

属性とは?|オブジェクト内部の名前付きデータ

属性は、オブジェクトが “自分専用に持っている名前付きのデータ” であり、”オブジェクトの一部” です。

アクセスするときは、object.attribute のようにドット記法を使います。

class Person:
    def __init__(self, name): # このnameは変数(仮引数)
        self.name = name      # self.nameは属性、右のnameは変数

p = Person("Alice")
print(p.name)	# p.nameは属性へのアクセス
				# pオブジェクトが持つnameという名前の属性にアクセスする
  				# pオブジェクトが持つname = 3行目のself.name

上のコードでは、self.name はオブジェクト p に属する属性。右側の name は関数の引数として渡された変数です。

このように、同じ文字列でも「文脈によって意味が変わる」点に注意しましょう。

変数と属性の違い一覧表

種類意味どこに属するかアクセス方法
変数スコープ内で使う値を入れる箱関数・モジュールx, namename = "Alice"
属性オブジェクトが持つ名前付きの値オブジェクトobject.attributep.name

補足:属性は変数のように使えるが、変数とは別物

属性は「オブジェクトに紐づいた “専用の変数のようなもの” 」ともいえますが、変数とは扱いも保存場所も異なります。

属性はオブジェクトごとに保持され、Python内部では __dict__ という仕組みで管理されます。

ゲッターとセッターの概要と使い方

カプセル化をすると属性に直接アクセスできなくなるため、外部からデータを取得したり変更したりするための「ゲッター」と「セッター」が必要になります。

これらは属性に対するアクセスや変更を制御し、データの整合性を保つために使用されます。

ゲッターは属性の値を外から取得するためのメソッドで、セッターは属性の値を外から設定(変更)するためのメソッドです。

Pythonではゲッターとセッターを使って、カプセル化されたデータへのアクセスを適切に制御します。

class MyClass:							 # MyClassクラスの定義
    def __init__(self, value):			 # コンストラクタ
        self.__private_variable = value  # 名前修飾されたプライベート属性

    def get_value(self):				 # ゲッターメソッドの定義
        return self.__private_variable 	 # 呼び出されたら、プライベート属性を返す

    def set_value(self, value):  # セッターメソッドの定義
        if value > 0:			 # 変更可能な値の条件
            self.__private_variable = value # 値の変更
        else:
            print("値は0より大きくなければなりません")

obj = MyClass(10)
print(obj.get_value())  # ゲッターメソッドを呼び出して値を取得

obj.set_value(20)  # セッターメソッドを呼び出してを設定
print(obj.get_value())

この例ではget_valueメソッドでプライベート変数の値を取得し、set_valueメソッドでその値を変更しています。

またセッター内でデータのバリデーションを行い、不正な値の設定を防いでいます。

このように、クラス内にプライベートな属性を設定した場合は、その属性を変更する専用のメソッド(ゲッターとセッター)を用意しておくことが推奨されます。

ただし、Pythonには実はもっと簡単にカプセル化された属性にアクセスする方法もあります。

今回のゲッターとセッターの内容を理解した上で、次回の プロパティ の記事に進んでください。

まとめ

カプセル化はクラス内のデータを保護し、データの整合性を保つために重要な手法です。

Pythonにはアンダーバー1つを使った「慣習的プライベート」と、アンダーバー2つによる「名前修飾」があり、属性へのアクセスを制御することができます。

またゲッターとセッターを使うことで、データの取得や変更を安全に行うことが可能です。

これによりコードの可読性や保守性が向上し、バグの発生を防ぐことができます。

練習問題:クラスとカプセル化の基礎を学ぼう

【Python】勉強猫がノートパソコンを前にして学習を始める様子。記事内の学習スタート用イラスト

クラスを使って商品の名前と価格を管理するプログラムを作成しましょう。

クラスの初期化メソッドを使って、商品の名前と価格を設定し、プライベート属性を用いることでデータのカプセル化を実現します。

最後に商品価格を変更し、正しい価格管理ができていることを確認しましょう。

問題の詳細条件

以下の要件に従ってコードを完成させてください。

  • 商品を表現する「Product」クラスを作成し、名前と価格を初期化メソッドで設定すること。
  • 名前はプライベート属性 _name、価格はプライベート属性 _price として設定すること。
  • 商品名を取得する get_name() メソッドを定義し、名前を返すこと。
  • 商品の価格を取得する get_price() メソッドを定義し、価格を返すこと。
  • 商品の価格を変更する set_price() メソッドを定義し、価格が 0 以上の場合にのみ価格を更新すること。価格が0未満の場合は警告メッセージを表示すること。
  • 商品のインスタンスを作成し、名前と価格を設定すること。
  • 商品名と価格を表示し、価格の変更も確認すること。

ただし、以下のような実行結果となるコードを書くこと。

商品名: りんご
価格: 100円
新しい価格: 120円
価格は0より大きい値を設定してください。

【ヒント】難しいと感じる人だけ見よう

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

Q
ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。
(※下記の□はコード内のインデントを表しています)

1:クラスProductの定義
□ 初期化メソッド__init__の定義
□ □ 商品名をself._nameに代入
□ □ 価格をself._priceに代入
□ ゲッターメソッドget_nameの定義
□ □ 商品名を返す
□ ゲッターメソッドget_priceの定義
□ □ 価格を返す
□ セッターメソッドset_priceの定義
□ □ 価格が0より大きいか判定
□ □ □ 真の場合、self._priceに新しい価格を代入
□ □ □ 偽の場合、警告メッセージを表示
2:クラスProductのインスタンスproductを作成し、商品名「りんご」と価格100を設定
3:商品名と価格を取得し、f文字列で商品名を出力
4:商品名と価格を取得し、f文字列で価格を出力
5:セッターメソッドset_priceを使い、価格を120に設定
6:f文字列で新しい価格を出力
7:無効な価格-50を設定しようとするが、警告が表示される

Q
ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

# クラスの定義: 「商品」を表現するクラス
class Product:
    # 初期化メソッド (__init__) を使って、商品の名前と価格を設定
    def __init__(self, name, price):
        # アンダーバー1つの変数は「慣習的にプライベート」属性として扱われます
        """【穴埋め問題1】ここにnameとpriceをプライベート変数として設定するコードを書いてください。"""

    # 商品名を取得するためのゲッターメソッド
    def get_name(self):
        """【穴埋め問題2】ここに商品名を返すコードを書いてください。"""

    # 商品の価格を取得するためのゲッターメソッド
    def get_price(self):
        """【穴埋め問題3】ここに商品の価格を返すコードを書いてください。"""

    # 商品の価格を設定するためのセッターメソッド
    def set_price(self, price):
        # 価格は0以上でなければならないという条件を設定
        """【穴埋め問題4】ここに価格が0より大きいかをチェックする条件を含めて、価格を設定するコードを書いてください。"""
        
# 商品のインスタンスを作成
product = Product("りんご", 100)

# 商品名と価格を表示
print(f"商品名: {product.get_name()}")
print(f"価格: {product.get_price()}円")

# 価格を変更
product.set_price(120)
print(f"新しい価格: {product.get_price()}円")

# 無効な価格を設定しようとする
product.set_price(-50)  # この操作は失敗し、警告が表示される

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

問題の答え合わせと解説

この問題の一つの正解例とそのコードの解説を以下に示します。

一つの正解例

例えば以下のようなプログラムが考えられます。

Q
正解コード
# クラスの定義: 「商品」を表現するクラス
class Product:
    # 初期化メソッド (__init__) を使って、商品の名前と価格を設定
    def __init__(self, name, price):
        # アンダーバー1つの変数は「慣習的にプライベート」属性として扱われます
        self._name = name  # 商品名
        self._price = price  # 価格
    
    # 商品名を取得するためのゲッターメソッド
    def get_name(self):
        return self._name
    
    # 商品の価格を取得するためのゲッターメソッド
    def get_price(self):
        return self._price
    
    # 商品の価格を設定するためのセッターメソッド
    def set_price(self, price):
        # 価格は0以上でなければならないという条件を設定
        if price > 0:
            self._price = price
        else:
            print("価格は0より大きい値を設定してください。")

# 商品のインスタンスを作成
product = Product("りんご", 100)

# 商品名と価格を表示
print(f"商品名: {product.get_name()}")
print(f"価格: {product.get_price()}円")

# 価格を変更
product.set_price(120)
print(f"新しい価格: {product.get_price()}円")

# 無効な価格を設定しようとする
product.set_price(-50)  # この操作は失敗し、警告が表示される

正解例の詳細解説

このコードは「商品」を表現するためのクラスを定義し、商品名や価格を管理する仕組みを提供しています。

このコードを通じてオブジェクト指向プログラミングの基本的な概念、特に「カプセル化」について学びます。

Q
詳細解説

クラスの定義

class Product:

ここではProductというクラスを定義しています。

クラスはオブジェクト(具体的には商品)の設計図のようなもので、商品ごとのデータや操作をまとめて管理できます。

初期化メソッド(コンストラクタ)

def __init__(self, name, price):
    self._name = name
    self._price = price

__init__メソッドはクラスのインスタンス(オブジェクト)が作成される際に自動的に呼び出され、オブジェクトの初期設定を行います。

ここでは商品の名前と価格を設定します。self._nameself._priceのようにアンダースコアが付いている変数は「慣習的にプライベート」とされ、クラスの外部から直接アクセスされないようにします。

カプセル化とゲッター・セッターメソッド

def get_name(self):
    return self._name
def get_price(self):
    return self._price
def set_price(self, price):
    if price > 0:
        self._price = price
    else:
        print("価格は0より大きい値を設定してください。")

「カプセル化」とは、データを外部から隠蔽し、データにアクセスする方法を制御することです。

この例では商品名や価格を直接操作するのではなく、get_name()get_price()メソッドを使ってデータにアクセスし、set_price()メソッドを使って価格を変更します。

こうすることで価格が0以下の場合には警告を表示するなど、データの不正な変更を防ぎ、データの一貫性を保つことができます。

インスタンスの作成とメソッドの利用

product = Product("りんご", 100)
print(f"商品名: {product.get_name()}")
print(f"価格: {product.get_price()}円")

上記のコードでは、Productクラスを使って「りんご」という商品を作成し、その名前や価格を取得して表示しています。

これによりオブジェクト指向の実際の使用方法を確認できます。

セッターメソッドの利用とバリデーション

product.set_price(120)
print(f"新しい価格: {product.get_price()}円")
product.set_price(-50)  # この操作は失敗し、警告が表示される

価格の変更はset_price()メソッドを使って行います。

このメソッド内では価格が0より大きいかどうかをチェックする「バリデーション」が行われており、不正な値を防いでいます。

このバリデーションも「カプセル化」により実現されるデータ保護の一例です。

まとめ

今回のコードを通じて、クラスの定義、初期化メソッド、そしてゲッターメソッドやセッターメソッドの使い方を学びました。

特に「カプセル化」により、データの隠蔽や制御がどのように行われているかを理解できたかと思います。

カプセル化はオブジェクト指向プログラミングの重要な概念であり、データの保護や管理を効率的に行うために不可欠な要素です。

これを理解することでより堅牢で安全なプログラムを作成できるようになるでしょう。

もっと分かりやすい学習サイトにするために

この記事を読んで「ここが分かりにくかった」「ここが難しかった」等の意見を募集しています。

世界一わかりやすいPython学習サイトにするため、ぜひ 問い合わせフォーム からご意見下さい。

<<前のページ

【Python学習記事のアイキャッチ画像】Lesson5-2 メソッドの基本を理解しよう

Pythonの記事一覧

Python学習カテゴリの親ページ用アイキャッチ画像(テキスト&問題集)、記事一覧へのリンク案内

次のページ>>

【Python学習記事のアイキャッチ画像】Lesson5-4 プロパティを理解しよう

Python用語集|カプセル化編

今回の記事で出てきた用語・関数などを一覧で紹介します。

このサイトに出てくる 全てのPython用語をまとめた用語集 も活用してください。

Python用語定義・使い方の概要解説記事へのリンク
カプセル化オブジェクトの内部データや実装を隠蔽し、安全に操作するための仕組み。通常、アクセス制御やゲッター・セッターで実現される本記事
ゲッタークラス内の非公開属性の値を外部から取得するためのメソッド本記事
セッタークラス内の非公開属性の値を外部から設定するためのメソッド本記事
変数値を一時的に保持し、後で再利用するために名前を付けたデータの入れ物Lesson1-2
属性クラスやインスタンスに属するデータ。self.名前 などで定義され、オブジェクトに紐づくなし
プロパティ@property デコレーターなどで定義される、ゲッター・セッターを使った属性アクセスの制御手段Lesson5-4

FAQ|Pythonカプセル化の基本

Q
Q1. カプセル化とはどんな場面で使うべきですか?

データを外部から直接変更されたくないときに使います。重要な変数を守り、予期しない変更を防ぐことでバグを減らし、コードの信頼性を高められます。

Q
Q2. ゲッターとセッターはいつ使えばいいですか?

外部から値の読み取りや変更を制御したいときに使います。例えば、値のチェックやログ記録など、単純な変数アクセスに追加処理が必要な場合に便利です。

Q
Q3. Pythonでは変数に「」や「_」をつける理由は?

変数名」は内部利用の目印、「_変数名」は名前の衝突を避けるための名前修飾(ネームマングリング)に使われます。カプセル化の一環としてよく用いられます。

記事URLをコピーしました