【Python】レッスン5-04:プロパティを理解しよう

一つ前の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基礎習得者にお勧めの道5選(実務or副業)
属性を守って使いやすくする設計の基本|カプセル化と可読性を両立
前回のカプセル化の記事 でクラス内のデータをカプセル化して外部からの直接アクセスを防ぐ方法と、安全にアクセスできるゲッターとセッターについて学びました。
今回はそのゲッターやセッターをさらに便利に扱える「プロパティ」について解説します。
「いつ使う?どう書く?メソッドとの違いは?」といった学習者の疑問にも、本文で順に答えていきます。
まずは手元のクラスの1つの属性を思い浮かべながら、読み進めてください。
プロパティの概要|デコレーターの基本と使いどころ
プロパティ とはPythonが提供する機能の一つで、クラスの属性に対するアクセス方法をカプセル化しながら、外部からはあたかも通常の属性のようにアクセスできる仕組みです。
通常は、クラスの属性を操作するにはゲッターとセッターを使用しますが、プロパティを使うことで、これらのメソッドを明示的に呼び出す必要がなくなり、コードが洗練されます。
プロパティを定義するには @property
デコレーター を使用します。
これによりユーザーは通常の属性のようにデータを操作できますが、実際には内部でゲッターやセッターメソッドが呼び出されて処理が行われます。
デコレーター自体の詳細解説はこの記事では行いませんが、↓↓の記事にて詳しく解説しています。
@property
と@<name>.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のプロパティは、クラスのデータを安全に管理し、コードの可読性と保守性を向上させるための重要な機能です。
プロパティを適切に活用することで、クラスの内部データに対する直接的なアクセスを防ぎ、データの一貫性を保ちながら柔軟なプログラムを作成できます。
プロパティは、オブジェクト指向プログラミングにおけるカプセル化の重要な要素の一つですので、ぜひ使いこなせるようになりましょう。
- サイト改善アンケート|ご意見をお聞かせください(1分で終わります)
-
本サイトでは、みなさまの学習をよりサポートできるサービスを目指しております。
そのため、みなさまの「プログラミングを学習する理由」などをアンケート形式でお伺いしています。1分だけ、ご協力いただけますと幸いです。
【Python】サイト改善アンケート
練習問題:プロパティを使ってみよう
この記事で学習した「プロパティ」を復習する練習問題に挑戦しましょう。
問題|名前と年齢を管理するクラスを作成しよう
人の名前と年齢を管理する小さなクラスを作成しましょう。
プロパティのゲッター/セッターを用いて、見た目は属性アクセスのままバリデーション(値の検証)を挟む実装を練習しましょう。
以下の要件に従ってコードを完成させてください。
- クラス名は
Person
とし、人物の名前と年齢を保持する。 - インスタンス変数は
_name
と_age
を用い、*頭アンダースコアでカプセル化(非公開扱い*とする。 name
とage
のプロパティを定義し、各プロパティにゲッターとセッターを実装する。- セッターでは以下を検証し、満たさない場合は
ValueError
を送出する。 name
:文字列(str
)のみ許可age
:0以上の整数(int
)のみ許可
ただし、以下のような実行結果となるコードを書くこと。
名前: 太郎 年齢: 25歳 新しい名前: 次郎 新しい年齢: 30歳
ヒント|難しいと感じる人だけ見よう
1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。
- ヒント1【コードの構成を見る】
-
正解のコードは上から順に以下のような構成となっています。
(※下記の□はコード内のインデントを表しています)1:クラスの定義と初期化:受け取った値を内部属性に格納する
・クラスを宣言し、オブジェクト生成時に自動で呼ばれる特別メソッドを用意する。
・受け取った名前と年齢を、そのオブジェクトだけが持つインスタンス変数に保存する。
・直接アクセスさせない意図を示すため、変数名の先頭にアンダースコアを付けておく。2:nameのプロパティ:読み書きの入口と文字列チェックを定義する
・読み取り用の仕組みは、内部に保存している名前をそのまま返すだけでよい。
・書き換え時は、受け取った値が文字列かどうかを検証する。
・条件を満たさない場合は ValueError を送出し、満たす場合のみ内部の変数を更新する。3:ageのプロパティ:読み書きの入口と整数・下限チェックを定義する
・読み取りは内部に保存している年齢をそのまま返すだけでよい。
・書き換え時は整数かどうか、そして0以上かどうかの二段階で確認する。
・条件を満たさないときは ValueError を送出し、満たすときだけ内部の変数を更新する。4:インスタンス生成とプロパティ参照:初期値を取得して画面に表示する
・クラスから新しいオブジェクトを作ると、初期化処理が自動で走る。
・値の取得は、名前と年齢のプロパティをそのまま参照すればよい。
・表示はフォーマット済み文字列を使い、文言と値(年齢には「歳」)を組み合わせる。5:プロパティ経由の再代入と確認表示:検証を通して値を更新する
・プロパティ名に代入すると、内部で書き換え用の処理が自動で動く。
・名前は文字列、年齢は0以上の整数でないとエラーになる。
・値を更新したら、同じプロパティを参照して最新の状態を表示する。
- ヒント2【穴埋め問題にする】
-
以下のコードをコピーし、コメントに従ってコードを完成させて下さい。
class Person: def __init__(self, name, age): self._name = name self._age = age # name属性のゲッター @'''(穴埋め)''' def name(self): return self._name # name属性のセッター @name.'''(穴埋め)''' def name(self, value): if not isinstance(value, '''(穴埋め)'''): raise ValueError("名前は文字列でなければなりません") self._name = value # age属性のゲッター @'''(穴埋め)''' def age(self): return self._age # age属性のセッター @age.setter def age(self, value): if not isinstance(value, '''(穴埋め)''') 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}歳")
このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。
解答例|人物プロパティ管理プログラム
例えば以下のようなプログラムが考えられます。
- 正解コード
-
class Person: def __init__(self, name, age): self._name = name self._age = age # name属性のゲッター @property def name(self): return self._name # name属性のセッター @name.setter def name(self, value): if not isinstance(value, str): raise ValueError("名前は文字列でなければなりません") self._name = value # age属性のゲッター @property def age(self): return self._age # age属性のセッター @age.setter def age(self, value): 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}歳")
解答例の解説|人物プロパティ管理プログラムの考え方
解答例の詳細解説は以下の通りです。
- 詳細解説
-
クラスの定義と初期化
class Person: def __init__(self, name, age): self._name = name self._age = age
ここでは人を表すクラスを用意し、オブジェクトを作った直後に実行される特別な初期化処理で、渡された名前と年齢をインスタンス専用の変数にしまい込んでいます。
変数名の先頭にアンダースコアを付けるのは「外から直接いじらないでね」という合図で、のちほどプロパティ経由で安全に触れるための下準備です。
初期化の段階では動作を単純に保ち、まずは値を持たせることに集中しています。nameのプロパティ
# name属性のゲッター @property def name(self): return self._name # name属性のセッター @name.setter def name(self, value): if not isinstance(value, str): raise ValueError("名前は文字列でなければなりません") self._name = value
ここでは名前を表す値に対して「読み取り」と「書き換え」の窓口を用意しています。
読み取り用の仕組みは、内部にしまってある値をそのまま返すだけのシンプルな動きです。
一方、書き換え用の仕組みでは、渡された値が文字列かどうかを必ず確かめ、条件を満たさなければ例外を起こして処理を止めます。
こうしておくと、外からは普通の属性のように扱えるのに、内部では不正な値が入り込むのを防げます。
最終的に条件を満たしたときだけ、非公開の変数に値を入れ替える設計です。ageのプロパティ
# age属性のゲッター @property def age(self): return self._age # age属性のセッター @age.setter def age(self, value): 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}歳")
ここでは、いったん作成したオブジェクトに対して、名前と年齢を新しい値に置き換えています。
見た目は単なる代入ですが、内部ではプロパティの書き換え用の仕組みが呼ばれ、値が正しい型や範囲かどうかをチェックしたうえで更新されます。
続けて、読み取り用の仕組みで最新の値を取り出し、メッセージと組み合わせて表示します。
これにより、通常の属性アクセスの書き心地を保ちながら、不正な入力を防いだ安全な更新と結果の確認が行えます。
プロパティの疑問解消|FAQと用語のまとめ
初心者がつまずきやすいポイントをFAQとしてまとめ、またよく使う専門用語をわかりやすく整理しました。
理解を深めたいときや、ふと疑問に感じたときに役立ててください。
FAQ|プロパティに関するよくある質問
- Q1. プロパティとゲッター・セッターの違いは?
-
プロパティはゲッター・セッターの使い方を簡潔に記述する構文です。デコレーター(@property)を使うことで、変数のようにアクセスできるのが特徴です。
- Q2. プロパティを使うメリットは?
-
メソッドを変数のように扱えるため、コードが簡潔かつ直感的になります。また、将来的に処理を追加しても、外部の呼び出し方を変えずに済みます。
- Q3. プロパティを使うとパフォーマンスが落ちることはありますか?
-
通常の用途では大きな差はありませんが、頻繁に呼ばれる処理には注意が必要です。パフォーマンスが重要な場面ではキャッシュの導入などで最適化することも検討しましょう。
Python用語集|プロパティに関する用語一覧
今回の記事で出てきた用語・関数などを一覧で紹介します。
このサイトに出てくる 全てのPython用語をまとめた用語集 も活用してください。
Python用語 | 定義・使い方の概要 | 解説記事へのリンク |
---|---|---|
カプセル化 | クラス内部のデータや実装を外部から隠し、安全なインターフェースを通じて操作するOOPの基本原則 | Lesson5-3 |
ゲッター | 非公開の属性値を取得するためのメソッド。プロパティやデコレーターと併用されることが多い | Lesson5-3 |
セッター | 非公開の属性値を設定するためのメソッド。入力値の検証などを行うことが可能 | Lesson5-3 |
デコレーター | 関数やメソッドの定義を簡潔に拡張するための構文。@記法 を使い、関数をラップして動作を変える | Lesson.F-2 |
プロパティ | メソッドを属性のように扱えるようにする機能。値の取得や設定を制御するために @property を使って定義される | 本記事 |
@property | ゲッターを定義するためのデコレーター。メソッドを属性のように呼び出せるようにする | 本記事 |
@プロパティ名.setter | セッターを定義するためのデコレーターで、対応する @property のプロパティに値を設定できるようにする | 本記事 |