カプセル化と継承

カプセル化はオブジェクトの内部詳細を隠し、安全な操作インターフェースのみを公開します。継承により、あるクラスは自動的に別のクラスの機能を持ち、繰り返しを避けられます。これら 2 つの概念はオブジェクト指向プログラミングの根幹です。


1. カプセル化とプライベート属性

カプセル化とは、オブジェクトの内部データは外部から直接アクセスすべきでない——メソッドを通じて操作すべきであるという考え方です。

シングルアンダースコア _ — 「保護された」属性の慣習

例:シングルおよびダブルアンダースコア

PYTHON
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 はアクセスを妨げません。これは「これは内部用です、触らないでください」とドキュメントに書くようなものです。

ダブルアンダースコア __ — 名前マングリング

例:銀行口座クラス

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 に変換されます。これは真の「プライバシー」ではなく、偶発的なオーバーライドを防ぐためのものです。

💡 Python に真のプライベートはありません。 「私たちはすべて責任ある大人である」という理解に基づいています——シングルアンダースコアは「内部使用」、ダブルアンダースコアは名前の衝突を防ぎます。他の言語(Java など)には真の private キーワードがありますが、Python にはなく、追加する予定もありません。

例:Temperature クラス(難易度 ⭐⭐)

PYTHON
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 の使用

PYTHON
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.celsiust.get_celsius() より簡潔。2)代入時の自動検証:t.celsius = valuet.set_celsius(value) より簡潔。3)通常の属性を計算属性に変換しても、既存のコードを壊さない。


3. 継承

継承により、子クラスは親クラスのすべての属性とメソッドを持てます:

例:Animal の継承

PYTHON
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() で親を呼び出す

PYTHON
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 パターン

PYTHON
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 で終わるものは、ミックスイン用であり、独立して使うためのものではないことを示します。クラスは複数の Mixin と 1 つのメインベースクラスをミックスインできます——深い単一継承よりもはるかに柔軟です。


よくあるユースケース


❓ よくある質問

Q 継承と Mixin はいつ使い分ければよいですか?
A 継承は「is-a」関係に適しています——犬は動物です。Mixin は「has-a ability」に適しています——アヒルは泳げます。継承よりも合成(Mixin)を優先してください。深い継承階層は保守が困難です。良いルール:継承は 3 レベルを超えないこと。
Q @property と通常のメソッドの違いは何ですか?
A @property はメソッドを属性のように見せます——アクセスに括弧は不要、代入はセッターをトリガーします。本質的には「属性インターセプター」です——属性を操作しているかのようにコードを書きますが、実際にはメソッドが実行されます。
Q Python は多重継承をサポートしていますか?落とし穴はありますか?
A はい——class C(A, B)。ただし、多重継承には「ダイヤモンド問題」(両方の親に同じ名前のメソッドがある場合、どちらを使うか)があります。Python は MRO(メソッド解決順序)を使用し、左から右に検索します。Mixin は多重継承を活用するデザインパターンで、Mixin クラスは小さく機能的に独立しており、ダイヤモンド問題を回避します。

📖 まとめ


📝 練習問題

  1. 初級(難易度 ⭐)Product クラスを定義し、price 属性に @property を使用——設定時は正の値であることを要求、取得時は元の値を返す。

  2. 中級(難易度 ⭐⭐):3 レベルの継承 AnimalMammalDogCat を定義。Animalnameage を持ち、Mammalfur_color を追加、DogCat はそれぞれ独自の speak() メソッドを実装。

  3. 上級(難易度 ⭐⭐⭐):ログシステムを作成。LoggableMixinlog_info()log_error() メソッドを提供)と基底クラス Applicationname 属性を持つ)を使用。WebAppCLIApp の両方が Application を継承し、LoggableMixin をミックスインしてログ機能を実装。

100%