カプセル化と継承
カプセル化はオブジェクトの内部詳細を隠し、安全な操作インターフェースのみを公開します。継承により、あるクラスは自動的に別のクラスの機能を持ち、繰り返しを避けられます。これら 2 つの概念はオブジェクト指向プログラミングの根幹です。
1. カプセル化とプライベート属性
カプセル化とは、オブジェクトの内部データは外部から直接アクセスすべきでない——メソッドを通じて操作すべきであるという考え方です。
シングルアンダースコア _ — 「保護された」属性の慣習
例:シングルおよびダブルアンダースコア
class Person:
def __init__(self, name, age):
self.name = name
self._age = age # シングルアンダースコア:内部用であることを示す
p = Person("Zhang San", 25)
print(p.name) # Zhang San — パブリック属性
print(p._age) # 25 — アクセスは可能だが、推奨されない
シングルアンダースコアは単なる慣習であり、強制はされません——Python はアクセスを妨げません。これは「これは内部用です、触らないでください」とドキュメントに書くようなものです。
ダブルアンダースコア __ — 名前マングリング
例:銀行口座クラス
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # ダブルアンダースコア:プライベート属性
def get_balance(self):
"""安全に残高を取得"""
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
account = BankAccount("Zhang San", 10000)
print(account.owner) # Zhang San — パブリック
# print(account.__balance) # エラー!AttributeError
print(account.get_balance()) # 10000 — メソッド経由でアクセス
ダブルアンダースコアは名前マングリングをトリガーします——__balance は内部的に _BankAccount__balance に変換されます。これは真の「プライバシー」ではなく、偶発的なオーバーライドを防ぐためのものです。
private キーワードがありますが、Python にはなく、追加する予定もありません。
例:Temperature クラス(難易度 ⭐⭐)
class Temperature:
def __init__(self, celsius=0):
self.__celsius = celsius
def to_fahrenheit(self):
return self.__celsius * 9 / 5 + 32
def set_celsius(self, value):
"""安全に温度を設定 — 絶対零度より低くできない"""
if value < -273.15:
print("Temperature cannot be below absolute zero!")
return
self.__celsius = value
def get_celsius(self):
return self.__celsius
t = Temperature(100)
print(f"100°C = {t.to_fahrenheit():.1f}°F") # 212.0°F
t.set_celsius(-300) # Temperature cannot be below absolute zero!
t.set_celsius(0)
print(f"0°C = {t.to_fahrenheit():.1f}°F") # 32.0°F
2. @property:エレガントなアクセス制御
set_celsius()/get_celsius() を書くのは冗長です。@property を使うと、属性アクセスの構文を保ちながらアクセスを制御できます:
例:@property の使用
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""摂氏温度を取得"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""摂氏温度を設定 — 自動検証"""
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
"""華氏(読み取り専用 — セッターなし)"""
return self._celsius * 9 / 5 + 32
t = Temperature(100)
print(t.celsius) # 100 — 属性のようにアクセス
print(t.fahrenheit) # 212.0
t.celsius = 0 # 属性のように代入
print(t.celsius) # 0
# t.celsius = -300 # ValueError が発生!
# t.fahrenheit = 100 # エラー!セッターなし
@property の利点: 1)アクセスに括弧不要:t.celsius は t.get_celsius() より簡潔。2)代入時の自動検証:t.celsius = value は t.set_celsius(value) より簡潔。3)通常の属性を計算属性に変換しても、既存のコードを壊さない。
3. 継承
継承により、子クラスは親クラスのすべての属性とメソッドを持てます:
例:Animal の継承
class Animal:
"""動物の基底クラス"""
def __init__(self, name):
self.name = name
def speak(self):
return "..."
def eat(self, food):
return f"{self.name} is eating {food}"
class Dog(Animal):
"""犬 — Animal から継承"""
def speak(self):
return "Woof!"
class Cat(Animal):
"""猫 — Animal から継承"""
def speak(self):
return "Meow!"
dog = Dog("Wangcai")
cat = Cat("Mimi")
print(dog.speak()) # Woof!(親メソッドをオーバーライド)
print(cat.speak()) # Meow!(親メソッドをオーバーライド)
print(dog.eat("bone")) # Wangcai is eating bone(親から継承)
print(cat.eat("fish")) # Mimi is eating fish(親から継承)
super():親メソッドの呼び出し
例:super() で親を呼び出す
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 親の __init__ を呼び出し
self.breed = breed # 子固有の属性を追加
def introduce(self):
return f"I'm {self.name}, a {self.breed}"
dog = Dog("Wangcai", "Golden Retriever")
print(dog.introduce()) # I'm Wangcai, a Golden Retriever
4. Mixin パターン
Mixin は特殊なクラスです——独立して使われるのではなく、他のクラスに機能を追加するために「ミックスイン」されます:
例:Mixin パターン
class FlyMixin:
"""飛行能力ミックスイン"""
def fly(self):
return f"{self.name} is flying!"
class SwimMixin:
"""水泳能力ミックスイン"""
def swim(self):
return f"{self.name} is swimming!"
class Animal:
def __init__(self, name):
self.name = name
class Duck(Animal, SwimMixin, FlyMixin):
"""アヒル — 泳ぐことも飛ぶこともできる"""
def speak(self):
return "Quack!"
class Fish(Animal, SwimMixin):
"""魚 — 泳ぐことしかできない"""
def speak(self):
return "..."
duck = Duck("Donald")
fish = Fish("Nemo")
print(duck.speak()) # Quack!
print(duck.swim()) # Donald is swimming!
print(duck.fly()) # Donald is flying!
print(fish.swim()) # Nemo is swimming!
Mixin で終わるものは、ミックスイン用であり、独立して使うためのものではないことを示します。クラスは複数の Mixin と 1 つのメインベースクラスをミックスインできます——深い単一継承よりもはるかに柔軟です。
よくあるユースケース
- データ検証:
@property.setterで代入時のデータ妥当性を確保 - 読み取り専用プロパティ:セッターなしの
@propertyで読み取り専用属性 - コード再利用:共通機能を基底クラスに配置。サブクラスは差分のみを記述
- 機能合成:Mixin を使って異なるクラスに柔軟に機能を追加
❓ よくある質問
@property と通常のメソッドの違いは何ですか?@property はメソッドを属性のように見せます——アクセスに括弧は不要、代入はセッターをトリガーします。本質的には「属性インターセプター」です——属性を操作しているかのようにコードを書きますが、実際にはメソッドが実行されます。class C(A, B)。ただし、多重継承には「ダイヤモンド問題」(両方の親に同じ名前のメソッドがある場合、どちらを使うか)があります。Python は MRO(メソッド解決順序)を使用し、左から右に検索します。Mixin は多重継承を活用するデザインパターンで、Mixin クラスは小さく機能的に独立しており、ダイヤモンド問題を回避します。📖 まとめ
- カプセル化:
_は「触るな」、__は名前マングリング、@propertyはエレガントなアクセス制御 - 継承:子クラスは自動的に親の属性とメソッドを持つ。
super()で親メソッドを呼び出す - メソッドオーバーライド:子クラスが親と同じ名前のメソッドを再定義
- Mixin:多重継承を使って柔軟に機能を追加。深い継承よりも推奨
- Python に真のプライベート属性はない——慣習と名前マングリングに依存
📝 練習問題
-
初級(難易度 ⭐):
Productクラスを定義し、price属性に@propertyを使用——設定時は正の値であることを要求、取得時は元の値を返す。 -
中級(難易度 ⭐⭐):3 レベルの継承
Animal→Mammal→Dog/Catを定義。Animalはnameとageを持ち、Mammalはfur_colorを追加、DogとCatはそれぞれ独自のspeak()メソッドを実装。 -
上級(難易度 ⭐⭐⭐):ログシステムを作成。
LoggableMixin(log_info()、log_error()メソッドを提供)と基底クラスApplication(name属性を持つ)を使用。WebAppとCLIAppの両方がApplicationを継承し、LoggableMixinをミックスインしてログ機能を実装。



