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

一つ前の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が提供する機能で、クラスの属性に対するアクセス方法をカプセル化しながら、外部からはあたかも通常の属性のようにアクセスできる仕組みです。
通常は、クラスの属性を操作するにはゲッターとセッターを使用しますが、プロパティを使うことで、これらのメソッドを明示的に呼び出す必要がなくなり、コードが洗練されます。
Pythonでは @property
デコレーター を使用してプロパティを定義し、ゲッターとセッターをシンプルに扱います。
これによりユーザーは通常の属性のようにデータを操作できますが、実際には内部でゲッターやセッターメソッドが呼び出されて処理が行われます。
デコレーター自体の詳細解説はこの記事では行いませんが、↓↓の記事にて詳しく解説しています。
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」な設計と言えるでしょう。
プロパティの使用例
簡単な例を見てみましょう。
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のプロパティは、クラスのデータを安全に管理し、コードの可読性と保守性を向上させるための重要な機能です。
プロパティを適切に活用することで、クラスの内部データに対する直接的なアクセスを防ぎ、データの一貫性を保ちながら柔軟なプログラムを作成できます。
プロパティは、オブジェクト指向プログラミングにおけるカプセル化の重要な要素の一つですので、ぜひ使いこなせるようになりましょう。
練習問題:名前と年齢を管理するクラスを作成しよう
クラスを使って名前と年齢を管理するプログラムを作成しましょう。
このプログラムでは名前と年齢を取得するためのゲッターメソッドと、値を設定するためのセッターメソッドを実装します。
プロパティを使用して、直接的に属性にアクセスする代わりにメソッドを通じてデータの取得や設定を行いましょう。
問題の詳細条件
以下の要件に従ってコードを完成させてください。
- クラス
Person
を作成し、名前と年齢を属性として持つこと。 - インスタンス変数
_name
と_age
をカプセル化し、外部から直接アクセスできないようにすること。 - プロパティ
name
とage
を使用して、名前と年齢を取得・設定できるようにすること。 name
属性には文字列のみを設定できるようにし、文字列以外が設定された場合はエラーを発生させること。age
属性には0以上の整数のみを設定できるようにし、不正な値が設定された場合はエラーを発生させること。
ただし、以下のような実行結果となるコードを書くこと。
名前: 太郎 年齢: 25歳 新しい名前: 次郎 新しい年齢: 30歳
【ヒント】難しいと感じる人だけ見よう
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
- ヒント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文字列で「新しい年齢:」と出力
- ヒント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}歳")
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
問題の答え合わせと解説
この問題の一つの正解例とそのコードの解説を以下に示します。
一つの正解例
例えば以下のようなプログラムが考えられます。
- 正解コード
-
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コードではプロパティを通じて、データを保護しつつ、アクセスや変更を安全に行う方法を紹介します。
- 詳細解説
-
クラス定義とコンストラクタ
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}歳")
プロパティを使って
name
とage
の値を取得し、それを表示しています。プロパティのおかげで、変数に直接アクセスするのではなく、セッターやゲッターを通じて安全にデータを扱っています。
プロパティを使った値の変更
person.name = "次郎" person.age = 30
このコードはセッターを使って
name
を「次郎」、age
を30に変更しています。このときセッターによってデータのバリデーションが行われ、適切な値が設定されます。
まとめ
このコードでは、クラス、コンストラクタ、プロパティ、ゲッター、セッターの基本的な使い方を学びました。
特にプロパティを使うことで、データの不正なアクセスや変更を防ぐと同時に、簡単にデータを取得・設定できる便利さを理解できたと思います。
Python用語集|プロパティ編
今回の記事で出てきた用語・関数などを一覧で紹介します。
このサイトに出てくる 全てのPython用語をまとめた用語集 も活用してください。
Python用語 | 定義・使い方の概要 | 解説記事へのリンク |
---|---|---|
カプセル化 | クラス内部のデータや実装を外部から隠し、安全なインターフェースを通じて操作するOOPの基本原則 | Lesson5-3 |
ゲッター | 非公開の属性値を取得するためのメソッド。プロパティやデコレーターと併用されることが多い | Lesson5-3 |
セッター | 非公開の属性値を設定するためのメソッド。入力値の検証などを行うことが可能 | Lesson5-3 |
デコレーター | 関数やメソッドの定義を簡潔に拡張するための構文。@記法 を使い、関数をラップして動作を変える | Lesson.F-2 |
プロパティ | メソッドを属性のように扱えるようにする機能。値の取得や設定を制御するために @property を使って定義される | 本記事 |
@property | ゲッターを定義するためのデコレーター。メソッドを属性のように呼び出せるようにする | 本記事 |
@プロパティ名.setter | セッターを定義するためのデコレーターで、対応する @property のプロパティに値を設定できるようにする | 本記事 |
FAQ|Pythonプロパティの活用法
- Q1. プロパティとゲッター・セッターの違いは?
-
プロパティはゲッター・セッターの使い方を簡潔に記述する構文です。デコレーター(@property)を使うことで、変数のようにアクセスできるのが特徴です。
- Q2. プロパティを使うメリットは?
-
メソッドを変数のように扱えるため、コードが簡潔かつ直感的になります。また、将来的に処理を追加しても、外部の呼び出し方を変えずに済みます。
- Q3. プロパティを使うとパフォーマンスが落ちることはありますか?
-
通常の用途では大きな差はありませんが、頻繁に呼ばれる処理には注意が必要です。パフォーマンスが重要な場面ではキャッシュの導入などで最適化することも検討しましょう。