Métodos mágicos de contenedores
Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 35 de 64.
Los métodos mágicos de contenedor permiten que sus clases se comporten como contenedores integrados (listas, diccionarios, etc.). Permiten la indexación, la comprobación de longitud y la iteración en sus objetos personalizados.
Aquí hay 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.itemsEl método __len__ hace que len() funcione:
my_list = CustomList([1, 2, 3, 4])
print(len(my_list)) # 4El método __getitem__ permite la indexación para la recuperación:
print(my_list[2]) # 3
print(my_list[0]) # 1El método __setitem__ permite la indexación para la asignación:
my_list[1] = 10
print(my_list[1]) # 10El método __contains__ hace que el operador in funcione:
print(3 in my_list) # True
print(100 in my_list) # FalseEl método __iter__ permite la iteración:
for item in my_list:
print(item)Salida:
4
3
1
10
True
False
1
10
3
4Punto 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, iteración y pruebas de pertenencia intuitivas para tus objetos.
Desafío
IntermedioEn este desafío, implementarás una clase Deck que simula una baraja de cartas con una funcionalidad completa y siguiendo las convenciones adecuadas de Python.
Solo necesitas editar el archivo deck.py. Sigue los comentarios TODO en el código que te guiarán para implementar:
- Una inicialización estándar de una baraja de 52 cartas (usando cadenas como "2H", "KD", "AS")
- Soporte para las operaciones integradas de Python:
- Indexación (
deck[0]) - Comprobación de longitud (
len(deck)) - Iteración (
for card in deck) - Prueba de pertenencia (
"AS" in deck)
- Indexación (
- Un método
shufflepara aleatorizar el orden de las cartas
Hoja de referencia
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 longitud y la iteración en tus objetos personalizados.
Métodos mágicos de contenedor clave:
__len__()- habilita la funciónlen()__getitem__()- habilita la indexación para la recuperación__setitem__()- habilita la indexación para la asignación__iter__()- habilita la iteración__contains__()- habilita el operadorin
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.itemsEjemplos de uso:
my_list = CustomList([1, 2, 3, 4])
# Comprobación de longitud
print(len(my_list)) # 4
# Indexación
print(my_list[2]) # 3
my_list[1] = 10
# Prueba de pertenencia
print(3 in my_list) # True
# Iteración
for item in my_list:
print(item)Pruébalo tú mismo
from deck import Deck
# Manejador de casos de prueba exhaustivo
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 ú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 indexación negativa
assert deck[-1] == deck[51], "Negative indexing should work correctly"
# Probar 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 existen cartas específicas
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]
# Primer barajado
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 hay una probabilidad minúscula de que no sea así)
different_order = (original_order != first_shuffle)
# Segundo barajado para estar extra seguros
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()
# Probar que todas las cartas válidas están en el mazo
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"
# Probar que las cartas inválidas no están en el mazo
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 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 múltiples 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 apropiada basada en 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 por defecto - ejecutar la suite de pruebas original
def test_deck():
try:
# Probar inicialización y 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 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 barajado (comprobación básica de que el orden cambia)
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 el barajado 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")Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases vs ObjetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Resumen - Calculadora simple4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosResumen - Jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso - Lista personalizada10Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador propertyDecorador de métodos estáticosDecorador de métodos de clase5Polimorfismo
Sobreescritura de métodos revisitadaDuck TypingClases y métodos abstractosDiseño de interfacesResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs de claseDecoradores de propiedadesAtributos privadosResumen - Gestor de cuentas bancarias