【レッスン5-04】プロパティでクラス設計を効率化しよう|Python学習

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

一つ前の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-3 カプセル化を理解しよう

Pythonの記事一覧

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

次のページ>>

【Python学習記事のアイキャッチ画像】Lesson5-5 クラスの継承を理解しよう

プロパティとは?|効率よくカプセル化を行う技術

ここからプロパティの説明に入ります。

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

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

前回のカプセル化の記事 でクラス内のデータをカプセル化して外部からの直接アクセスを防ぐ方法と、安全にアクセスできるゲッターとセッターについて学びました。

今回はそのゲッターやセッターをさらに便利に扱える「プロパティ」について解説します。

プロパティを使うことで、コードをよりシンプルに、かつ直感的に記述できるようになります。

プロパティの概要|デコレーターを使おう

プロパティはPythonが提供する機能で、クラスの属性に対するアクセス方法をカプセル化しながら、外部からはあたかも通常の属性のようにアクセスできる仕組みです。

通常は、クラスの属性を操作するにはゲッターとセッターを使用しますが、プロパティを使うことで、これらのメソッドを明示的に呼び出す必要がなくなり、コードが洗練されます。

Pythonでは @propertyデコレーター を使用してプロパティを定義し、ゲッターとセッターをシンプルに扱います。

これによりユーザーは通常の属性のようにデータを操作できますが、実際には内部でゲッターやセッターメソッドが呼び出されて処理が行われます。

デコレーター自体の詳細解説はこの記事では行いませんが、↓↓の記事にて詳しく解説しています。

あわせて読みたい
【レッスンF-2】デコレーターとは?関数をより柔軟に扱うテクニック|Python学習
【レッスンF-2】デコレーターとは?関数をより柔軟に扱うテクニック|Python学習

propertyデコレーター と プロパティ名.setterデコレーター

クラスの内部データ(属性)へのアクセスを制御する方法の中核となるのが @property@属性名.setter という2つのデコレーターです。

property:ゲッターの定義

property デコレーターは、インスタンスメソッドを読み取り専用の属性のように見せるための仕組みです。

class Person:
    def __init__(self, name):
        self._name = name  # 慣習的にプライベートとみなす属性

    @property		# @propurtyデコレーターの使用宣言
    def name(self): # name属性として振る舞うゲッターメソッドの定義
        return self._name

この定義により、以下のように属性風にアクセスできます:

p = Person("Alice")
print(p.name)  # ← 属性のように見えるが、メソッド name() が呼び出される

プロパティ名.setter:セッターの定義

プロパティに値を代入可能にするためには、ゲッターと対になるセッターを定義します。

その際に使うのが プロパティ名.setter デコレーターです。

    @name.setter		   # ゲッターと同じ名前を使う必要がある
    def name(self, value): # name属性として振る舞うセッターメソッドの定義
        self._name = value

これにより、以下のような代入操作が可能になります:

p.name = "Bob"  # セッターメソッド name() が呼び出される

ポイントまとめ

デコレーター役割条件
@propertyゲッター(読み取り)を定義メソッド名がプロパティ名になる
@プロパティ名.setterセッター(書き込み)を定義@property と同じ名前を使う必要がある

このように、Pythonの @property 機構は安全で一貫性のある属性操作を実現するための強力なツールです。

カプセル化を強化しつつ、ユーザーには直感的なインターフェースを提供する、まさに「Pythonic」な設計と言えるでしょう。

【Python】勉強猫がノートパソコンを見ながら考え込む様子。記事内の休憩用イラスト

プロパティの使用例

簡単な例を見てみましょう。

Personクラスでは、名前の取得と設定をプロパティで制御しています。

class Person:
    def __init__(self, name):
        self._name = name  # アンダースコアを付けた、慣習的にプライベートな属性

    # name属性のゲッター
    @property			  # propertyデコレーターの使用宣言
    def name(self):		  # ゲッターメソッドの定義(name属性として振る舞う)
        return self._name # 呼び出されたら、プライベート属性を返す

    # name属性のセッター
    @name.setter		   # プロパティ名.setter	デコレーターの使用宣言
    def name(self, value): # セッターメソッドの定義(name属性に値を設定)
        if not isinstance(value, str): # 値が文字列であることを検証
            raise ValueError("名前は文字列でなければなりません")
        self._name = value # 呼び出されたら、プライベート属性を変更

# 使用例
p = Person("Alice")
print(p.name)  # pオブジェクトのname属性を出力(メソッドを使用しない)

p.name = "Bob" # pオブジェクトのname属性に値を再代入(メソッドを使用しない)
print(p.name)  # pオブジェクトのname属性を出力(メソッドを使用しない)

この例ではデコレーターを使用してname属性にゲッターとセッターを定義しています。

こうすることでnameにアクセスする際にメソッドを呼び出す必要がなく、p.nameの形式で簡単にデータの取得と設定ができます。

また、セッターでは値の検証が行われ、不正な値が代入されないようになっています。

プロパティの利点

プロパティの主な利点は、属性のアクセス方法をカプセル化し、データの整合性を保ちながら柔軟に管理できる点です。

たとえば、将来的にデータの保存方法が変わったとしても、外部のコードを変更する必要はありません。

プロパティを使っている限り、内部の実装が変わっても外部のインターフェースは同じまま維持できます。

まとめ

Pythonのプロパティは、クラスのデータを安全に管理し、コードの可読性と保守性を向上させるための重要な機能です。

プロパティを適切に活用することで、クラスの内部データに対する直接的なアクセスを防ぎ、データの一貫性を保ちながら柔軟なプログラムを作成できます。

プロパティは、オブジェクト指向プログラミングにおけるカプセル化の重要な要素の一つですので、ぜひ使いこなせるようになりましょう。

練習問題:名前と年齢を管理するクラスを作成しよう

【Python】勉強猫がノートパソコンに向かい、練習問題に挑戦する様子。記事内の休憩用イラスト

クラスを使って名前と年齢を管理するプログラムを作成しましょう。

このプログラムでは名前と年齢を取得するためのゲッターメソッドと、値を設定するためのセッターメソッドを実装します。

プロパティを使用して、直接的に属性にアクセスする代わりにメソッドを通じてデータの取得や設定を行いましょう。

問題の詳細条件

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

  • クラスPersonを作成し、名前と年齢を属性として持つこと。
  • インスタンス変数_name_ageをカプセル化し、外部から直接アクセスできないようにすること。
  • プロパティnameageを使用して、名前と年齢を取得・設定できるようにすること。
  • name属性には文字列のみを設定できるようにし、文字列以外が設定された場合はエラーを発生させること。
  • age属性には0以上の整数のみを設定できるようにし、不正な値が設定された場合はエラーを発生させること。

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

名前: 太郎
年齢: 25歳
新しい名前: 次郎
新しい年齢: 30歳

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

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

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

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

1:Personクラスの定義
  □ コンストラクタ(init)の定義
  □ □ インスタンス変数_nameにnameの値を代入
  □ □ インスタンス変数_ageにageの値を代入
  □ nameプロパティのゲッターの定義
  □ □ name属性の値を返す
  □ nameプロパティのセッターの定義
  □ □ nameの値が文字列かどうかを判定し、エラーを発生させるか、インスタンス変数_nameに値を設定
  □ ageプロパティのゲッターの定義
  □ □ age属性の値を返す
  □ ageプロパティのセッターの定義
  □ □ ageの値が整数かつ0以上かどうかを判定し、エラーを発生させるか、インスタンス変数_ageに値を設定
1:Personクラスのインスタンスを作成し、変数personに代入
2:プロパティnameを使って名前を取得し、f文字列で「名前:」と出力
3:プロパティageを使って年齢を取得し、f文字列で「年齢:」と出力
4:プロパティnameを使って名前を「次郎」に設定
5:プロパティageを使って年齢を30に設定
6:プロパティnameを使って新しい名前を取得し、f文字列で「新しい名前:」と出力
7:プロパティageを使って新しい年齢を取得し、f文字列で「新しい年齢:」と出力

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

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

class Person:
    def __init__(self, name, age):
        """【穴埋め問題1】
        ここにインスタンス変数の初期化を行うコードを書いてください。
        """

    # name属性のゲッター
    @property
    def name(self):
        """このメソッドは、name属性を取得するためのゲッターです。"""
        return """【穴埋め問題2】
        ここにname属性の値を返すコードを書いてください。
        """

    # name属性のセッター
    @name.setter
    def name(self, value):
        """このメソッドは、name属性に新しい値を設定するためのセッターです。"""
        """【穴埋め問題3】
        ここにname属性の値を検証し、設定するコードを書いてください。
        """

    # age属性のゲッター
    @property
    def age(self):
        """このメソッドは、age属性を取得するためのゲッターです。"""
        return """【穴埋め問題4】
        ここにage属性の値を返すコードを書いてください。
        """

    # age属性のセッター
    @age.setter
    def age(self, value):
        """このメソッドは、age属性に新しい値を設定するためのセッターです。"""
        """【穴埋め問題5】
        ここにage属性の値を検証し、設定するコードを書いてください。
        """

# インスタンスの作成
"""【穴埋め問題6】
ここにPersonクラスのインスタンスを作成するコードを書いてください。
"""

# プロパティを使って名前と年齢を取得
print(f"名前: {person.name}")
print(f"年齢: {person.age}歳")

# プロパティを使って名前と年齢を設定
"""【穴埋め問題7】
ここにnameとage属性の値を設定するコードを書いてください。
"""

# 設定後の名前と年齢を表示
print(f"新しい名前: {person.name}")
print(f"新しい年齢: {person.age}歳")

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

問題の答え合わせと解説

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

一つの正解例

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

Q
正解コード
class Person:
    def __init__(self, name, age):
        # インスタンス変数の初期化(カプセル化を考慮して、変数名にアンダースコアをつける)
        self._name = name
        self._age = age

    # name属性のゲッター
    @property
    def name(self):
        """このメソッドは、name属性を取得するためのゲッターです。"""
        return self._name

    # name属性のセッター
    @name.setter
    def name(self, value):
        """このメソッドは、name属性に新しい値を設定するためのセッターです。"""
        if not isinstance(value, str):
            raise ValueError("名前は文字列でなければなりません")
        self._name = value

    # age属性のゲッター
    @property
    def age(self):
        """このメソッドは、age属性を取得するためのゲッターです。"""
        return self._age

    # age属性のセッター
    @age.setter
    def age(self, value):
        """このメソッドは、age属性に新しい値を設定するためのセッターです。年齢は0以上の整数でなければなりません。"""
        if not isinstance(value, int) or value < 0:
            raise ValueError("年齢は0以上の整数でなければなりません")
        self._age = value

# インスタンスの作成
person = Person("太郎", 25)

# プロパティを使って名前と年齢を取得
print(f"名前: {person.name}")
print(f"年齢: {person.age}歳")

# プロパティを使って名前と年齢を設定
person.name = "次郎"
person.age = 30

# 設定後の名前と年齢を表示
print(f"新しい名前: {person.name}")
print(f"新しい年齢: {person.age}歳")

正解例の詳細解説

このPythonコードではプロパティを通じて、データを保護しつつ、アクセスや変更を安全に行う方法を紹介します。

Q
詳細解説

クラス定義とコンストラクタ

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

このコードは Person というクラスを定義しています。

__init__ という特別なメソッドは「コンストラクタ」と呼ばれ、オブジェクトが作られるときに自動的に実行されます。

ここではname(名前)と age(年齢)という2つの属性を初期化しています。

変数の名前の前にアンダースコア(_)をつけることで、これらの変数は直接アクセスされるべきでないことを示しています。

プロパティとゲッター

@property
def name(self):
    return self._name

@property はゲッターと呼ばれるメソッドを定義します。

このメソッドにより、name を直接 person.name のように呼び出して使うことができます。プロパティを使うことで、データの取得が安全に行えます。

ゲッターを使えば、_name というプライベートなデータにアクセスする際に不正な操作を防ぐことができます。

プロパティとセッター

@name.setter
def name(self, value):
    if not isinstance(value, str):
        raise ValueError("名前は文字列でなければなりません")
    self._name = value

@name.setter はセッターと呼ばれるメソッドです。

このメソッドはname を変更するときに使われます。セッターはデータを変更する際にバリデーション(検証)を追加することができるため、データの一貫性を保つのに役立ちます。

ここではname が文字列でなければエラーを出すようにしています。

ageのゲッターとセッター

@property
def age(self):
    return self._age

age 属性にもプロパティのゲッターを使って値を取得しています。

これによりperson.age といった簡単な方法で値を取得できます。

@age.setter
def age(self, value):
    if not isinstance(value, int) or value < 0:
        raise ValueError("年齢は0以上の整数でなければなりません")
    self._age = value

こちらは age のセッターです。

年齢が整数で0以上であることを確認してから値をセットしています。もし不正な値が入ると、エラーメッセージが表示されます。

このようにしてデータが常に正しい形で保持されることを保証します。

インスタンスの作成とプロパティの利用

person = Person("太郎", 25)

ここではPerson クラスのインスタンス(実体)を作成しています。

このときname に「太郎」、age に25という値を渡しています。

print(f"名前: {person.name}")
print(f"年齢: {person.age}歳")

プロパティを使ってnameage の値を取得し、それを表示しています。

プロパティのおかげで、変数に直接アクセスするのではなく、セッターやゲッターを通じて安全にデータを扱っています。

プロパティを使った値の変更

person.name = "次郎"
person.age = 30

このコードはセッターを使って name を「次郎」、age を30に変更しています。

このときセッターによってデータのバリデーションが行われ、適切な値が設定されます。

まとめ

このコードでは、クラス、コンストラクタ、プロパティ、ゲッター、セッターの基本的な使い方を学びました。

特にプロパティを使うことで、データの不正なアクセスや変更を防ぐと同時に、簡単にデータを取得・設定できる便利さを理解できたと思います。

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

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

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

<<前のページ

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

Pythonの記事一覧

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

次のページ>>

【Python学習記事のアイキャッチ画像】Lesson5-5 クラスの継承を理解しよう

Python用語集|プロパティ編

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

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

Python用語定義・使い方の概要解説記事へのリンク
カプセル化クラス内部のデータや実装を外部から隠し、安全なインターフェースを通じて操作するOOPの基本原則Lesson5-3
ゲッター非公開の属性値を取得するためのメソッド。プロパティやデコレーターと併用されることが多いLesson5-3
セッター非公開の属性値を設定するためのメソッド。入力値の検証などを行うことが可能Lesson5-3
デコレーター関数やメソッドの定義を簡潔に拡張するための構文。@記法 を使い、関数をラップして動作を変えるLesson.F-2
プロパティメソッドを属性のように扱えるようにする機能。値の取得や設定を制御するために @property を使って定義される本記事
@propertyゲッターを定義するためのデコレーター。メソッドを属性のように呼び出せるようにする本記事
@プロパティ名.setterセッターを定義するためのデコレーターで、対応する @property のプロパティに値を設定できるようにする本記事

FAQ|Pythonプロパティの活用法

Q
Q1. プロパティとゲッター・セッターの違いは?

プロパティはゲッター・セッターの使い方を簡潔に記述する構文です。デコレーター(@property)を使うことで、変数のようにアクセスできるのが特徴です。

Q
Q2. プロパティを使うメリットは?

メソッドを変数のように扱えるため、コードが簡潔かつ直感的になります。また、将来的に処理を追加しても、外部の呼び出し方を変えずに済みます。

Q
Q3. プロパティを使うとパフォーマンスが落ちることはありますか?

通常の用途では大きな差はありませんが、頻繁に呼ばれる処理には注意が必要です。パフォーマンスが重要な場面ではキャッシュの導入などで最適化することも検討しましょう。

記事URLをコピーしました