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 Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return Number(self.value + other.value)

    def __sub__(self, other):
        return Number(self.value - other.value)

    def __mul__(self, other):
        return Number(self.value * other.value)

    def __str__(self):
        return str(self.value)

# 使用例
a = Number(10)
b = Number(5)

print(a + b)  # 15
print(a - b)  # 5
print(a * b)  # 50

Fractionクラスでは、+-*/**などの演算子をそれぞれ__add____sub____mul__で定義し、分数の加減乗除や累乗を直感的に扱えるようにしています。これにより、通常の数値演算と同様の表記で分数演算を行えるようになっています。

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

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

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

    def __lt__(self, other):
        return self.score < other.score

    def __eq__(self, other):
        return self.score == other.score

# 使用例
a = Student(80)
b = Student(90)

print(a < b)   # True
print(a == b)  # False

__eq____lt__などの特殊メソッドを定義することで、==<などの演算子が数値のように機能します。データを直感的に比較・整理できる設計になっています。

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

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

シーケンスプロトコルとは、自作のオブジェクトをリストやタプルのように振る舞わせるためのインターフェース(仕組み)のことです。

class Playlist:
    def __init__(self):
        self.songs = []

    def __len__(self):
        return len(self.songs)

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

# 使用例
playlist = Playlist()
playlist.songs.append("Song A")
playlist.songs.append("Song B")

print(len(playlist))  # 2
print(playlist[0])    # Song A

このコードでは、__len____getitem__ などの特殊メソッドを定義すると、オブジェクトを「複数の要素を順番に持つデータ」として扱えるようになります。これをシーケンスプロトコルと呼びます。

たとえば、__len__ を定義すると len(playlist) が使えるようになり、__getitem__ を定義すると playlist[0] のようなインデックスアクセスが可能になります。

コンテキストマネージャ

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クラスを作成してください。コンストラクタでは幅を受け取り、インスタンス変数に保存してください。今回は幅として「5」を使用します。

次に__str__ メソッドを定義し、print(rect)str(rect) を実行したときに「幅:5」と表示されるようにしてください。さらに__repr__ メソッドを定義し、repr(rect) を実行したときに「Rectangle(5)」と表示されるようにしてください。

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

実行結果:

幅:5
Rectangle(5)
幅:5

問題2

BankAccountクラスを作成してください。コンストラクタでは残高を受け取り、インスタンス変数に保存してください。また、+ 演算子で2つの口座の残高を合計できるように、__add__ メソッドを定義してください。合計結果は新しいBankAccountオブジェクトとして返してください。

さらに、print() で表示したときに「3000円」のように表示されるよう、__str__ メソッドを定義してください。最後に、残高1000円の口座と2000円の口座を作成し、a + b の結果を表示してください。

3000円

問題3

Bookクラスを作成してください。コンストラクタではページ数を受け取り、インスタンス変数に保存してください。また、章データとして「第1章」と「第2章」を持つリストを作成してください。len(book) を実行したときにページ数「200」が返るよう、__len__ メソッドを定義してください。

さらに、book[0] を実行したときに「第1章」が取得できるよう、__getitem__ メソッドを定義してください。最後に、ページ数200のBookオブジェクトを作成し、len(book)book[0] の結果を表示してください。

200
第1章

中級問題

問題4

Fractionクラスを作成してください。コンストラクタでは数値を受け取り、インスタンス変数に保存してください。また、+ 演算子で加算できるように __add__ メソッド、- 演算子で減算できるように __sub__ メソッド、* 演算子で乗算できるように __mul__ メソッドを定義してください。

演算結果は新しいFractionオブジェクトとして返してください。さらに、print() で数値だけが表示されるように、__str__ メソッドを定義してください。最後に、値が6のオブジェクトと値が2のオブジェクトを作成し、加算、減算、乗算の結果を表示してください。

実行結果:

a + b = 8
a - b = 4
a * b = 12

問題5

Playlistクラスを作成してください。コンストラクタでは空のリスト songs を作成してください。また、add_song() メソッドを作成し、曲をプレイリストへ追加できるようにしてください。さらに、len(playlist) を実行したときに曲数を返すよう、__len__ メソッドを定義してください。

加えて、playlist[0] のようにインデックスで曲名を取得できるよう、__getitem__ メソッドを定義してください。最後に、「Song A」「Song B」「Song C」を追加し、曲数と1曲目、2曲目を表示してください。

実行結果:

3
Song A
Song B

問題6

Polynomialクラスを作成してください。コンストラクタでは、一次式 ax + b の係数 ab を受け取り、インスタンス変数に保存してください。また、p(2) のようにオブジェクトを関数として呼び出したときに、多項式の値を計算できるよう、__call__ メソッドを定義してください。

さらに、+ 演算子で2つの多項式を加算できるように、__add__ メソッドを定義してください。加算結果は新しいPolynomialオブジェクトとして返してください。

加えて、print() で「2x + 3」のように表示されるよう、__str__ メソッドを定義してください。最後に、2x + 31x + 4 の2つのオブジェクトを作成してください。その後、p1(2) の結果と、p1 + p2 の結果を表示してください。

実行結果:

p1 = 2x + 3
p1(2) = 7
p3 = 3x + 7

問題7

Vectorクラスを作成してください。コンストラクタでは、x座標とy座標を受け取り、インスタンス変数に保存してください。また、+ 演算子でベクトル同士を加算できるように __add__ メソッドを定義してください。

さらに、- 演算子でベクトル同士を減算できるように __sub__ メソッドを定義してください。加えて、* 演算子でベクトルを整数倍できるように __mul__ メソッドを定義してください。また、== 演算子で2つのベクトルを比較できるように __eq__ メソッドを定義してください。

さらに、v[0] で x座標、v[1] で y座標を取得できるように __getitem__ メソッドを定義してください。加えて、len(v) を実行したときに 2 を返すように __len__ メソッドを定義してください。また、abs(v) を実行したときに x と y の合計を返すように __abs__ メソッドを定義してください。

さらに、print()(2, 3) のように表示されるよう、__str__ メソッドを定義してください。最後に、(2, 3)(1, 4) の2つのベクトルを作成し、加算、減算、2倍、比較結果、インデックスアクセス、長さ、abs() の結果を表示してください。

実行結果:

v1 + v2 = (3, 7)
v1 - v2 = (1, -1)
v1 * 2 = (4, 6)
v1 == v2: False
v1[0] = 2
v1[1] = 3
len(v1) = 2
abs(v1) = 5

問題8

Databaseクラスを作成してください。コンストラクタではデータベース名を受け取り、インスタンス変数に保存してください。with 文で利用できるように、__enter__ メソッドと __exit__ メソッドを定義してください。__enter__ では SQLite データベースへ接続し、カーソルを作成してください。接続時には「接続しました」と表示してください。

__exit__ では、データベースへコミットした後に接続を閉じ、「切断しました」と表示してください。さらに、create_table() メソッドを作成し、users テーブルを作成してください。テーブルには idname の2つの列を持たせてください。

加えて、insert_user() メソッドを作成し、ユーザー情報を登録できるようにしてください。また、show_users() メソッドを作成し、登録されているすべてのユーザー情報を取得できるようにしてください。

最後に、メモリ上のデータベース ":memory:" を使用して Database オブジェクトを作成してください。その後、IDが1で名前が「田中」のユーザーと、IDが2で名前が「佐藤」のユーザーを登録してください。最後に、登録されているすべてのユーザー情報を表示してください。

実行結果:

接続しました
(1, '田中')
(2, '佐藤')
切断しました

問題9

Temperatureクラスを作成してください。コンストラクタでは摂氏温度を受け取り、celsius に保存してください。また、celsius をプロパティとして定義し、値を取得できるようにしてください。

さらに、@celsius.setter を使用し、-273.15 未満の値が設定された場合は ValueError を発生させるようにしてください。加えて、fahrenheit プロパティを作成し、摂氏温度から華氏温度を計算して返してください。また、@fahrenheit.setter を使用し、華氏温度を代入すると摂氏温度へ変換して保存されるようにしてください。

さらに、+ 演算子で温度同士、または温度と数値を加算できるように __add__ メソッドを定義してください。加えて、- 演算子で温度同士、または温度と数値を減算できるように __sub__ メソッドを定義してください。また、* 演算子で温度を数値倍できるように __mul__ メソッドを定義してください。

さらに、/ 演算子で温度を数値で割れるように __truediv__ メソッドを定義してください。加えて、== 演算子で温度比較を行えるように __eq__ メソッドを定義してください。また、< 演算子で温度比較を行えるように __lt__ メソッドを定義してください。

さらに、float() を実行したときに華氏温度を返すように __float__ メソッドを定義してください。加えて、int() を実行したときに摂氏温度の整数値を返すように __int__ メソッドを定義してください。また、print() で「25.0°C」のように表示されるよう、__str__ メソッドを定義してください。

さらに、華氏温度からTemperatureオブジェクトを作成できるように、from_fahrenheit() クラスメソッドを定義してください。加えて、ケルビン温度を返す to_kelvin() メソッドを定義してください。最後に、25度と10度のTemperatureオブジェクトを作成してください。

その後、華氏温度、float変換、int変換、加算、減算、乗算、除算、比較結果を表示してください。さらに、華氏68度からTemperatureオブジェクトを作成し、結果を表示してください。最後に、t1.fahrenheit = 32 を実行し、変更後の温度を表示してください。

実行結果:

温度t1.fahrenheit = 77.0
float(t1) = 77.0
int(t1) = 25
温度t1 + 温度t2 = 35.0°C
温度t1 - 温度t2 = 15.0°C
温度t1 * 2 = 50.0°C
温度t1 / 5 = 5.0°C
温度t1 == 25 = True
温度t1 < 30 = True
温度t3 = 20.0°C
温度t1.to_kelvin() = 298.15
温度t1 = 0.0°C

上級問題

問題10

Matrixクラスを作成してください。コンストラクタでは、2×2行列を表す2次元リストを受け取り、インスタンス変数 data に保存してください。

また、+ 演算子で2つの行列を加算できるように __add__ メソッドを定義してください。さらに、* 演算子で行列を整数倍できるように __mul__ メソッドを定義してください。加えて、@ 演算子で行列積を計算できるように __matmul__ メソッドを定義してください。

また、A[0, 1] のように要素へアクセスできるように __getitem__ メソッドを定義してください。さらに、A[0, 1] = 10 のように要素を変更できるように __setitem__ メソッドを定義してください。加えて、転置行列を返す transpose() メソッドを定義してください。

また、対角成分の和を返す trace() メソッドを定義してください。さらに、print() で行列を表示できるように __str__ メソッドを定義してください。最後に、[[1, 2], [3, 4]] の行列Aと、[[5, 6], [7, 8]] の行列Bを作成してください。

その後、行列の加算、スカラー倍、行列積を表示してください。さらに、A[0, 1] の値を表示してください。その後、A[0, 1] = 10 を実行し、変更後の行列を表示してください。最後に、転置行列と跡の結果を表示してください。

実行結果:

A + B = [[6, 8], [10, 12]]
A * 2 = [[2, 4], [6, 8]]
A @ B = [[19, 22], [43, 50]]
A[0, 1] = 2
A = [[1, 10], [3, 4]]
A.transpose() = [[1, 3], [10, 4]]
A.trace() = 5

問題11

SmartDictionaryクラスを作成してください。このクラスは通常の辞書のようにデータを管理できますが、辞書形式だけでなく属性形式でも値へアクセスできる特別な辞書として動作します。

コンストラクタでは初期データとして {'name': 'Alice', 'age': 30, 'city': 'Tokyo'} を受け取り、内部データとして保持してください。属性アクセスでは smart_dict.name のように書くことで値を取得できるようにしてください。例えば smart_dict.name"Alice" を返し、smart_dict.age30 を返します。

辞書アクセスでは smart_dict['city'] のように書くことで値を取得できるようにしてください。例えば smart_dict['city']"Tokyo" を返します。属性形式で smart_dict.country = 'Japan' を実行した場合、新しいデータとして保存されるようにしてください。

また、辞書形式で smart_dict['job'] = 'Engineer' を実行した場合も保存されるようにしてください。'name' in smart_dict のような in 演算子を使った包含確認を実装してください。'name' は True、'height' は False になるようにしてください。

for文でオブジェクトを反復処理できるようにし、すべてのキーを順番に取り出せるようにしてください。len(smart_dict) で要素数を取得できるようにしてください。初期データに加えて countryjob を追加した後の要素数は 5 になります。

keys()values()items() メソッドを実装し、通常の辞書のようにキー一覧、値一覧、キーと値の組を取得できるようにしてください。update() メソッドを実装し、{'age': 31, 'hobby': 'Reading'} を渡すと既存データを更新できるようにしてください。更新後、age31 になり、新たに hobby が追加されます。

del smart_dict.city のような属性形式の削除、および del smart_dict['job'] のような辞書形式の削除を実装してください。存在しない属性 smart_dict.height にアクセスした場合は AttributeError を発生させてください。また、存在しないキー smart_dict['height'] にアクセスした場合は KeyError を発生させてください。最後に、内部データを通常の辞書として返す to_dict() メソッドを実装してください。

実行結果:

初期状態:
SmartDictionary({'name': 'Alice', 'age': 30, 'city': 'Tokyo'})

属性風アクセス:
smart_dict.name = Alice
smart_dict.age = 30
smart_dict.city = Tokyo

辞書風アクセス:
smart_dict['name'] = Alice
smart_dict['age'] = 30

新しい要素の追加:
smart_dict.country = Japan
smart_dict['occupation'] = Engineer

包含確認:
'name' in smart_dict: True
'height' in smart_dict: False

イテレーション:
  name: Alice
  age: 30
  city: Tokyo
  country: Japan
  occupation: Engineer

要素数: 5

キーの一覧: ['name', 'age', 'city', 'country', 'occupation']
値の一覧: ['Alice', 30, 'Tokyo', 'Japan', 'Engineer']

更新:
更新後: SmartDictionary({'name': 'Alice', 'age': 31, 'city': 'Tokyo', 'country': 'Japan', 'occupation': 'Engineer', 'hobby': 'Reading'})

削除:
削除後: SmartDictionary({'name': 'Alice', 'age': 31, 'country': 'Japan', 'hobby': 'Reading'})

エラー: 'SmartDictionary' object has no attribute 'nonexistent'
エラー: "'nonexistent'"

通常の辞書に変換:
型: , 内容: {'name': 'Alice', 'age': 31, 'country': 'Japan', 'hobby': 'Reading'}

問題12

TaskクラスとTaskManagerクラスを作成してください。Taskクラスでは、タスク名、説明、優先度を保持し、タスクの開始時刻、完了時刻、状態を管理できるようにしてください。状態は "pending""in_progress""completed" の3種類を使用してください。

Taskクラスには start() メソッドを実装し、タスク開始時に状態を "in_progress" に変更してください。また、開始時刻を現在時刻として記録してください。

complete() メソッドでは、状態を "completed" に変更し、完了時刻を記録してください。get_duration() メソッドでは、タスク開始から完了までの経過時間を返してください。完了済みタスクでは「完了時刻 − 開始時刻」を返し、実行中タスクでは「現在時刻 − 開始時刻」を返してください。

__str__() メソッドを実装し、"設計 - システム設計の作成 (high)" のような形式で表示してください。TaskManagerクラスでは、タスク一覧、現在実行中のタスク、ログ情報を管理してください。

__enter__()__exit__() を実装し、with文でTaskManagerを使用できるようにしてください。開始時には "TaskManager started" をログへ追加してください。終了時には "TaskManager completed successfully" をログへ追加してください。add_task() メソッドでは新しいTaskオブジェクトを生成し、内部リストへ追加してください。

start_task() メソッドは @contextmanager を使用して実装してください。with文で利用できるようにし、開始時にはタスクを "in_progress" に変更してください。処理終了後には自動的に完了状態へ変更してください。

_log_entry() メソッドでは、現在時刻とメッセージを辞書形式でログリストへ保存してください。ログデータは {'timestamp': 時刻, 'message': メッセージ} の形式にしてください。__iter__() を実装し、for文で完了済みタスクのみ取り出せるようにしてください。get_task_stats() メソッドでは以下の情報を辞書形式で返してください。

総タスク数
完了タスク数
実行中タスク数
保留タスク数
完了率
平均所要時間(秒)

タスクの実行例として、以下の5つのタスクを使用してください。

「設計」 優先度 "high"
「実装」 優先度 "medium"
「テスト」 優先度 "medium"
「レビュー」 優先度 "low"
「ドキュメント作成」 優先度 "low"

「設計」「実装」「テスト」は with manager.start_task(...) を使用して実行してください。「レビュー」は add_task() で追加後、手動で start() と complete() を呼び出してください。

「ドキュメント作成」は pending 状態のまま残してください。各タスク実行時には time.sleep(0.1) を使用し、レビューのみ time.sleep(0.05) を使用してください。display_report() メソッドでは以下を表示してください。

タスクマネージャ名
総タスク数
完了数
実行中数
保留数
完了率
平均所要時間
完了済みタスク一覧
最新5件のログ

最後に、for文を使用して完了済みタスクをすべて表示してください。表示形式は "タスク名: 秒数" としてください。

実行結果:

[14:54:51] TaskManager started
[14:54:51] Task added: 設計
[14:54:51] Task started: 設計
設計作業を実行中...
[14:54:51] Task completed: 設計
[14:54:51] Task added: 実装
[14:54:51] Task started: 実装
実装作業を実行中...
[14:54:51] Task completed: 実装
[14:54:51] Task added: テスト
[14:54:51] Task started: テスト
テストを実行中...
[14:54:51] Task completed: テスト
[14:54:51] Task added: レビュー
[14:54:51] Task added: ドキュメント作成

==================================================
タスクマネージャ: プロジェクトA
==================================================
総タスク数: 5
完了: 4 | 実行中: 0 | 保留: 1
完了率: 80.0%
平均所要時間: 0.0分

完了したタスク:
   設計 (0.0分)
   実装 (0.0分)
   テスト (0.0分)
   レビュー (0.0分)

最近のログ:
  [14:54:51] Task added: テスト
  [14:54:51] Task started: テスト
  [14:54:51] Task completed: テスト
  [14:54:51] Task added: レビュー
  [14:54:51] Task added: ドキュメント作成
[14:54:51] TaskManager completed successfully

============================================================
イテレータのデモ
[14:54:51] TaskManager started
[14:54:51] Task added: タスク1
[14:54:51] Task started: タスク1
タスク1 を実行中...
[14:54:51] Task completed: タスク1
[14:54:51] Task added: タスク2
[14:54:51] Task started: タスク2
タスク2 を実行中...
[14:54:52] Task completed: タスク2
[14:54:52] Task added: タスク3
[14:54:52] Task started: タスク3
タスク3 を実行中...
[14:54:52] Task completed: タスク3

完了したタスク (プロジェクトB):
  - タスク1: 0.05秒
  - タスク2: 0.05秒
  - タスク3: 0.05秒
[14:54:52] TaskManager completed successfully

演習問題解答

初級問題

問題1 解答

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

    def __str__(self):
        return f"幅:{self.width}"

    def __repr__(self):
        return f"Rectangle({self.width})"

# 使用例
rect = Rectangle(5)

print(str(rect))   # 幅:5
print(repr(rect))  # Rectangle(5)
print(rect)        # 幅:5

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

問題2 解答

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

    def __add__(self, other):
        return BankAccount(self.balance + other.balance)

    def __str__(self):
        return f"{self.balance}円"

# 使用例
a = BankAccount(1000)
b = BankAccount(2000)

c = a + b

print(c)  # 3000円

問題3 解答

class Book:
    def __init__(self, pages):
        self.pages = pages
        self.chapters = ["第1章", "第2章"]

    def __len__(self):
        return self.pages

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

# 使用例
book = Book(200)

print(len(book))  # 200
print(book[0])    # 第1章

中級問題

問題4 解答

class Fraction:
    def __init__(self, num):
        self.num = num

    def __add__(self, other):
        return Fraction(self.num + other.num)

    def __sub__(self, other):
        return Fraction(self.num - other.num)

    def __mul__(self, other):
        return Fraction(self.num * other.num)

    def __str__(self):
        return str(self.num)

# 使用例
a = Fraction(6)
b = Fraction(2)

print(f"a + b = {a + b}")  # a + b = 8
print(f"a - b = {a - b}")  # a - b = 4
print(f"a * b = {a * b}")  # a * b = 12

問題5 解答

class Playlist:
    def __init__(self):
        self.songs = []

    def __len__(self):
        return len(self.songs)

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

    def add_song(self, song):
        self.songs.append(song)

# 使用例
playlist = Playlist()

playlist.add_song("Song A")
playlist.add_song("Song B")
playlist.add_song("Song C")

print(len(playlist))  # 3
print(playlist[0])    # Song A
print(playlist[1])    # Song B

問題6 解答

class Polynomial:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __call__(self, x):
        return self.a * x + self.b

    def __add__(self, other):
        return Polynomial(self.a + other.a, self.b + other.b)

    def __str__(self):
        return f"{self.a}x + {self.b}"

# 使用例
p1 = Polynomial(2, 3)
p2 = Polynomial(1, 4)

print(f"p1 = {p1}")          # 2x + 3
print(f"p1(2) = {p1(2)}")       # 7

p3 = p1 + p2
print(f"p3 = {p3}")          # 3x + 7

問題7 解答

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

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, n):
        return Vector(self.x * n, self.y * n)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __getitem__(self, index):
        if index == 0:
            return self.x
        return self.y

    def __len__(self):
        return 2

    def __abs__(self):
        return self.x + self.y

    def __str__(self):
        return f"({self.x}, {self.y})"

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

print(f"v1 + v2 = {v1 + v2}")   # v1 + v2 = (3, 7)
print(f"v1 - v2 = {v1 - v2}")   # v1 - v2 = (1, -1)
print(f"v1 * 2 = {v1 * 2}")    # v1 * 2 = (4, 6)

print(f"v1 == v2: {v1 == v2}")  # v1 == v2: False

print(f"v1[0] = {v1[0]}")     # v1[0] = 2
print(f"v1[1] = {v1[1]}")     # v1[1] = 3

print(f"len(v1) = {len(v1)}")   # len(v1) = 2
print(f"abs(v1) = {abs(v1)}")   # abs(v1) = 5

問題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 value < -273.15:
            raise ValueError("温度が低すぎます")
        self._celsius = value

    @property
    def fahrenheit(self):
        return (self._celsius * 9 / 5) + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self._celsius = (value - 32) * 5 / 9

    def __add__(self, other):
        if isinstance(other, Temperature):
            return Temperature(self.celsius + other.celsius)
        return Temperature(self.celsius + other)

    def __sub__(self, other):
        if isinstance(other, Temperature):
            return Temperature(self.celsius - other.celsius)
        return Temperature(self.celsius - other)

    def __mul__(self, other):
        return Temperature(self.celsius * other)

    def __truediv__(self, other):
        return Temperature(self.celsius / other)

    def __eq__(self, other):
        if isinstance(other, Temperature):
            return self.celsius == other.celsius
        return self.celsius == other

    def __lt__(self, other):
        if isinstance(other, Temperature):
            return self.celsius < other.celsius
        return self.celsius < other

    def __float__(self):
        return self.fahrenheit

    def __int__(self):
        return int(self.celsius)

    def __str__(self):
        return f"{self.celsius:.1f}°C"

    @classmethod
    def from_fahrenheit(cls, value):
        celsius = (value - 32) * 5 / 9
        return cls(celsius)

    def to_kelvin(self):
        return self.celsius + 273.15

# 使用例
t1 = Temperature(25)
t2 = Temperature(10)

print(f"温度t1 = {t1}")                 # 25.0°C
print(f"温度t1.fahrenheit = {t1.fahrenheit}")      # 77.0

print(f"float(t1) = {float(t1)}")          # 77.0
print(f"int(t1) = {int(t1)}")            # 25

print(f"温度t1 + 温度t2 = {t1 + t2}")            # 35.0°C
print(f"温度t1 - 温度t2 = {t1 - t2}")            # 15.0°C

print(f"温度t1 * 2 = {t1 * 2}")             # 50.0°C
print(f"温度t1 / 5 = {t1 / 5}")             # 5.0°C

print(f"温度t1 == 25 = {t1 == 25}")           # True
print(f"温度t1 < 30 = {t1 < 30}")            # True

t3 = Temperature.from_fahrenheit(68)
print(f"温度t3 = {t3}")                 # 20.0°C

print(f"温度t1.to_kelvin() = {t1.to_kelvin()}")     # 298.15

t1.fahrenheit = 32
print(f"温度t1 = {t1}")                 # 0.0°C

上級問題

問題10 解答

class Matrix:
    def __init__(self, data):
        self.data = data

    def __add__(self, other):
        result = [
            [
                self.data[i][j] + other.data[i][j]
                for j in range(2)
            ]
            for i in range(2)
        ]
        return Matrix(result)

    def __mul__(self, number):
        result = [
            [
                self.data[i][j] * number
                for j in range(2)
            ]
            for i in range(2)
        ]
        return Matrix(result)

    def __matmul__(self, other):
        result = [
            [
                self.data[i][0] * other.data[0][j] +
                self.data[i][1] * other.data[1][j]
                for j in range(2)
            ]
            for i in range(2)
        ]
        return Matrix(result)

    def __getitem__(self, index):
        i, j = index
        return self.data[i][j]

    def __setitem__(self, index, value):
        i, j = index
        self.data[i][j] = value

    def transpose(self):
        result = [
            [self.data[0][0], self.data[1][0]],
            [self.data[0][1], self.data[1][1]]
        ]
        return Matrix(result)

    def trace(self):
        return self.data[0][0] + self.data[1][1]

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

# 使用例
A = Matrix([[1, 2], [3, 4]])
B = Matrix([[5, 6], [7, 8]])

print(f"A + B = {A + B}")      # [[6, 8], [10, 12]]
print(f"A * 2 = {A * 2}")      # [[2, 4], [6, 8]]
print(f"A @ B = {A @ B}")      # [[19, 22], [43, 50]]

print(f"A[0, 1] = {A[0, 1]}")    # 2

A[0, 1] = 10
print(f"A = {A}")          # [[1, 10], [3, 4]]

print(f"A.transpose() = {A.transpose()}")  # [[1, 3], [10, 4]]

print(f"A.trace() = {A.trace()}")  # 5

問題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()