Menu
Coddy logo textTech

コンテナのマジックメソッド

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 35/64。

コンテナ特殊メソッドを使用すると、クラスを組み込みコンテナ(リスト、辞書など)のように動作させることができます。これらにより、カスタムオブジェクトでのインデックス参照、長さの確認、および反復処理が可能になります。

以下は、コンテナマジックメソッドを持つクラスの例です:

class CustomList:
    def __init__(self, items):
        self.items = items
    
    def __len__(self):
        return len(self.items)
    
    def __getitem__(self, index):
        return self.items[index]
    
    def __setitem__(self, index, value):
        self.items[index] = value
    
    def __iter__(self):
        return iter(self.items)
    
    def __contains__(self, item):
        return item in self.items

__len__ メソッドにより、len() が機能するようになります:

my_list = CustomList([1, 2, 3, 4])
print(len(my_list))  # 4

__getitem__ メソッドを使用すると、値を取得するためのインデックス操作が可能になります:

print(my_list[2])    # 3
print(my_list[0])    # 1

__setitem__ メソッドを使用すると、代入のためのインデックス指定が可能になります:

my_list[1] = 10
print(my_list[1])    # 10

__contains__ メソッドにより、in 演算子が機能するようになります:

print(3 in my_list)     # True
print(100 in my_list)   # False

__iter__ メソッドはイテレーション(反復処理)を可能にします:

for item in my_list:
    print(item)

出力:

4
3
1
10
True
False
1
10
3
4

重要なポイント: __len____getitem____setitem____iter____contains__ といったコンテナ特殊メソッド(マジックメソッド)を使用すると、カスタムクラスを組み込みコンテナのように動作させることができます。これにより、オブジェクトに対して直感的なインデックス参照、反復処理、およびメンバーシップテストが可能になります。

challenge icon

チャレンジ

中級

このチャレンジでは、包括的な機能と適切なPythonの慣習を備えた、トランプのデッキをシミュレートする Deck クラスを実装します。

編集が必要なのは deck.py ファイルのみです。コード内のTODOコメントに従って、以下の実装を進めてください:

  • 標準的な52枚のカードデッキの初期化("2H", "KD", "AS" のような文字列を使用)
  • Pythonの組み込み操作のサポート:
    • インデックス参照 (deck[0])
    • 長さの確認 (len(deck))
    • 反復処理 (for card in deck)
    • メンバーシップテスト ("AS" in deck)
  • カードの順序をランダムに入れ替える shuffle メソッド

チートシート

コンテナマジックメソッドを使用すると、クラスを組み込みコンテナ(リスト、辞書など)のように動作させることができます。これにより、カスタムオブジェクトでのインデックス参照、長さの確認、および反復処理が可能になります。

主要なコンテナマジックメソッド:

  • __len__() - len() 関数を有効にします
  • __getitem__() - 取得のためのインデックス参照を有効にします
  • __setitem__() - 代入のためのインデックス参照を有効にします
  • __iter__() - 反復処理を有効にします
  • __contains__() - in 演算子を有効にします
class CustomList:
    def __init__(self, items):
        self.items = items
    
    def __len__(self):
        return len(self.items)
    
    def __getitem__(self, index):
        return self.items[index]
    
    def __setitem__(self, index, value):
        self.items[index] = value
    
    def __iter__(self):
        return iter(self.items)
    
    def __contains__(self, item):
        return item in self.items

使用例:

my_list = CustomList([1, 2, 3, 4])

# 長さの確認
print(len(my_list))  # 4

# インデックス参照
print(my_list[2])    # 3
my_list[1] = 10

# メンバーシップテスト
print(3 in my_list)     # True

# 反復処理
for item in my_list:
    print(item)

自分で試してみよう

from deck import Deck

# 包括的なテストケースハンドラー
test_case = input()

def test_basic_functionality():
    deck = Deck()
    assert len(deck) == 52, f"Deck should have 52 cards, but has {len(deck)}"
    
    first_card = deck[0]
    assert isinstance(first_card, str), f"Card should be a string, but got {type(first_card)}"
    
    assert "AS" in deck, "Ace of Spades should be in the deck"
    assert "XY" not in deck, "XY is not a valid card and should not be in the deck"
    
    cards = [card for card in deck]
    assert len(cards) == 52, f"Iteration should yield 52 cards, but got {len(cards)}"
    
    original_first_five = [deck[i] for i in range(5)]
    deck.shuffle()
    shuffled_first_five = [deck[i] for i in range(5)]
    assert original_first_five != shuffled_first_five or len(deck) <= 5, "Shuffle should change card order"
    
    print("Basic functionality tests passed!")

def test_edge_cases():
    deck = Deck()
    
    # 最初と最後のカードへのアクセスをテスト
    first_card = deck[0]
    last_card = deck[51]
    assert isinstance(first_card, str) and isinstance(last_card, str), "First and last cards should be strings"
    
    # 負のインデックスをテスト
    assert deck[-1] == deck[51], "Negative indexing should work correctly"
    
    # 範囲外アクセスのテスト
    try:
        invalid_card = deck[52]
        print("Test failed: Should raise IndexError for out of bounds access")
    except IndexError:
        print("Edge case test passed: IndexError raised for out of bounds access")
    
    print("Edge case tests passed!")

def test_card_uniqueness():
    deck = Deck()
    cards = [card for card in deck]
    unique_cards = set(cards)
    
    assert len(unique_cards) == 52, f"All cards should be unique, but found {len(unique_cards)} unique cards"
    
    # 特定のカードが存在することを確認
    expected_cards = ["2H", "10S", "KD", "AC"]
    for card in expected_cards:
        assert card in deck, f"Expected card {card} not found in deck"
    
    print("Card uniqueness tests passed!")

def test_shuffle_behavior():
    deck = Deck()
    original_order = [card for card in deck]
    
    # 1回目のシャッフル
    deck.shuffle()
    first_shuffle = [card for card in deck]
    assert len(first_shuffle) == 52, "Shuffle should preserve all 52 cards"
    assert set(first_shuffle) == set(original_order), "Shuffle should not add or remove cards"
    
    # おそらく順序が変わっている(変わらない確率は極めて低い)
    different_order = (original_order != first_shuffle)
    
    # 念のため2回目のシャッフル
    deck.shuffle()
    second_shuffle = [card for card in deck]
    different_order_2 = (first_shuffle != second_shuffle)
    
    assert different_order or different_order_2, "Multiple shuffles should change the order"
    
    print("Shuffle behavior tests passed!")

def test_contains_behavior():
    deck = Deck()
    
    # すべての有効なカードがデッキに含まれているかテスト
    suits = ['H', 'D', 'C', 'S']
    ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
    
    for suit in suits:
        for rank in ranks:
            card = rank + suit
            assert card in deck, f"Valid card {card} should be in the deck"
    
    # 無効なカードがデッキに含まれていないかテスト
    invalid_cards = ["1H", "11S", "XD", "AX", "JX", ""]
    for card in invalid_cards:
        assert card not in deck, f"Invalid card {card} should not be in the deck"
    
    print("Contains behavior tests passed!")

def test_iteration_behavior():
    deck = Deck()
    
    # イテレーションのテスト
    card_count = 0
    for card in deck:
        card_count += 1
        assert isinstance(card, str), f"Each card should be a string, but got {type(card)}"
    
    assert card_count == 52, f"Iteration should yield 52 cards, but got {card_count}"
    
    # 複数回のイテレーションをテスト
    first_iteration = [card for card in deck]
    second_iteration = [card for card in deck]
    assert first_iteration == second_iteration, "Multiple iterations should yield the same order"
    
    print("Iteration behavior tests passed!")

# 入力に基づいて適切なテストを実行
if test_case == "basic_functionality":
    test_basic_functionality()
elif test_case == "edge_cases":
    test_edge_cases()
elif test_case == "card_uniqueness":
    test_card_uniqueness()
elif test_case == "shuffle_behavior":
    test_shuffle_behavior()
elif test_case == "contains_behavior":
    test_contains_behavior()
elif test_case == "iteration_behavior":
    test_iteration_behavior()
else:
    # デフォルトのテスト - 元のテストスイートを実行
    def test_deck():
        try:
            # 初期化と長さのテスト
            deck = Deck()
            assert len(deck) == 52, f"Deck should have 52 cards, but has {len(deck)}"
            
            # getitemのテスト
            first_card = deck[0]
            assert isinstance(first_card, str), f"Card should be a string, but got {type(first_card)}"
            
            # containsのテスト
            assert "AS" in deck, "Ace of Spades should be in the deck"
            assert "XY" not in deck, "XY is not a valid card and should not be in the deck"
            
            # イテレーションのテスト
            cards = [card for card in deck]
            assert len(cards) == 52, f"Iteration should yield 52 cards, but got {len(cards)}"
            assert len(set(cards)) == 52, "All cards in the deck should be unique"
            
            # シャッフルのテスト(順序が変わるかどうかの基本的なチェック)
            original_first_five = [deck[i] for i in range(5)]
            deck.shuffle()
            shuffled_first_five = [deck[i] for i in range(5)]
            assert original_first_five != shuffled_first_five or len(deck) <= 5, "Shuffle should change card order"
            
            # シャッフルによってカードが失われないことを確認
            assert len(deck) == 52, f"Deck should still have 52 cards after shuffle, but has {len(deck)}"
            
            print("All tests passed!")
        except AssertionError as e:
            print(f"Test failed: {e}")

    test_deck()
    print("Tests completed")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン