Python 特殊メソッド – クラスの振る舞いをカスタマイズする

2025-10-29

はじめに

Pythonの特殊メソッド(ダンダーメソッドまたはマジックメソッドとも呼ばれる)は、クラスの振る舞いをカスタマイズする強力な機能です。これらのメソッドは名前の前後をダブルアンダースコア(__)で囲まれており、Pythonインタプリタによって特定の状況で自動的に呼び出されます。

特殊メソッドを理解し適切に使用することで、Pythonの組み込み型のように自然で直感的な振る舞いを持つクラスを作成できます。例えば、+演算子を使用したオブジェクトの加算や、len()関数での長さの取得、print()関数での文字列表現など、Pythonの基本的な操作を独自のクラスでサポートできるようになります。

特殊メソッドの基本

コンストラクタとデストラクタ

最も基本的な特殊メソッドは、オブジェクトの生成と破棄に関わるものです。

コンストラクタは、オブジェクト生成時に自動的に呼び出され、初期設定や属性の初期化を行う特別なメソッドです(__init__)。

デストラクタは、オブジェクトが削除される際に自動的に呼び出され、後処理やリソース解放を行うメソッドです(__del__)。

次のコードは、書籍を表す Book クラスを定義しており、コンストラクタ __init__ でタイトル、著者、ページ数を初期化してオブジェクト生成時にメッセージを表示し、デストラクタ __del__ でオブジェクト破棄時にメッセージを出力し、__repr__ メソッドで開発者向けの文字列表現を、__str__ メソッドでユーザー向けの見やすい文字列表現を提供する設計になっており、オブジェクトの生成、表示、破棄をそれぞれ適切に管理できるようになっています。

class Book:
    def __init__(self, title, author, pages):
        """コンストラクタ:オブジェクト生成時に呼び出される"""
        self.title = title
        self.author = author
        self.pages = pages
        self.current_page = 1
        print(f"『{self.title}』のオブジェクトが作成されました")

    def __del__(self):
        """デストラクタ:オブジェクトが破棄される時に呼び出される"""
        print(f"『{self.title}』のオブジェクトが破棄されました")

    def __repr__(self):
        """開発者向けの文字列表現"""
        return f"Book('{self.title}', '{self.author}', {self.pages})"

    def __str__(self):
        """ユーザー向けの文字列表現"""
        return f"『{self.title}』 - {self.author} ({self.pages}ページ)"

# 使用例
book = Book("Python入門", "山田太郎", 350)
print(book)      # __str__が呼び出される
print(repr(book)) # __repr__が呼び出される

オブジェクトの表現に関する特殊メソッド

オブジェクトの文字列表現を制御するメソッドは、デバッグやログ出力で非常に役立ちます。次のコードは__repr____str____format__を利用してオブジェクトの表現方法を柔軟に制御しています。

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        """開発者向けの明確な表現"""
        return f"Vector({self.x}, {self.y})"

    def __str__(self):
        """ユーザー向けの分かりやすい表現"""
        return f"({self.x}, {self.y})"

    def __format__(self, format_spec):
        """format()関数やf-stringで使用される"""
        if format_spec == "polar":
            import math
            r = math.sqrt(self.x**2 + self.y**2)
            theta = math.degrees(math.atan2(self.y, self.x))
            return f"({r:.2f}, {theta:.1f}°)"
        else:
            return f"({self.x:{format_spec}}, {self.y:{format_spec}})"

# 使用例
v = Vector(3, 4)
print(repr(v))  # Vector(3, 4)
print(str(v))   # (3, 4)
print(f"{v}")          # (3, 4)
print(f"{v:polar}")    # (5.00, 53.1°)
print(f"{v:06.2f}")    # (03.00, 04.00)

__repr__は開発者向けの明確な表示、__str__はユーザー向けの簡潔な表示を定義しています。さらに__format__により、format()関数やf-stringでフォーマット指定子を使って、通常の数値形式だけでなく「polar」を指定すると極座標表示にも対応します。

算術演算子のオーバーロード

算術演算子の動作をカスタマイズすることで、数学的なオブジェクトを自然に扱えるようになります。以下のコードは、算術演算子のオーバーロードを用いて分数同士の自然な計算を可能にした例です。

class Fraction:
    def __init__(self, numerator, denominator=1):
        if denominator == 0:
            raise ValueError("分母は0にできません")
        self.numerator = numerator
        self.denominator = denominator
        self._simplify()

    def _gcd(self, a, b):
        """最大公約数を計算"""
        while b:
            a, b = b, a % b
        return a

    def _simplify(self):
        """分数を約分"""
        gcd = self._gcd(abs(self.numerator), abs(self.denominator))
        self.numerator //= gcd
        self.denominator //= gcd

        # 分母を正にする
        if self.denominator < 0:
            self.numerator = -self.numerator
            self.denominator = -self.denominator

    def __add__(self, other):
        """加算演算子 + のオーバーロード"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = (self.numerator * other.denominator + 
                        other.numerator * self.denominator)
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __radd__(self, other):
        """右側加算(他の型 + Fraction)"""
        return self.__add__(other)

    def __sub__(self, other):
        """減算演算子 - のオーバーロード"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = (self.numerator * other.denominator - 
                        other.numerator * self.denominator)
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __mul__(self, other):
        """乗算演算子 * のオーバーロード"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = self.numerator * other.numerator
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __truediv__(self, other):
        """除算演算子 / のオーバーロード"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = self.numerator * other.denominator
        new_denominator = self.denominator * other.numerator
        return Fraction(new_numerator, new_denominator)

    def __pow__(self, power):
        """累乗演算子 ** のオーバーロード"""
        if isinstance(power, int):
            return Fraction(self.numerator ** power, self.denominator ** power)
        return NotImplemented

    def __eq__(self, other):
        """等価演算子 == のオーバーロード"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        return (self.numerator == other.numerator and 
                self.denominator == other.denominator)

    def __lt__(self, other):
        """小なり演算子 < のオーバーロード"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        return (self.numerator * other.denominator < 
                other.numerator * self.denominator)

    def __float__(self):
        """float()関数で呼び出される"""
        return self.numerator / self.denominator

    def __repr__(self):
        return f"Fraction({self.numerator}, {self.denominator})"

    def __str__(self):
        if self.denominator == 1:
            return str(self.numerator)
        return f"{self.numerator}/{self.denominator}"

# 使用例
f1 = Fraction(1, 2)
f2 = Fraction(3, 4)

print(f1 + f2)    # 5/4
print(f1 - f2)    # -1/4
print(f1 * f2)    # 3/8
print(f1 / f2)    # 2/3
print(f1 ** 2)    # 1/4
print(f1 == Fraction(2, 4))  # True
print(f1 < f2)    # True
print(float(f1))  # 0.5

Fractionクラスでは、+-*/**などの演算子をそれぞれ__add____sub____mul____truediv____pow__で定義し、分数の加減乗除や累乗を直感的に扱えるようにしています。また、__eq____lt__により比較演算、__float__で浮動小数への変換も可能です。これにより、通常の数値演算と同様の表記で分数演算を行えるようになっています。

比較演算子のオーバーロード

オブジェクト間の比較を可能にすることで、ソートや集合操作などで便利に使用できます。以下のコードは、比較演算子のオーバーロードを使ってStudentオブジェクト同士をスコアで比較できるようにした例です。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __eq__(self, other):
        """等価 == """
        if not isinstance(other, Student):
            return NotImplemented
        return self.score == other.score

    def __ne__(self, other):
        """不等価 != """
        return not self.__eq__(other)

    def __lt__(self, other):
        """小なり < """
        if not isinstance(other, Student):
            return NotImplemented
        return self.score < other.score

    def __le__(self, other):
        """以下 <= """
        if not isinstance(other, Student):
            return NotImplemented
        return self.score <= other.score

    def __gt__(self, other):
        """大なり > """
        if not isinstance(other, Student):
            return NotImplemented
        return self.score > other.score

    def __ge__(self, other):
        """以上 >= """
        if not isinstance(other, Student):
            return NotImplemented
        return self.score >= other.score

    def __hash__(self):
        """ハッシュ値の計算(setやdictのキーに使用する場合)"""
        return hash((self.name, self.score))

    def __repr__(self):
        return f"Student('{self.name}', {self.score})"

# 使用例
students = [
    Student("Alice", 85),
    Student("Bob", 92),
    Student("Charlie", 78)
]

# ソート(__lt__が使用される)
sorted_students = sorted(students)
for student in sorted_students:
    print(f"{student.name}: {student.score}")

# 比較
alice = Student("Alice", 85)
bob = Student("Bob", 92)
print(alice < bob)   # True
print(alice == bob)  # False

__eq____lt__などの特殊メソッドを定義することで、==<などの演算子が数値のように機能します。これにより、sorted()関数で自動的にスコア順に並び替えることも可能です。さらに__hash__を実装することで、setdictのキーとしても利用できます。データを直感的に比較・整理できる設計になっています。

コンテナとしての振る舞い

リストや辞書のように振る舞うクラスを作成するための特殊メソッドです。以下のコードは、シーケンスプロトコルを実装したPlaylistクラスの例です。

class Playlist:
    def __init__(self, name):
        self.name = name
        self._songs = []

    def __len__(self):
        """len()関数で呼び出される"""
        return len(self._songs)

    def __getitem__(self, index):
        """インデックスアクセス playlist[i]"""
        return self._songs[index]

    def __setitem__(self, index, value):
        """インデックス代入 playlist[i] = song"""
        self._songs[index] = value

    def __delitem__(self, index):
        """del playlist[i]"""
        del self._songs[index]

    def __contains__(self, item):
        """in 演算子"""
        return item in self._songs

    def __iter__(self):
        """イテレーションのためのイテレータを返す"""
        return iter(self._songs)

    def __reversed__(self):
        """reversed()関数で呼び出される"""
        return reversed(self._songs)

    def append(self, song):
        """曲を追加"""
        self._songs.append(song)

    def insert(self, index, song):
        """曲を挿入"""
        self._songs.insert(index, song)

    def __add__(self, other):
        """プレイリストの結合"""
        if not isinstance(other, Playlist):
            return NotImplemented

        new_playlist = Playlist(f"{self.name} + {other.name}")
        new_playlist._songs = self._songs + other._songs
        return new_playlist

    def __repr__(self):
        return f"Playlist('{self.name}', {len(self)} songs)"

# 使用例
playlist = Playlist("My Favorites")
playlist.append("Song A")
playlist.append("Song B")
playlist.append("Song C")

print(len(playlist))           # 3
print(playlist[1])             # Song B
print("Song A" in playlist)    # True

# イテレーション
for song in playlist:
    print(song)

# スライシング
print(playlist[0:2])           # ['Song A', 'Song B']

# 逆順イテレーション
for song in reversed(playlist):
    print(song)

__len____getitem____setitem____delitem__などを定義することで、リストのように要素数取得・インデックスアクセス・代入・削除が可能になります。さらに、__contains__in演算子、__iter__で反復処理、__reversed__で逆順操作に対応しています。__add__により他のプレイリストと結合もでき、Pythonの組み込みリスト操作を自然な形で拡張した設計になっています。

コンテキストマネージャ

with文で使用するための特殊メソッドです。リソース管理に便利です。以下のコードは、コンテキストマネージャ(with文)を実現するための__enter____exit__メソッドのオーバーロードの例です。

class DatabaseConnection:
    def __init__(self, database_url):
        self.database_url = database_url
        self.connection = None

    def __enter__(self):
        """with文の開始時に呼び出される"""
        print(f"データベースに接続中: {self.database_url}")
        self.connection = f"Connection to {self.database_url}"
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """with文の終了時に呼び出される"""
        print("データベース接続を閉じています")
        self.connection = None
        if exc_type is not None:
            print(f"エラーが発生しました: {exc_val}")
        # Trueを返すと例外を抑制する
        return False

    def execute_query(self, query):
        """クエリを実行"""
        if self.connection is None:
            raise RuntimeError("データベースに接続されていません")
        print(f"クエリを実行: {query}")
        return f"Result of {query}"

# 使用例
with DatabaseConnection("postgresql://localhost/mydb") as db:
    result = db.execute_query("SELECT * FROM users")
    print(result)
# 自動的に接続が閉じられる

DatabaseConnectionクラスでは、with文の開始時に__enter__が呼ばれてデータベースへ接続し、終了時に__exit__が呼ばれて安全に接続を閉じます。これにより、接続の開閉を明示的に書かずにリソース管理を自動化でき、エラーが発生しても確実に後処理が行われる安全な設計になっています。

呼び出し可能オブジェクト

関数のように呼び出せるオブジェクトを作成します。以下のコードは、__call__メソッドのオーバーロードによって多項式オブジェクトを関数のように呼び出せるようにした例です。

class Polynomial:
    def __init__(self, *coefficients):
        """係数は高位から低位の順で指定"""
        self.coefficients = coefficients

    def __call__(self, x):
        """多項式の値を計算"""
        result = 0
        for power, coef in enumerate(reversed(self.coefficients)):
            result += coef * (x ** power)
        return result

    def __repr__(self):
        terms = []
        for power, coef in enumerate(reversed(self.coefficients)):
            if coef != 0:
                if power == 0:
                    terms.append(str(coef))
                elif power == 1:
                    terms.append(f"{coef}x")
                else:
                    terms.append(f"{coef}x^{power}")
        return " + ".join(reversed(terms)) if terms else "0"

# 使用例
p = Polynomial(2, -3, 1)  # 2x² - 3x + 1
print(p)                  # 2x^2 + -3x + 1
print(p(0))               # 1
print(p(1))               # 0
print(p(2))               # 3

Polynomialクラスは係数を高位から低位の順で受け取り、__call__を使って任意の値xで多項式の値を計算します。__repr__では多項式の式を文字列として整形し、数式のように表示できるようにしています。これにより、オブジェクトを関数的に扱う直感的な記述が可能になります。

属性アクセスのカスタマイズ

属性へのアクセスを動的に制御する特殊メソッドです。以下のコードは、__getattr____setattr____delattr__を利用して動的に属性を管理するクラスの例です。

class DynamicAttributes:
    def __init__(self):
        self._attributes = {}

    def __getattr__(self, name):
        """存在しない属性にアクセスした時に呼び出される"""
        if name in self._attributes:
            return self._attributes[name]
        else:
            # デフォルト値を返す代わりにAttributeErrorを発生させる
            raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")

    def __setattr__(self, name, value):
        """属性の設定時に呼び出される"""
        if name.startswith('_'):
            # 通常の属性設定
            super().__setattr__(name, value)
        else:
            # 動的属性
            self._attributes[name] = value

    def __delattr__(self, name):
        """属性の削除時に呼び出される"""
        if name in self._attributes:
            del self._attributes[name]
        else:
            super().__delattr__(name)

    def __dir__(self):
        """dir()関数で表示される属性のリスト"""
        standard_attrs = super().__dir__()
        dynamic_attrs = list(self._attributes.keys())
        return sorted(set(standard_attrs) | set(dynamic_attrs))

# 使用例
obj = DynamicAttributes()
obj.name = "Python"  # __setattr__が呼び出される
obj.version = 3.9

print(obj.name)      # __getattr__が呼び出される
print(obj.version)

print(dir(obj))      # 動的属性も含まれる

DynamicAttributesクラスでは、通常の属性と異なり、ユーザーが自由に追加・変更した属性を内部辞書_attributesに保存します。存在しない属性にアクセスすると__getattr__が呼ばれ、定義済みでなければAttributeErrorを発生させます。また__dir__をオーバーライドすることで、dir()関数に動的に追加された属性も表示されるようになっています。

実践的な例:ベクトルクラス

数学的なベクトルクラスを作成し、さまざまな特殊メソッドを実装してみましょう。以下のコードは、算術演算子のオーバーロードによってベクトル演算を直感的に扱えるようにしたVectorクラスです。

import math

class Vector:
    def __init__(self, *components):
        self.components = components
        self.dimension = len(components)

    def __repr__(self):
        return f"Vector{self.components}"

    def __str__(self):
        return f"Vector{self.components}"

    def __len__(self):
        return self.dimension

    def __getitem__(self, index):
        return self.components[index]

    def __abs__(self):
        """ベクトルの大きさ"""
        return math.sqrt(sum(x**2 for x in self.components))

    def __add__(self, other):
        """ベクトルの加算"""
        if not isinstance(other, Vector):
            return NotImplemented
        if self.dimension != other.dimension:
            raise ValueError("次元が異なるベクトルは加算できません")
        new_components = [a + b for a, b in zip(self.components, other.components)]
        return Vector(*new_components)

    def __sub__(self, other):
        """ベクトルの減算"""
        if not isinstance(other, Vector):
            return NotImplemented
        if self.dimension != other.dimension:
            raise ValueError("次元が異なるベクトルは減算できません")
        new_components = [a - b for a, b in zip(self.components, other.components)]
        return Vector(*new_components)

    def __mul__(self, other):
        """スカラー倍または内積"""
        if isinstance(other, (int, float)):
            # スカラー倍
            new_components = [x * other for x in self.components]
            return Vector(*new_components)
        elif isinstance(other, Vector):
            # 内積
            if self.dimension != other.dimension:
                raise ValueError("次元が異なるベクトルは内積を計算できません")
            return sum(a * b for a, b in zip(self.components, other.components))
        else:
            return NotImplemented

    def __rmul__(self, other):
        """右側からの乗算"""
        return self.__mul__(other)

    def __truediv__(self, scalar):
        """スカラー除算"""
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ValueError("0で除算できません")
            new_components = [x / scalar for x in self.components]
            return Vector(*new_components)
        return NotImplemented

    def __eq__(self, other):
        """等価比較"""
        if not isinstance(other, Vector):
            return NotImplemented
        return self.components == other.components

    def __ne__(self, other):
        return not self.__eq__(other)

    def dot(self, other):
        """内積(明示的なメソッド)"""
        return self * other

    def cross(self, other):
        """外積(3次元ベクトルのみ)"""
        if self.dimension != 3 or other.dimension != 3:
            raise ValueError("外積は3次元ベクトルのみで定義されます")
        a1, a2, a3 = self.components
        b1, b2, b3 = other.components
        return Vector(
            a2 * b3 - a3 * b2,
            a3 * b1 - a1 * b3,
            a1 * b2 - a2 * b1
        )

    def normalize(self):
        """単位ベクトルを返す"""
        magnitude = abs(self)
        if magnitude == 0:
            raise ValueError("零ベクトルは正規化できません")
        return self / magnitude

# 使用例
v1 = Vector(1, 2, 3)
v2 = Vector(4, 5, 6)

print(v1 + v2)      # Vector(5, 7, 9)
print(v1 - v2)      # Vector(-3, -3, -3)
print(v1 * 2)       # Vector(2, 4, 6)
print(2 * v1)       # Vector(2, 4, 6)
print(v1 * v2)      # 32 (内積)
print(v1 / 2)       # Vector(0.5, 1.0, 1.5)
print(abs(v1))      # 3.741... (大きさ)
print(v1.cross(v2)) # Vector(-3, 6, -3) (外積)

__add____sub__でベクトルの加減算、__mul__でスカラー倍や内積、__truediv__でスカラー除算を定義しています。さらに__abs__でベクトルの大きさ、cross()で外積、normalize()で単位ベクトルを計算できます。これにより、数学的なベクトル演算をPythonの演算子を使って自然に表現できる設計になっています。

まとめ

特殊メソッドは、Pythonにおけるクラス設計をより柔軟かつ自然にするための強力な仕組みです。

__init____del__でオブジェクトの初期化・終了を制御し、__str____repr__で見やすい文字列表現を定義できます。さらに、__add____mul__などで算術演算、__eq____lt__で比較演算を行えるほか、__len____getitem__などによりリストのような動作を持つクラスも実現可能です。

また、__call__で関数のような呼び出し、__enter____exit__でリソース管理、__getattr____setattr__で動的な属性制御も可能です。これらを適切に実装することで、Pythonの文法と自然に連携し、可読性・拡張性に優れた「Pythonic」なコードを構築できます。


演習問題

初級問題

問題1
Rectangleクラスに__str____repr__メソッドを実装してください。__str__では「矩形: 幅x高さ」の形式で、__repr__ではRectangle(幅, 高さ)の形式で表示されるようにしてください。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

問題2
BankAccountクラスに__add__メソッドを実装して、2つの口座を合併できるようにしてください。残高は合算され、口座名義は「名前1 & 名前2」の形式にしてください。

問題3
Bookクラスに__len__メソッドを実装して、本のページ数を返すようにしてください。また__getitem__メソッドを実装して、インデックスアクセスで章のタイトルを取得できるようにしてください。

中級問題

問題4
Fractionクラスを完成させて、分数同士の四則演算(+, -, *, /)と比較演算(==, <, >)ができるようにしてください。

問題5
Playlistクラスにコンテナとしての振る舞いを実装してください。__len__, __getitem__, __setitem__, __contains__, __iter__メソッドを実装し、曲の追加、削除、検索ができるようにしてください。

問題6
Polynomialクラスを作成し、多項式の計算ができるようにしてください。__init__で係数を受け取り、__call__で多項式の値を計算し、__add____sub__で多項式の加算・減算を実装してください。

問題7
Vectorクラスにベクトル演算を実装してください。加算、減算、スカラー倍、内積、大きさの計算ができるようにし、__abs__メソッドでベクトルの大きさを返すようにしてください。

問題8
DatabaseConnectionクラスを作成し、コンテキストマネージャとして機能するようにしてください。__enter__で接続を確立し、__exit__で接続を閉じるように実装してください。

問題9
Temperatureクラスを作成し、摂氏と華氏の変換ができるようにしてください。__init__で摂氏温度を受け取り、__float__で華氏温度を返すようにし、比較演算子も実装してください。

上級問題

問題10
Matrixクラスを作成し、行列演算を実装してください。加算、減算、乗算、転置ができるようにし、__matmul__メソッドで行列積を計算できるようにしてください。

問題11
SmartDictionaryクラスを作成し、属性風のアクセスと辞書風のアクセスの両方をサポートしてください。__getattr__, __setattr__, __getitem__, __setitem__メソッドを適切に実装してください。

問題12
TaskManagerクラスを作成し、コンテキストマネージャとイテレータの両方の機能を持たせてください。__enter__でタスクの開始を、__exit__で終了を記録し、__iter__で完了したタスクをイテレートできるようにしてください。

初級問題

問題1 解答

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def __str__(self):
        """ユーザー向けの文字列表現"""
        return f"矩形: {self.width}x{self.height}"

    def __repr__(self):
        """開発者向けの文字列表現"""
        return f"Rectangle({self.width}, {self.height})"

    def area(self):
        """面積を計算"""
        return self.width * self.height

    def perimeter(self):
        """周囲長を計算"""
        return 2 * (self.width + self.height)

# 使用例
rect = Rectangle(5, 3)
print(str(rect))    # 矩形: 5x3
print(repr(rect))   # Rectangle(5, 3)
print(f"面積: {rect.area()}")        # 面積: 15
print(f"周囲長: {rect.perimeter()}") # 周囲長: 16

# リストに入れた場合の表示
rectangles = [Rectangle(2, 4), Rectangle(3, 5)]
print(rectangles)   # [Rectangle(2, 4), Rectangle(3, 5)]

解説: __str__はユーザーフレンドリーな表示、__repr__は開発者向けの明確な表現を返します。

問題2 解答

class BankAccount:
    def __init__(self, account_holder, balance=0):
        self.account_holder = account_holder
        self.balance = balance

    def __add__(self, other):
        """2つの口座を合併"""
        if not isinstance(other, BankAccount):
            return NotImplemented

        new_holder = f"{self.account_holder} & {other.account_holder}"
        new_balance = self.balance + other.balance

        # 新しい口座オブジェクトを作成
        new_account = BankAccount(new_holder, new_balance)
        return new_account

    def __str__(self):
        return f"口座名義: {self.account_holder}, 残高: {self.balance}円"

    def deposit(self, amount):
        """預入"""
        if amount > 0:
            self.balance += amount
            print(f"{amount}円預け入れました")
        return self.balance

    def withdraw(self, amount):
        """引出"""
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"{amount}円引き出しました")
        return self.balance

# 使用例
account1 = BankAccount("山田太郎", 100000)
account2 = BankAccount("佐藤花子", 50000)

print("合併前:")
print(account1)  # 口座名義: 山田太郎, 残高: 100000円
print(account2)  # 口座名義: 佐藤花子, 残高: 50000円

# 口座の合併
merged_account = account1 + account2
print("\n合併後:")
print(merged_account)  # 口座名義: 山田太郎 & 佐藤花子, 残高: 150000円

# 演算の結果、元の口座は変更されない
print(f"\n元の口座1: {account1.balance}円")  # 100000円
print(f"元の口座2: {account2.balance}円")  # 50000円

問題3 解答

class Book:
    def __init__(self, title, author, pages, chapters=None):
        self.title = title
        self.author = author
        self.pages = pages
        self.chapters = chapters if chapters is not None else []

    def __len__(self):
        """本のページ数を返す"""
        return self.pages

    def __getitem__(self, index):
        """章のタイトルを取得"""
        if 0 <= index < len(self.chapters):
            return self.chapters[index]
        elif index < 0 and abs(index) <= len(self.chapters):
            return self.chapters[index]
        else:
            raise IndexError("章のインデックスが範囲外です")

    def __str__(self):
        return f"『{self.title}』 - {self.author}"

    def __repr__(self):
        return f"Book('{self.title}', '{self.author}', {self.pages})"

    def add_chapter(self, chapter_title):
        """章を追加"""
        self.chapters.append(chapter_title)

    def get_chapter_count(self):
        """章の数を取得"""
        return len(self.chapters)

# 使用例
book = Book("Python学習ガイド", "田中哲也", 350)
book.add_chapter("Pythonの基礎")
book.add_chapter("関数とクラス")
book.add_chapter("データ構造")
book.add_chapter("実践プロジェクト")

print(f"本: {book}")
print(f"ページ数: {len(book)}ページ")  # __len__が呼ばれる
print(f"章の数: {book.get_chapter_count()}")

# 章へのアクセス
print(f"第1章: {book[0]}")      # __getitem__が呼ばれる
print(f"第2章: {book[1]}")
print(f"最後の章: {book[-1]}")

# スライシングもサポート(__getitem__がスライスオブジェクトを処理)
try:
    print(f"最初の2章: {book[0:2]}")  # ['Pythonの基礎', '関数とクラス']
except TypeError:
    print("スライシングはサポートしていません")

# イテレーション
print("\nすべての章:")
for i, chapter in enumerate(book.chapters, 1):
    print(f"第{i}章: {chapter}")

中級問題

問題4 解答

import math

class Fraction:
    def __init__(self, numerator, denominator=1):
        if denominator == 0:
            raise ValueError("分母は0にできません")
        self.numerator = numerator
        self.denominator = denominator
        self._simplify()

    def _gcd(self, a, b):
        """最大公約数を計算"""
        while b:
            a, b = b, a % b
        return a

    def _simplify(self):
        """分数を約分"""
        gcd = self._gcd(abs(self.numerator), abs(self.denominator))
        self.numerator //= gcd
        self.denominator //= gcd

        # 分母を正にする
        if self.denominator < 0:
            self.numerator = -self.numerator
            self.denominator = -self.denominator

    def __add__(self, other):
        """加算: self + other"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = (self.numerator * other.denominator + 
                        other.numerator * self.denominator)
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __radd__(self, other):
        """右側加算: other + self"""
        return self.__add__(other)

    def __sub__(self, other):
        """減算: self - other"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = (self.numerator * other.denominator - 
                        other.numerator * self.denominator)
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __rsub__(self, other):
        """右側減算: other - self"""
        if isinstance(other, int):
            return Fraction(other) - self
        return NotImplemented

    def __mul__(self, other):
        """乗算: self * other"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        new_numerator = self.numerator * other.numerator
        new_denominator = self.denominator * other.denominator
        return Fraction(new_numerator, new_denominator)

    def __rmul__(self, other):
        """右側乗算: other * self"""
        return self.__mul__(other)

    def __truediv__(self, other):
        """除算: self / other"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented
        if other.numerator == 0:
            raise ZeroDivisionError("0で除算できません")

        new_numerator = self.numerator * other.denominator
        new_denominator = self.denominator * other.numerator
        return Fraction(new_numerator, new_denominator)

    def __rtruediv__(self, other):
        """右側除算: other / self"""
        if isinstance(other, int):
            return Fraction(other) / self
        return NotImplemented

    def __eq__(self, other):
        """等価: self == other"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        return (self.numerator == other.numerator and 
                self.denominator == other.denominator)

    def __ne__(self, other):
        """不等価: self != other"""
        return not self.__eq__(other)

    def __lt__(self, other):
        """小なり: self < other"""
        if isinstance(other, int):
            other = Fraction(other)
        if not isinstance(other, Fraction):
            return NotImplemented

        return (self.numerator * other.denominator < 
                other.numerator * self.denominator)

    def __le__(self, other):
        """以下: self <= other"""
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        """大なり: self > other"""
        return not self.__le__(other)

    def __ge__(self, other):
        """以上: self >= other"""
        return not self.__lt__(other)

    def __float__(self):
        """float変換"""
        return self.numerator / self.denominator

    def __str__(self):
        if self.denominator == 1:
            return str(self.numerator)
        return f"{self.numerator}/{self.denominator}"

    def __repr__(self):
        return f"Fraction({self.numerator}, {self.denominator})"

# 使用例
f1 = Fraction(1, 2)
f2 = Fraction(3, 4)

print(f"f1 = {f1}")  # 1/2
print(f"f2 = {f2}")  # 3/4

# 四則演算
print(f"\n{f1} + {f2} = {f1 + f2}")      # 1/2 + 3/4 = 5/4
print(f"{f1} - {f2} = {f1 - f2}")      # 1/2 - 3/4 = -1/4
print(f"{f1} * {f2} = {f1 * f2}")      # 1/2 * 3/4 = 3/8
print(f"{f1} / {f2} = {f1 / f2}")      # 1/2 / 3/4 = 2/3

# 比較演算
print(f"\n{f1} == {f2}: {f1 == f2}")    # False
print(f"{f1} < {f2}: {f1 < f2}")       # True
print(f"{f1} > {f2}: {f1 > f2}")       # False

# 整数との演算
print(f"\n{f1} + 1 = {f1 + 1}")        # 1/2 + 1 = 3/2
print(f"2 * {f2} = {2 * f2}")          # 2 * 3/4 = 3/2

# float変換
print(f"\nfloat({f1}) = {float(f1)}")  # 0.5

問題5 解答

class Playlist:
    def __init__(self, name):
        self.name = name
        self._songs = []

    def __len__(self):
        """プレイリストの曲数を返す"""
        return len(self._songs)

    def __getitem__(self, index):
        """インデックスアクセスをサポート"""
        if isinstance(index, slice):
            # スライシングの場合
            return self._songs[index]
        elif isinstance(index, int):
            # 単一インデックスの場合
            if 0 <= index < len(self._songs) or (index < 0 and abs(index) <= len(self._songs)):
                return self._songs[index]
            else:
                raise IndexError("プレイリストのインデックスが範囲外です")
        else:
            raise TypeError("インデックスは整数またはスライスである必要があります")

    def __setitem__(self, index, value):
        """インデックスでの代入をサポート"""
        if 0 <= index < len(self._songs):
            self._songs[index] = value
        else:
            raise IndexError("プレイリストのインデックスが範囲外です")

    def __delitem__(self, index):
        """インデックスでの削除をサポート"""
        if 0 <= index < len(self._songs):
            del self._songs[index]
        else:
            raise IndexError("プレイリストのインデックスが範囲外です")

    def __contains__(self, item):
        """in演算子をサポート"""
        return item in self._songs

    def __iter__(self):
        """イテレーションをサポート"""
        return iter(self._songs)

    def __str__(self):
        return f"Playlist: {self.name} ({len(self)} songs)"

    def __repr__(self):
        return f"Playlist('{self.name}', {self._songs})"

    def add_song(self, song):
        """曲を追加"""
        self._songs.append(song)
        print(f"'{song}'をプレイリストに追加しました")

    def remove_song(self, song):
        """曲を削除"""
        if song in self._songs:
            self._songs.remove(song)
            print(f"'{song}'をプレイリストから削除しました")
            return True
        else:
            print(f"'{song}'はプレイリストに存在しません")
            return False

    def clear(self):
        """プレイリストを空にする"""
        self._songs.clear()
        print("プレイリストを空にしました")

    def shuffle(self):
        """プレイリストをシャッフル"""
        import random
        random.shuffle(self._songs)
        print("プレイリストをシャッフルしました")

# 使用例
playlist = Playlist("お気に入り")

# 曲を追加
playlist.add_song("Song A")
playlist.add_song("Song B")
playlist.add_song("Song C")
playlist.add_song("Song D")
playlist.add_song("Song E")

print(f"\n{playlist}")
print(f"曲数: {len(playlist)}")  # __len__

# インデックスアクセス
print(f"\n最初の曲: {playlist[0]}")    # __getitem__
print(f"最後の曲: {playlist[-1]}")

# スライシング
print(f"\n最初の3曲: {playlist[0:3]}")

# 包含確認
print(f"\n'Song B'は含まれる: {'Song B' in playlist}")  # __contains__

# イテレーション
print(f"\nすべての曲:")
for song in playlist:  # __iter__
    print(f"  - {song}")

# 曲の更新
playlist[2] = "New Song C"  # __setitem__
print(f"\n3曲目を更新: {playlist[2]}")

# 曲の削除
del playlist[1]  # __delitem__
print(f"2曲目を削除後: {len(playlist)}曲")

# シャッフル
playlist.shuffle()
print(f"\nシャッフル後: {list(playlist)}")

問題6 解答

class Polynomial:
    def __init__(self, *coefficients):
        """
        多項式を初期化
        係数は高位から低位の順で指定: aₙxⁿ + aₙ₋₁xⁿ⁻¹ + ... + a₁x + a₀
        """
        self.coefficients = list(coefficients)
        # 先頭の0係数を削除
        while len(self.coefficients) > 1 and self.coefficients[0] == 0:
            self.coefficients.pop(0)

    def __call__(self, x):
        """多項式の値を計算: P(x)"""
        result = 0
        n = len(self.coefficients) - 1
        for i, coef in enumerate(self.coefficients):
            power = n - i
            result += coef * (x ** power)
        return result

    def __add__(self, other):
        """多項式の加算"""
        if not isinstance(other, Polynomial):
            # 定数との加算をサポート
            if isinstance(other, (int, float)):
                new_coeffs = self.coefficients.copy()
                new_coeffs[-1] += other
                return Polynomial(*new_coeffs)
            return NotImplemented

        # 係数の数を揃える
        max_len = max(len(self.coefficients), len(other.coefficients))
        self_padded = [0] * (max_len - len(self.coefficients)) + self.coefficients
        other_padded = [0] * (max_len - len(other.coefficients)) + other.coefficients

        # 係数を足し合わせる
        new_coeffs = [a + b for a, b in zip(self_padded, other_padded)]
        return Polynomial(*new_coeffs)

    def __radd__(self, other):
        """右側加算"""
        return self.__add__(other)

    def __sub__(self, other):
        """多項式の減算"""
        if not isinstance(other, Polynomial):
            # 定数との減算をサポート
            if isinstance(other, (int, float)):
                new_coeffs = self.coefficients.copy()
                new_coeffs[-1] -= other
                return Polynomial(*new_coeffs)
            return NotImplemented

        # 係数の数を揃える
        max_len = max(len(self.coefficients), len(other.coefficients))
        self_padded = [0] * (max_len - len(self.coefficients)) + self.coefficients
        other_padded = [0] * (max_len - len(other.coefficients)) + other.coefficients

        # 係数を引き算する
        new_coeffs = [a - b for a, b in zip(self_padded, other_padded)]
        return Polynomial(*new_coeffs)

    def __rsub__(self, other):
        """右側減算"""
        if isinstance(other, (int, float)):
            # 定数 - 多項式
            constant_poly = Polynomial(other)
            return constant_poly - self
        return NotImplemented

    def degree(self):
        """多項式の次数を返す"""
        return len(self.coefficients) - 1

    def __str__(self):
        """多項式の文字列表現"""
        if all(coef == 0 for coef in self.coefficients):
            return "0"

        terms = []
        n = len(self.coefficients) - 1

        for i, coef in enumerate(self.coefficients):
            power = n - i

            if coef == 0:
                continue

            if power == 0:
                term = str(coef)
            elif power == 1:
                term = f"{coef}x" if coef != 1 else "x"
            else:
                term = f"{coef}x^{power}" if coef != 1 else f"x^{power}"

            # 符号の処理
            if coef < 0:
                term = term if term.startswith('-') else f"-{term.lstrip('+')}"
            elif terms:  # 最初の項でなければ+を付ける
                term = f"+ {term}"

            terms.append(term)

        return ' '.join(terms) if terms else "0"

    def __repr__(self):
        return f"Polynomial{tuple(self.coefficients)}"

# 使用例
# P(x) = 2x² + 3x + 1
p1 = Polynomial(2, 3, 1)
print(f"p1 = {p1}")  # 2x^2 + 3x + 1

# Q(x) = x² - 2x + 4
p2 = Polynomial(1, -2, 4)
print(f"p2 = {p2}")  # x^2 - 2x + 4

# 多項式の計算
x = 2
print(f"\np1({x}) = {p1(x)}")  # p1(2) = 15
print(f"p2({x}) = {p2(x)}")  # p2(2) = 4

# 加算
p3 = p1 + p2
print(f"\np1 + p2 = {p3}")  # 3x^2 + x + 5

# 減算
p4 = p1 - p2
print(f"p1 - p2 = {p4}")  # x^2 + 5x - 3

# 定数との演算
p5 = p1 + 5
print(f"p1 + 5 = {p5}")  # 2x^2 + 3x + 6

p6 = 3 + p2
print(f"3 + p2 = {p6}")  # x^2 - 2x + 7

# 次数
print(f"\np1の次数: {p1.degree()}")  # 2
print(f"p2の次数: {p2.degree()}")  # 2

# 異なる次数の多項式
p7 = Polynomial(1, 0, 2)  # x² + 2
p8 = Polynomial(3, 1)     # 3x + 1
print(f"\n({p7}) + ({p8}) = {p7 + p8}")  # x^2 + 3x + 3

問題7 解答

import math

class Vector:
    def __init__(self, *components):
        self.components = list(components)
        self.dimension = len(components)

    def __abs__(self):
        """ベクトルの大きさ(ノルム)"""
        return math.sqrt(sum(x**2 for x in self.components))

    def __add__(self, other):
        """ベクトルの加算"""
        if not isinstance(other, Vector):
            return NotImplemented
        if self.dimension != other.dimension:
            raise ValueError("次元が異なるベクトルは加算できません")

        new_components = [a + b for a, b in zip(self.components, other.components)]
        return Vector(*new_components)

    def __sub__(self, other):
        """ベクトルの減算"""
        if not isinstance(other, Vector):
            return NotImplemented
        if self.dimension != other.dimension:
            raise ValueError("次元が異なるベクトルは減算できません")

        new_components = [a - b for a, b in zip(self.components, other.components)]
        return Vector(*new_components)

    def __mul__(self, other):
        """スカラー倍または内積"""
        if isinstance(other, (int, float)):
            # スカラー倍
            new_components = [x * other for x in self.components]
            return Vector(*new_components)
        elif isinstance(other, Vector):
            # 内積
            if self.dimension != other.dimension:
                raise ValueError("次元が異なるベクトルは内積を計算できません")
            return sum(a * b for a, b in zip(self.components, other.components))
        else:
            return NotImplemented

    def __rmul__(self, other):
        """右側からの乗算"""
        return self.__mul__(other)

    def __truediv__(self, scalar):
        """スカラー除算"""
        if isinstance(scalar, (int, float)):
            if scalar == 0:
                raise ZeroDivisionError("0で除算できません")
            new_components = [x / scalar for x in self.components]
            return Vector(*new_components)
        return NotImplemented

    def __eq__(self, other):
        """等価比較"""
        if not isinstance(other, Vector):
            return NotImplemented
        return self.components == other.components

    def __ne__(self, other):
        return not self.__eq__(other)

    def __getitem__(self, index):
        """インデックスアクセス"""
        return self.components[index]

    def __len__(self):
        """ベクトルの次元"""
        return self.dimension

    def __str__(self):
        return f"Vector{tuple(self.components)}"

    def __repr__(self):
        return f"Vector{tuple(self.components)}"

    def dot(self, other):
        """内積(明示的なメソッド)"""
        return self * other

    def cross(self, other):
        """外積(3次元ベクトルのみ)"""
        if self.dimension != 3 or other.dimension != 3:
            raise ValueError("外積は3次元ベクトルのみで定義されます")

        a1, a2, a3 = self.components
        b1, b2, b3 = other.components

        return Vector(
            a2 * b3 - a3 * b2,
            a3 * b1 - a1 * b3,
            a1 * b2 - a2 * b1
        )

    def normalize(self):
        """単位ベクトルを返す"""
        magnitude = abs(self)
        if magnitude == 0:
            raise ValueError("零ベクトルは正規化できません")
        return self / magnitude

    def angle_between(self, other):
        """2つのベクトル間の角度を計算(ラジアン)"""
        if self.dimension != other.dimension:
            raise ValueError("次元が異なるベクトル間の角度は計算できません")

        dot_product = self.dot(other)
        magnitudes = abs(self) * abs(other)

        if magnitudes == 0:
            raise ValueError("零ベクトルを含む角度は計算できません")

        # 数値誤差を考慮
        cos_theta = max(-1.0, min(1.0, dot_product / magnitudes))
        return math.acos(cos_theta)

# 使用例
v1 = Vector(1, 2, 3)
v2 = Vector(4, 5, 6)

print(f"v1 = {v1}")
print(f"v2 = {v2}")

# ベクトルの大きさ
print(f"\n|v1| = {abs(v1):.2f}")  # 3.74
print(f"|v2| = {abs(v2):.2f}")  # 8.77

# 加算・減算
print(f"\nv1 + v2 = {v1 + v2}")  # Vector(5, 7, 9)
print(f"v1 - v2 = {v1 - v2}")  # Vector(-3, -3, -3)

# スカラー倍
print(f"\n2 * v1 = {2 * v1}")    # Vector(2, 4, 6)
print(f"v1 * 3 = {v1 * 3}")    # Vector(3, 6, 9)

# 内積
print(f"\nv1 · v2 = {v1.dot(v2)}")  # 32
print(f"v1 · v2 = {v1 * v2}")      # 32(演算子使用)

# 外積(3次元のみ)
print(f"\nv1 × v2 = {v1.cross(v2)}")  # Vector(-3, 6, -3)

# 単位ベクトル
v1_unit = v1.normalize()
print(f"\nv1の単位ベクトル = {v1_unit}")
print(f"単位ベクトルの大きさ = {abs(v1_unit):.6f}")  # 1.000000

# 角度
angle = v1.angle_between(v2)
print(f"\nv1とv2の角度 = {math.degrees(angle):.1f}°")  # 12.9°

問題8 解答

import sqlite3
from datetime import datetime

class DatabaseConnection:
    def __init__(self, database_url):
        self.database_url = database_url
        self.connection = None
        self.cursor = None
        self.is_connected = False

    def __enter__(self):
        """with文の開始時に呼び出される"""
        try:
            print(f"データベースに接続中: {self.database_url}")
            self.connection = sqlite3.connect(self.database_url)
            self.cursor = self.connection.cursor()
            self.is_connected = True
            print("データベース接続が確立されました")
            return self
        except Exception as e:
            print(f"データベース接続エラー: {e}")
            raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        """with文の終了時に呼び出される"""
        print("データベース接続を閉じています...")

        if self.cursor:
            self.cursor.close()

        if self.connection:
            if exc_type is not None:
                # エラーが発生した場合はロールバック
                print("エラーが発生したため、変更をロールバックします")
                self.connection.rollback()
            else:
                # 正常終了の場合はコミット
                print("変更をコミットします")
                self.connection.commit()

            self.connection.close()

        self.is_connected = False
        print("データベース接続が閉じられました")

        # エラーを抑制しない(Falseを返す)
        return False

    def execute_query(self, query, parameters=None):
        """SQLクエリを実行"""
        if not self.is_connected:
            raise RuntimeError("データベースに接続されていません")

        try:
            if parameters:
                self.cursor.execute(query, parameters)
            else:
                self.cursor.execute(query)

            print(f"クエリを実行: {query}")
            return self.cursor
        except Exception as e:
            print(f"クエリ実行エラー: {e}")
            raise

    def create_table(self, table_name, columns):
        """テーブルを作成"""
        column_defs = ', '.join(columns)
        query = f"CREATE TABLE IF NOT EXISTS {table_name} ({column_defs})"
        self.execute_query(query)
        print(f"テーブル '{table_name}' を作成または既に存在します")

    def insert_data(self, table_name, data):
        """データを挿入"""
        placeholders = ', '.join(['?' for _ in data])
        columns = ', '.join(data.keys())
        values = list(data.values())

        query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
        self.execute_query(query, values)
        print(f"データをテーブル '{table_name}' に挿入しました")

    def fetch_all(self, table_name, condition=None):
        """すべてのデータを取得"""
        query = f"SELECT * FROM {table_name}"
        if condition:
            query += f" WHERE {condition}"

        cursor = self.execute_query(query)
        return cursor.fetchall()

    def get_table_info(self):
        """データベースのテーブル情報を取得"""
        cursor = self.execute_query("SELECT name FROM sqlite_master WHERE type='table'")
        tables = cursor.fetchall()
        return [table[0] for table in tables]

# 使用例
def demo_database_operations():
    # データベースファイル(メモリ上に作成)
    db_url = ":memory:"

    try:
        with DatabaseConnection(db_url) as db:
            # テーブル作成
            db.create_table("users", [
                "id INTEGER PRIMARY KEY AUTOINCREMENT",
                "name TEXT NOT NULL",
                "email TEXT UNIQUE NOT NULL",
                "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
            ])

            # データ挿入
            users_data = [
                {"name": "山田太郎", "email": "yamada@example.com"},
                {"name": "佐藤花子", "email": "sato@example.com"},
                {"name": "鈴木一郎", "email": "suzuki@example.com"}
            ]

            for user in users_data:
                db.insert_data("users", user)

            # データ取得
            print("\nすべてのユーザー:")
            users = db.fetch_all("users")
            for user in users:
                print(f"  ID: {user[0]}, 名前: {user[1]}, メール: {user[2]}")

            # テーブル情報
            tables = db.get_table_info()
            print(f"\nデータベース内のテーブル: {tables}")

            # 条件付き検索
            print("\n名前が '山田太郎' のユーザー:")
            specific_user = db.fetch_all("users", "name = '山田太郎'")
            for user in specific_user:
                print(f"  ID: {user[0]}, 名前: {user[1]}, メール: {user[2]}")

    except Exception as e:
        print(f"エラーが発生しました: {e}")

# 実行
demo_database_operations()

# コンテキストマネージャの利点を実演
print("\n" + "="*50)
print("エラーハンドリングのデモ")

def demo_error_handling():
    db_url = ":memory:"

    try:
        with DatabaseConnection(db_url) as db:
            # テーブル作成
            db.create_table("test", ["id INTEGER PRIMARY KEY"])

            # 意図的にエラーを発生させる(重複キー)
            db.execute_query("INSERT INTO test (id) VALUES (1)")
            db.execute_query("INSERT INTO test (id) VALUES (1)")  # 重複エラー

    except Exception as e:
        print(f"エラーをキャッチ: {e}")

demo_error_handling()

問題9 解答

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError("温度は数値である必要があります")
        if value < -273.15:
            raise ValueError("絶対零度以下は設定できません")
        self._celsius = value

    @property
    def fahrenheit(self):
        """華氏温度を計算"""
        return (self._celsius * 9/5) + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        """華氏温度から摂氏温度を設定"""
        if not isinstance(value, (int, float)):
            raise TypeError("温度は数値である必要があります")
        self._celsius = (value - 32) * 5/9

    def __float__(self):
        """float()で呼び出され、華氏温度を返す"""
        return self.fahrenheit

    def __int__(self):
        """int()で呼び出され、摂氏温度を整数で返す"""
        return int(self._celsius)

    def __eq__(self, other):
        """等価比較 == """
        if isinstance(other, Temperature):
            return self._celsius == other._celsius
        elif isinstance(other, (int, float)):
            return self._celsius == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """小なり比較 < """
        if isinstance(other, Temperature):
            return self._celsius < other._celsius
        elif isinstance(other, (int, float)):
            return self._celsius < other
        else:
            return NotImplemented

    def __le__(self, other):
        """以下比較 <= """
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        """大なり比較 > """
        return not self.__le__(other)

    def __ge__(self, other):
        """以上比較 >= """
        return not self.__lt__(other)

    def __add__(self, other):
        """温度の加算"""
        if isinstance(other, Temperature):
            new_celsius = self._celsius + other._celsius
        elif isinstance(other, (int, float)):
            new_celsius = self._celsius + other
        else:
            return NotImplemented
        return Temperature(new_celsius)

    def __sub__(self, other):
        """温度の減算"""
        if isinstance(other, Temperature):
            new_celsius = self._celsius - other._celsius
        elif isinstance(other, (int, float)):
            new_celsius = self._celsius - other
        else:
            return NotImplemented
        return Temperature(new_celsius)

    def __mul__(self, other):
        """温度の乗算(スカラーのみ)"""
        if isinstance(other, (int, float)):
            new_celsius = self._celsius * other
            return Temperature(new_celsius)
        return NotImplemented

    def __truediv__(self, other):
        """温度の除算(スカラーのみ)"""
        if isinstance(other, (int, float)):
            if other == 0:
                raise ZeroDivisionError("0で除算できません")
            new_celsius = self._celsius / other
            return Temperature(new_celsius)
        return NotImplemented

    def __str__(self):
        return f"{self._celsius:.1f}°C ({self.fahrenheit:.1f}°F)"

    def __repr__(self):
        return f"Temperature({self._celsius})"

    def to_kelvin(self):
        """ケルビン温度を返す"""
        return self._celsius + 273.15

    @classmethod
    def from_fahrenheit(cls, fahrenheit):
        """華氏温度からTemperatureオブジェクトを作成"""
        celsius = (fahrenheit - 32) * 5/9
        return cls(celsius)

    @classmethod
    def from_kelvin(cls, kelvin):
        """ケルビン温度からTemperatureオブジェクトを作成"""
        celsius = kelvin - 273.15
        return cls(celsius)

# 使用例
# 基本的な使用
temp1 = Temperature(25)
print(f"温度1: {temp1}")  # 25.0°C (77.0°F)
print(f"華氏: {float(temp1):.1f}°F")  # __float__が呼ばれる
print(f"摂氏(整数): {int(temp1)}°C")  # __int__が呼ばれる

# 華氏から作成
temp2 = Temperature.from_fahrenheit(100)
print(f"\n華氏100度: {temp2}")  # 37.8°C (100.0°F)

# ケルビンから作成
temp3 = Temperature.from_kelvin(300)
print(f"ケルビン300度: {temp3}")  # 26.9°C (80.3°F)

# 比較演算
print(f"\n比較演算:")
print(f"temp1 == 25: {temp1 == 25}")        # True
print(f"temp1 < temp2: {temp1 < temp2}")    # True
print(f"temp1 > 20: {temp1 > 20}")          # True

# 算術演算
print(f"\n算術演算:")
temp4 = temp1 + 10
print(f"temp1 + 10 = {temp4}")  # 35.0°C (95.0°F)

temp5 = temp2 - temp1
print(f"temp2 - temp1 = {temp5}")  # 12.8°C (55.0°F)

temp6 = temp1 * 2
print(f"temp1 * 2 = {temp6}")  # 50.0°C (122.0°F)

temp7 = temp1 / 2
print(f"temp1 / 2 = {temp7}")  # 12.5°C (54.5°F)

# 華氏温度の設定
temp8 = Temperature(0)
temp8.fahrenheit = 32  # 氷点
print(f"\n華氏32度: {temp8}")  # 0.0°C (32.0°F)

# ソートのデモ
temperatures = [Temperature(30), Temperature(15), Temperature(25), Temperature(10)]
sorted_temps = sorted(temperatures)  # __lt__が使用される
print(f"\n温度のソート:")
for temp in sorted_temps:
    print(f"  {temp}")

# エラーハンドリング
try:
    temp_invalid = Temperature(-300)  # 絶対零度以下
except ValueError as e:
    print(f"\nエラー: {e}")

上級問題

問題10 解答

class Matrix:
    def __init__(self, data):
        """
        行列を初期化
        data: 2次元リスト [[a11, a12, ...], [a21, a22, ...], ...]
        """
        if not all(len(row) == len(data[0]) for row in data):
            raise ValueError("すべての行の長さが同じである必要があります")

        self.data = data
        self.rows = len(data)
        self.cols = len(data[0])

    def __add__(self, other):
        """行列の加算"""
        if not isinstance(other, Matrix):
            return NotImplemented
        if self.rows != other.rows or self.cols != other.cols:
            raise ValueError("加算する行列の次元が一致しません")

        result_data = [
            [self.data[i][j] + other.data[i][j] for j in range(self.cols)]
            for i in range(self.rows)
        ]
        return Matrix(result_data)

    def __sub__(self, other):
        """行列の減算"""
        if not isinstance(other, Matrix):
            return NotImplemented
        if self.rows != other.rows or self.cols != other.cols:
            raise ValueError("減算する行列の次元が一致しません")

        result_data = [
            [self.data[i][j] - other.data[i][j] for j in range(self.cols)]
            for i in range(self.rows)
        ]
        return Matrix(result_data)

    def __mul__(self, other):
        """行列のスカラー倍または要素ごとの乗算"""
        if isinstance(other, (int, float)):
            # スカラー倍
            result_data = [
                [self.data[i][j] * other for j in range(self.cols)]
                for i in range(self.rows)
            ]
            return Matrix(result_data)
        elif isinstance(other, Matrix):
            # 要素ごとの乗算(アダマール積)
            if self.rows != other.rows or self.cols != other.cols:
                raise ValueError("要素ごとの乗算する行列の次元が一致しません")

            result_data = [
                [self.data[i][j] * other.data[i][j] for j in range(self.cols)]
                for i in range(self.rows)
            ]
            return Matrix(result_data)
        else:
            return NotImplemented

    def __rmul__(self, other):
        """右側からの乗算"""
        return self.__mul__(other)

    def __matmul__(self, other):
        """行列積 @ 演算子"""
        if not isinstance(other, Matrix):
            return NotImplemented
        if self.cols != other.rows:
            raise ValueError("行列積: 左行列の列数と右行列の行数が一致しません")

        result_data = [
            [
                sum(self.data[i][k] * other.data[k][j] for k in range(self.cols))
                for j in range(other.cols)
            ]
            for i in range(self.rows)
        ]
        return Matrix(result_data)

    def __getitem__(self, index):
        """要素へのアクセス"""
        if isinstance(index, tuple):
            i, j = index
            return self.data[i][j]
        else:
            return self.data[index]

    def __setitem__(self, index, value):
        """要素の設定"""
        if isinstance(index, tuple):
            i, j = index
            self.data[i][j] = value
        else:
            raise TypeError("行全体の代入はサポートしていません")

    def __eq__(self, other):
        """等価比較"""
        if not isinstance(other, Matrix):
            return NotImplemented
        return self.data == other.data

    def __str__(self):
        """行列の文字列表現"""
        if self.rows == 0 or self.cols == 0:
            return "[]"

        # 各列の最大幅を計算
        col_widths = [
            max(len(str(self.data[i][j])) for i in range(self.rows))
            for j in range(self.cols)
        ]

        lines = []
        for i in range(self.rows):
            elements = [
                str(self.data[i][j]).rjust(col_widths[j])
                for j in range(self.cols)
            ]
            lines.append("[" + "  ".join(elements) + "]")

        return "\n".join(lines)

    def __repr__(self):
        return f"Matrix({self.data})"

    def transpose(self):
        """転置行列を返す"""
        result_data = [
            [self.data[j][i] for j in range(self.rows)]
            for i in range(self.cols)
        ]
        return Matrix(result_data)

    def determinant(self):
        """行列式を計算(正方行列のみ)"""
        if self.rows != self.cols:
            raise ValueError("行列式は正方行列のみ計算できます")

        if self.rows == 1:
            return self.data[0][0]
        elif self.rows == 2:
            return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data[1][0]
        elif self.rows == 3:
            # サラスの方法
            a, b, c = self.data[0]
            d, e, f = self.data[1]
            g, h, i = self.data[2]
            return (a*e*i + b*f*g + c*d*h) - (c*e*g + b*d*i + a*f*h)
        else:
            # 余因子展開(再帰的)
            det = 0
            for j in range(self.cols):
                minor = self._get_minor(0, j)
                sign = 1 if j % 2 == 0 else -1
                det += sign * self.data[0][j] * minor.determinant()
            return det

    def _get_minor(self, row, col):
        """小行列を取得"""
        result_data = [
            [
                self.data[i][j] for j in range(self.cols) if j != col
            ]
            for i in range(self.rows) if i != row
        ]
        return Matrix(result_data)

    def is_square(self):
        """正方行列かどうか"""
        return self.rows == self.cols

    def trace(self):
        """跡(対角成分の和)を計算"""
        if not self.is_square():
            raise ValueError("跡は正方行列のみ計算できます")
        return sum(self.data[i][i] for i in range(self.rows))

# 使用例
# 行列の作成
A = Matrix([[1, 2, 3], [4, 5, 6]])
B = Matrix([[7, 8, 9], [10, 11, 12]])
C = Matrix([[1, 2], [3, 4], [5, 6]])

print("行列 A:")
print(A)
print("\n行列 B:")
print(B)
print("\n行列 C:")
print(C)

# 加算・減算
print("\nA + B:")
print(A + B)

print("\nA - B:")
print(A - B)

# スカラー倍
print("\n2 * A:")
print(2 * A)

# 要素ごとの乗算
print("\nA * B (要素ごと):")
print(A * B)

# 行列積
print("\nA @ C (行列積):")
print(A @ C)

# 転置
print("\nAの転置:")
print(A.transpose())

# 正方行列の演算
D = Matrix([[1, 2], [3, 4]])
E = Matrix([[5, 6], [7, 8]])

print("\n正方行列 D:")
print(D)
print("正方行列 E:")
print(E)

print(f"\nDの行列式: {D.determinant()}")  # -2
print(f"Eの行列式: {E.determinant()}")  # -2
print(f"Dの跡: {D.trace()}")           # 5

# 行列積(@演算子)
print("\nD @ E:")
print(D @ E)

# 要素アクセス
print(f"\nD[0, 1] = {D[0, 1]}")  # 2
D[0, 1] = 10
print("D[0, 1]を10に変更:")
print(D)

問題11 解答

class SmartDictionary:
    def __init__(self, initial_data=None):
        self._data = {}
        if initial_data:
            self._data.update(initial_data)

    def __getattr__(self, name):
        """属性風のアクセス: obj.name"""
        if name in self._data:
            return self._data[name]
        elif name in self.__dict__:
            return self.__dict__[name]
        else:
            # 属性エラーを発生させる
            raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")

    def __setattr__(self, name, value):
        """属性の設定: obj.name = value"""
        if name.startswith('_'):
            # プライベート属性は通常通り設定
            super().__setattr__(name, value)
        else:
            # それ以外はデータ辞書に設定
            self._data[name] = value

    def __delattr__(self, name):
        """属性の削除: del obj.name"""
        if name in self._data:
            del self._data[name]
        elif name in self.__dict__:
            super().__delattr__(name)
        else:
            raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")

    def __getitem__(self, key):
        """辞書風のアクセス: obj[key]"""
        if key in self._data:
            return self._data[key]
        else:
            raise KeyError(f"'{key}'")

    def __setitem__(self, key, value):
        """辞書風の設定: obj[key] = value"""
        self._data[key] = value

    def __delitem__(self, key):
        """辞書風の削除: del obj[key]"""
        if key in self._data:
            del self._data[key]
        else:
            raise KeyError(f"'{key}'")

    def __contains__(self, key):
        """包含確認: key in obj"""
        return key in self._data

    def __iter__(self):
        """イテレーション: for key in obj"""
        return iter(self._data)

    def __len__(self):
        """要素数: len(obj)"""
        return len(self._data)

    def __str__(self):
        """ユーザー向けの文字列表現"""
        return f"SmartDictionary({self._data})"

    def __repr__(self):
        """開発者向けの文字列表現"""
        return f"SmartDictionary({self._data})"

    def keys(self):
        """すべてのキーを返す"""
        return self._data.keys()

    def values(self):
        """すべての値を返す"""
        return self._data.values()

    def items(self):
        """すべてのキー値ペアを返す"""
        return self._data.items()

    def get(self, key, default=None):
        """キーに対応する値を返す(デフォルト値付き)"""
        return self._data.get(key, default)

    def update(self, other):
        """他の辞書やSmartDictionaryで更新"""
        if isinstance(other, SmartDictionary):
            self._data.update(other._data)
        elif isinstance(other, dict):
            self._data.update(other)
        else:
            raise TypeError("update()は辞書またはSmartDictionaryを引数に取ります")

    def clear(self):
        """すべての要素を削除"""
        self._data.clear()

    def to_dict(self):
        """通常の辞書に変換"""
        return self._data.copy()

# 使用例
# 初期化
smart_dict = SmartDictionary({'name': 'Alice', 'age': 30, 'city': 'Tokyo'})

print("初期状態:")
print(smart_dict)

# 属性風のアクセス
print(f"\n属性風アクセス:")
print(f"smart_dict.name = {smart_dict.name}")    # Alice
print(f"smart_dict.age = {smart_dict.age}")      # 30
print(f"smart_dict.city = {smart_dict.city}")    # Tokyo

# 辞書風のアクセス
print(f"\n辞書風アクセス:")
print(f"smart_dict['name'] = {smart_dict['name']}")  # Alice
print(f"smart_dict['age'] = {smart_dict['age']}")    # 30

# 新しい属性/キーの追加
print(f"\n新しい要素の追加:")
smart_dict.country = 'Japan'           # 属性風
smart_dict['occupation'] = 'Engineer'  # 辞書風

print(f"smart_dict.country = {smart_dict.country}")            # Japan
print(f"smart_dict['occupation'] = {smart_dict['occupation']}") # Engineer

# 包含確認
print(f"\n包含確認:")
print(f"'name' in smart_dict: {'name' in smart_dict}")    # True
print(f"'height' in smart_dict: {'height' in smart_dict}") # False

# イテレーション
print(f"\nイテレーション:")
for key in smart_dict:
    print(f"  {key}: {smart_dict[key]}")

# 要素数
print(f"\n要素数: {len(smart_dict)}")

# メソッドの使用
print(f"\nキーの一覧: {list(smart_dict.keys())}")
print(f"値の一覧: {list(smart_dict.values())}")

# 更新
print(f"\n更新:")
smart_dict.update({'age': 31, 'hobby': 'Reading'})
print(f"更新後: {smart_dict}")

# 削除
print(f"\n削除:")
del smart_dict.city          # 属性風
del smart_dict['occupation'] # 辞書風
print(f"削除後: {smart_dict}")

# エラーハンドリング
try:
    print(smart_dict.nonexistent)  # 存在しない属性
except AttributeError as e:
    print(f"\nエラー: {e}")

try:
    print(smart_dict['nonexistent'])  # 存在しないキー
except KeyError as e:
    print(f"エラー: {e}")

# 通常の辞書への変換
print(f"\n通常の辞書に変換:")
normal_dict = smart_dict.to_dict()
print(f"型: {type(normal_dict)}, 内容: {normal_dict}")

問題12 解答

from datetime import datetime, timedelta
from contextlib import contextmanager

class Task:
    def __init__(self, name, description, priority="medium"):
        self.name = name
        self.description = description
        self.priority = priority
        self.created_at = datetime.now()
        self.started_at = None
        self.completed_at = None
        self.status = "pending"  # pending, in_progress, completed

    def start(self):
        """タスクを開始"""
        if self.status == "pending":
            self.started_at = datetime.now()
            self.status = "in_progress"
            return True
        return False

    def complete(self):
        """タスクを完了"""
        if self.status == "in_progress":
            self.completed_at = datetime.now()
            self.status = "completed"
            return True
        return False

    def get_duration(self):
        """タスクの所要時間を計算"""
        if self.status == "completed" and self.started_at and self.completed_at:
            return self.completed_at - self.started_at
        elif self.status == "in_progress" and self.started_at:
            return datetime.now() - self.started_at
        else:
            return timedelta(0)

    def __str__(self):
        status_icons = {
            "pending": "⏳",
            "in_progress": "🔄",
            "completed": "✅"
        }
        icon = status_icons.get(self.status, "❓")
        return f"{icon} {self.name} - {self.description} ({self.priority})"

    def __repr__(self):
        return f"Task('{self.name}', '{self.description}', '{self.priority}')"

class TaskManager:
    def __init__(self, name):
        self.name = name
        self._tasks = []
        self._current_task = None
        self._log = []

    def __enter__(self):
        """コンテキストマネージャとしての開始"""
        self._log_entry("TaskManager started")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """コンテキストマネージャとしての終了"""
        if self._current_task and self._current_task.status == "in_progress":
            self._current_task.complete()
            self._log_entry(f"Auto-completed task: {self._current_task.name}")

        status = "with error" if exc_type else "successfully"
        self._log_entry(f"TaskManager completed {status}")

        # エラーを抑制しない
        return False

    def __iter__(self):
        """完了したタスクをイテレート"""
        completed_tasks = [task for task in self._tasks if task.status == "completed"]
        return iter(completed_tasks)

    def _log_entry(self, message):
        """ログエントリを追加"""
        timestamp = datetime.now()
        log_entry = {
            'timestamp': timestamp,
            'message': message
        }
        self._log.append(log_entry)
        print(f"[{timestamp.strftime('%H:%M:%S')}] {message}")

    def add_task(self, name, description, priority="medium"):
        """新しいタスクを追加"""
        task = Task(name, description, priority)
        self._tasks.append(task)
        self._log_entry(f"Task added: {name}")
        return task

    @contextmanager
    def start_task(self, name, description, priority="medium"):
        """タスクを開始するコンテキストマネージャ"""
        task = self.add_task(name, description, priority)

        try:
            if task.start():
                self._current_task = task
                self._log_entry(f"Task started: {name}")
            yield task
        except Exception as e:
            self._log_entry(f"Task error: {name} - {e}")
            raise
        finally:
            if task.status == "in_progress":
                task.complete()
                self._log_entry(f"Task completed: {name}")
                self._current_task = None

    def get_task_stats(self):
        """タスクの統計を取得"""
        total = len(self._tasks)
        completed = len([t for t in self._tasks if t.status == "completed"])
        in_progress = len([t for t in self._tasks if t.status == "in_progress"])
        pending = len([t for t in self._tasks if t.status == "pending"])

        # 平均所要時間
        completed_tasks = [t for t in self._tasks if t.status == "completed"]
        avg_duration = sum((t.get_duration().total_seconds() for t in completed_tasks), 0.0)
        if completed_tasks:
            avg_duration /= len(completed_tasks)

        return {
            'total_tasks': total,
            'completed': completed,
            'in_progress': in_progress,
            'pending': pending,
            'completion_rate': (completed / total * 100) if total > 0 else 0,
            'avg_duration_seconds': avg_duration
        }

    def get_completed_tasks(self):
        """完了したタスクのリストを取得"""
        return [task for task in self._tasks if task.status == "completed"]

    def get_log(self, recent_count=10):
        """最近のログを取得"""
        return self._log[-recent_count:] if self._log else []

    def display_report(self):
        """レポートを表示"""
        stats = self.get_task_stats()

        print(f"\n{'='*50}")
        print(f"タスクマネージャ: {self.name}")
        print(f"{'='*50}")
        print(f"総タスク数: {stats['total_tasks']}")
        print(f"完了: {stats['completed']} | 実行中: {stats['in_progress']} | 保留: {stats['pending']}")
        print(f"完了率: {stats['completion_rate']:.1f}%")

        if stats['completed'] > 0:
            avg_minutes = stats['avg_duration_seconds'] / 60
            print(f"平均所要時間: {avg_minutes:.1f}分")

        print(f"\n完了したタスク:")
        for task in self.get_completed_tasks():
            duration = task.get_duration().total_seconds() / 60
            print(f"  ✅ {task.name} ({duration:.1f}分)")

        print(f"\n最近のログ:")
        for log_entry in self.get_log(5):
            time_str = log_entry['timestamp'].strftime('%H:%M:%S')
            print(f"  [{time_str}] {log_entry['message']}")

# 使用例
def demo_task_manager():
    # タスクマネージャの使用
    with TaskManager("プロジェクトA") as manager:
        # コンテキストマネージャを使用したタスク実行
        with manager.start_task("設計", "システム設計の作成", "high"):
            # タスクの実行中...
            print("設計作業を実行中...")
            # 何らかの処理
            import time
            time.sleep(0.1)  # シミュレーション

        with manager.start_task("実装", "コーディング", "medium"):
            print("実装作業を実行中...")
            time.sleep(0.1)

        with manager.start_task("テスト", "単体テストの実施", "medium"):
            print("テストを実行中...")
            time.sleep(0.1)

        # 手動でのタスク追加と管理
        review_task = manager.add_task("レビュー", "コードレビュー", "low")
        # 後で完了させる
        review_task.start()
        time.sleep(0.05)
        review_task.complete()

        # 保留中のタスク
        manager.add_task("ドキュメント作成", "技術文書の作成", "low")

        # レポート表示
        manager.display_report()

# 実行
demo_task_manager()

print("\n" + "="*60)
print("イテレータのデモ")

# イテレータのデモ
def demo_iterator():
    with TaskManager("プロジェクトB") as manager:
        # いくつかのタスクを実行
        tasks = [
            ("タスク1", "説明1"),
            ("タスク2", "説明2"),
            ("タスク3", "説明3")
        ]

        for name, desc in tasks:
            with manager.start_task(name, desc):
                print(f"{name} を実行中...")
                import time
                time.sleep(0.05)

        # 完了したタスクをイテレート
        print(f"\n完了したタスク ({manager.name}):")
        for task in manager:  # __iter__ が呼ばれる
            duration = task.get_duration().total_seconds()
            print(f"  - {task.name}: {duration:.2f}秒")

demo_iterator()