Menu
CoddyTech

Métodos mágicos de contenedores

Parte de la sección Programación orientada a objetos del Journey de Python de Coddy. Lección 35 de 64.

Los métodos mágicos de contenedor permiten que tus clases se comporten como contenedores integrados (listas, diccionarios, etc.). Permiten la indexación, la comprobación de la longitud y la iteración en tus objetos personalizados.

Aquí tienes un ejemplo de una clase con métodos mágicos de contenedor:

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

El método __len__ hace que funcione len():

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

El método __getitem__ permite la indexación para recuperar elementos:

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

El método __setitem__ permite usar índices para asignar valores:

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

El método __contains__ hace que el operador in funcione:

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

El método __iter__ permite la iteración:

for item in my_list:
    print(item)

Salida:

4
3
1
10
True
False
1
10
3
4

Punto clave: los métodos mágicos de contenedor como __len__, __getitem__, __setitem__, __iter__ y __contains__ hacen que tus clases personalizadas se comporten como contenedores integrados. Esto proporciona indexación intuitiva, iteración y comprobación de pertenencia para tus objetos.

challenge icon

Desafío

Intermedio

En este desafío, implementarás una clase Deck que simula una baraja de cartas con funcionalidades completas y siguiendo las convenciones adecuadas de Python.

Solo tienes que editar el archivo deck.py. Sigue los comentarios TODO del código, que te guían para implementar lo siguiente:

  • La inicialización de una baraja estándar de 52 cartas (usando cadenas como "2H", "KD", "AS")
  • Compatibilidad con las operaciones integradas de Python:
    • Indexación (deck[0])
    • Comprobación de la longitud (len(deck))
    • Iteración (for card in deck)
    • Comprobación de pertenencia ("AS" in deck)
  • Un método shuffle para aleatorizar el orden de las cartas

Pruébalo tú mismo

from deck import Deck

# Controlador integral de casos de prueba
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()
    
    # Probar el acceso a la primera y la última carta
    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"
    
    # Probar la indexación negativa
    assert deck[-1] == deck[51], "Negative indexing should work correctly"
    
    # Probar el acceso fuera de los límites
    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"
    
    # Verificar que existan determinadas cartas
    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]
    
    # Primera mezcla
    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"
    
    # Lo más probable es que el orden haya cambiado (aunque existe una probabilidad mínima de que no haya sido así)
    different_order = (original_order != first_shuffle)
    
    # Segunda mezcla para mayor seguridad
    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()
    
    # Comprobar que todas las cartas válidas estén en la baraja
    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"
    
    # Comprobar que las cartas no válidas no estén en la baraja
    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()
    
    # Probar la iteración
    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}"
    
    # Probar varias iteraciones
    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!")

# Ejecutar la prueba adecuada según la entrada
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:
    # Prueba predeterminada: ejecutar el conjunto de pruebas original
    def test_deck():
        try:
            # Probar la inicialización y la longitud
            deck = Deck()
            assert len(deck) == 52, f"Deck should have 52 cards, but has {len(deck)}"
            
            # Probar getitem
            first_card = deck[0]
            assert isinstance(first_card, str), f"Card should be a string, but got {type(first_card)}"
            
            # Probar 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"
            
            # Probar la iteración
            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"
            
            # Probar shuffle (comprobación básica de que cambia el orden)
            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"
            
            # Comprobar que shuffle no pierde cartas
            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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Python online