Introducción
Las características de la programación orientada a objetos (OOP, por sus siglas en inglés) de Python ofrecen herramientas poderosas para crear objetos expresivos y personalizados. Entre estas, los métodos especiales __init__, __str__ y __repr__ juegan roles cruciales en la definición del comportamiento y la representación de sus objetos Python.
En este tutorial, exploraremos estos métodos especiales, comprenderemos sus propósitos y aprenderemos cómo implementarlos eficazmente en sus clases Python. Al final de este laboratorio, podrá crear objetos Python más intuitivos y fáciles de usar.
Creación de Clases con el Método __init__
El primer método especial que exploraremos es __init__, que se llama cuando se crea un objeto. Este método le permite inicializar los atributos de su objeto.
Entendiendo el Método __init__
El método __init__, también conocido como constructor, se llama automáticamente cuando crea una nueva instancia de una clase. Su propósito principal es configurar el estado inicial del objeto asignando valores a los atributos.
Comencemos creando una clase Python simple con el método __init__:
Abra la terminal en su entorno LabEx.
Navegue al directorio del proyecto:
cd ~/projectCree un nuevo archivo Python llamado
person.pyusando el editor de código:En el editor, agregue el siguiente código a
person.py:class Person: def __init__(self, name, age): self.name = name self.age = age ## Create a Person object person1 = Person("Alice", 30) ## Access the attributes print(f"Name: {person1.name}") print(f"Age: {person1.age}")Guarde el archivo.
Ejecute el script Python:
python3 person.py
Debería ver la siguiente salida:
Name: Alice
Age: 30
Explicación del Método __init__
En el código anterior:
- Definimos una clase
Personcon un método__init__que toma tres parámetros:self,nameyage. - El parámetro
selfse refiere a la instancia que se está creando y se pasa automáticamente cuando se crea un objeto. - Dentro del método
__init__, asignamos los valores denameyagea los atributos del objeto usandoself.nameyself.age. - Cuando creamos un nuevo objeto
Personconperson1 = Person("Alice", 30), el método__init__se llama automáticamente. - Luego podemos acceder a los atributos usando la notación de punto:
person1.nameyperson1.age.
Añadiendo Más Funcionalidad
Mejoremos nuestra clase Person agregando un método para calcular el año de nacimiento basado en el año actual y la edad de la persona:
Abra
person.pyen el editor.Actualice el código para incluir un método para calcular el año de nacimiento:
import datetime class Person: def __init__(self, name, age): self.name = name self.age = age def birth_year(self): current_year = datetime.datetime.now().year return current_year - self.age ## Create a Person object person1 = Person("Alice", 30) ## Access the attributes and call the method print(f"Name: {person1.name}") print(f"Age: {person1.age}") print(f"Birth Year: {person1.birth_year()}")Guarde el archivo.
Ejecute el script Python:
python3 person.py
La salida ahora debería incluir el año de nacimiento calculado:
Name: Alice
Age: 30
Birth Year: 1993
(El año de nacimiento real dependerá del año actual cuando ejecute el script)
Ahora ha creado una clase Python con el método __init__ para inicializar los atributos del objeto y ha agregado un método para realizar cálculos basados en esos atributos.
Implementando el Método __str__
Ahora que entendemos cómo crear una clase con el método __init__, exploremos otro método especial llamado __str__. Este método nos permite definir cómo se debe representar un objeto como una cadena.
Entendiendo el Método __str__
El método __str__ se llama cuando usa la función str() en un objeto o cuando imprime un objeto usando la función print(). Debe devolver una representación de cadena legible por humanos del objeto.
Actualicemos nuestra clase Person para incluir un método __str__:
Abra
person.pyen el editor.Actualice el código para incluir un método
__str__:import datetime class Person: def __init__(self, name, age): self.name = name self.age = age def birth_year(self): current_year = datetime.datetime.now().year return current_year - self.age def __str__(self): return f"{self.name}, {self.age} years old" ## Create a Person object person1 = Person("Alice", 30) ## Access the attributes and call the method print(f"Name: {person1.name}") print(f"Age: {person1.age}") print(f"Birth Year: {person1.birth_year()}") ## Use the __str__ method implicitly print("\nString representation of the object:") print(person1) ## Use the __str__ method explicitly print("\nExplicit string conversion:") print(str(person1))Guarde el archivo.
Ejecute el script Python:
python3 person.py
Debería ver una salida similar a esta:
Name: Alice
Age: 30
Birth Year: 1993
String representation of the object:
Alice, 30 years old
Explicit string conversion:
Alice, 30 years old
Cómo Funciona __str__
En el código anterior:
- Definimos un método
__str__que devuelve una cadena formateada con el nombre y la edad de la persona. - Cuando llamamos a
print(person1), Python llama automáticamente al método__str__para determinar qué mostrar. - También podemos convertir explícitamente un objeto a una cadena usando
str(person1), que también llama al método__str__.
Qué Sucede Sin __str__
Para comprender la importancia del método __str__, veamos qué sucede cuando no lo definimos:
Cree un nuevo archivo llamado
without_str.py:Agregue el siguiente código:
class SimpleClass: def __init__(self, value): self.value = value ## Create an object obj = SimpleClass(42) ## Print the object print(obj)Guarde el archivo.
Ejecute el script:
python3 without_str.py
Debería ver una salida como:
<__main__.SimpleClass object at 0x7f2d8c3e9d90>
La salida no es muy informativa. Muestra el nombre de la clase y la dirección de memoria del objeto, pero no su contenido. Esta es la razón por la que implementar un método __str__ adecuado es importante para que sus objetos sean más fáciles de usar.
Ejercicio Práctico
Creemos una nueva clase llamada Book con un método __str__:
Cree un nuevo archivo llamado
book.py:Agregue el siguiente código:
class Book: def __init__(self, title, author, pages): self.title = title self.author = author self.pages = pages def __str__(self): return f'"{self.title}" by {self.author} ({self.pages} pages)' ## Create book objects book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180) book2 = Book("To Kill a Mockingbird", "Harper Lee", 281) ## Print the books print(book1) print(book2)Guarde el archivo.
Ejecute el script:
python3 book.py
La salida debería ser:
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)
Ahora comprende cómo usar el método __str__ para crear representaciones de cadena legibles por humanos de sus objetos.
Implementando el Método __repr__
Además de __str__, Python proporciona otro método especial para la representación de cadenas: __repr__. Mientras que __str__ está destinado a proporcionar una representación legible por humanos, __repr__ tiene como objetivo proporcionar una representación inequívoca de un objeto que se puede usar para recrear el objeto si es posible.
Entendiendo el Método __repr__
El método __repr__ se llama cuando usa la función repr() en un objeto o cuando muestra un objeto en una sesión interactiva. Debe devolver una cadena que, cuando se pasa a eval(), crearía un objeto equivalente (cuando sea posible).
Actualicemos nuestra clase Book para incluir un método __repr__:
Abra
book.pyen el editor.Actualice el código para incluir un método
__repr__:class Book: def __init__(self, title, author, pages): self.title = title self.author = author self.pages = pages def __str__(self): return f'"{self.title}" by {self.author} ({self.pages} pages)' def __repr__(self): return f'Book("{self.title}", "{self.author}", {self.pages})' ## Create book objects book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180) book2 = Book("To Kill a Mockingbird", "Harper Lee", 281) ## Print the books (uses __str__) print("String representation (using __str__):") print(book1) print(book2) ## Get the representation (uses __repr__) print("\nRepresentation (using __repr__):") print(repr(book1)) print(repr(book2))Guarde el archivo.
Ejecute el script:
python3 book.py
Debería ver una salida como:
String representation (using __str__):
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)
Representation (using __repr__):
Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
Book("To Kill a Mockingbird", "Harper Lee", 281)
Diferencias entre __str__ y __repr__
Las principales diferencias entre __str__ y __repr__ son:
__str__está destinado a la salida legible por humanos, mientras que__repr__está destinado a desarrolladores y depuración.- Si
__str__no está definido pero__repr__sí, Python usará__repr__como respaldo parastr()oprint(). __repr__idealmente debería devolver una cadena que pueda recrear el objeto cuando se pasa aeval(), aunque esto no siempre es posible o necesario.
La Función eval() con __repr__
Cuando se implementa correctamente, la cadena devuelta por __repr__ se puede usar con eval() para recrear el objeto. Veamos esto en acción:
Cree un nuevo archivo llamado
repr_eval.py:Agregue el siguiente código:
class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"Point at ({self.x}, {self.y})" def __repr__(self): return f"Point({self.x}, {self.y})" ## Create a point p1 = Point(3, 4) ## Get the repr string repr_str = repr(p1) print(f"Representation: {repr_str}") ## Use eval to recreate the object p2 = eval(repr_str) print(f"Recreated object: {p2}") ## Verify they have the same values print(f"p1.x = {p1.x}, p1.y = {p1.y}") print(f"p2.x = {p2.x}, p2.y = {p2.y}")Guarde el archivo.
Ejecute el script:
python3 repr_eval.py
Debería ver una salida como:
Representation: Point(3, 4)
Recreated object: Point at (3, 4)
p1.x = 3, p1.y = 4
p2.x = 3, p2.y = 4
Esto demuestra que podemos recrear el objeto original usando la cadena devuelta por __repr__ y la función eval().
Cuándo Usar Cada Método
- Use
__init__para configurar el estado inicial de sus objetos. - Use
__str__para proporcionar una representación legible por humanos para los usuarios finales. - Use
__repr__para proporcionar una representación precisa e inequívoca para desarrolladores y depuración.
Ejercicio Práctico: Un Ejemplo Completo
Pongamos todo junto creando una clase Rectangle con los tres métodos especiales:
Cree un nuevo archivo llamado
rectangle.py:Agregue el siguiente código:
class Rectangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height) def __str__(self): return f"Rectangle with width {self.width} and height {self.height}" def __repr__(self): return f"Rectangle({self.width}, {self.height})" ## Create rectangles rect1 = Rectangle(5, 10) rect2 = Rectangle(3, 7) ## Display information about the rectangles print(f"Rectangle 1: {rect1}") print(f"Area: {rect1.area()}") print(f"Perimeter: {rect1.perimeter()}") print(f"Representation: {repr(rect1)}") print("\nRectangle 2: {0}".format(rect2)) print(f"Area: {rect2.area()}") print(f"Perimeter: {rect2.perimeter()}") print(f"Representation: {repr(rect2)}") ## Recreate a rectangle using eval rect3 = eval(repr(rect1)) print(f"\nRecreated rectangle: {rect3}") print(f"Is it the same area? {rect3.area() == rect1.area()}")Guarde el archivo.
Ejecute el script:
python3 rectangle.py
Debería ver una salida como:
Rectangle 1: Rectangle with width 5 and height 10
Area: 50
Perimeter: 30
Representation: Rectangle(5, 10)
Rectangle 2: Rectangle with width 3 and height 7
Area: 21
Perimeter: 20
Representation: Rectangle(3, 7)
Recreated rectangle: Rectangle with width 5 and height 10
Is it the same area? True
Este ejemplo demuestra cómo los tres métodos especiales (__init__, __str__ y __repr__) funcionan juntos para crear una clase bien diseñada.
Creando una Aplicación Práctica
Ahora que entendemos los tres métodos especiales __init__, __str__ y __repr__, creemos una aplicación más práctica que demuestre su uso en un escenario del mundo real. Crearemos un sistema bancario simple con una clase BankAccount.
Construyendo una Clase Bank Account
Cree un nuevo archivo llamado
bank_account.py:Agregue el siguiente código:
class BankAccount: def __init__(self, account_number, owner_name, balance=0.0): self.account_number = account_number self.owner_name = owner_name self.balance = balance self.transactions = [] def deposit(self, amount): if amount <= 0: print("Deposit amount must be positive") return False self.balance += amount self.transactions.append(f"Deposit: +${amount:.2f}") return True def withdraw(self, amount): if amount <= 0: print("Withdrawal amount must be positive") return False if amount > self.balance: print("Insufficient funds") return False self.balance -= amount self.transactions.append(f"Withdrawal: -${amount:.2f}") return True def get_transaction_history(self): return self.transactions def __str__(self): return f"Account {self.account_number} | Owner: {self.owner_name} | Balance: ${self.balance:.2f}" def __repr__(self): return f'BankAccount("{self.account_number}", "{self.owner_name}", {self.balance})'Guarde el archivo.
Probando la Clase Bank Account
Ahora probemos nuestra clase BankAccount:
Cree un nuevo archivo llamado
bank_test.py:Agregue el siguiente código:
from bank_account import BankAccount ## Create bank accounts account1 = BankAccount("12345", "John Doe", 1000.0) account2 = BankAccount("67890", "Jane Smith", 500.0) ## Display initial account information print("Initial Account Status:") print(account1) print(account2) ## Perform transactions print("\nPerforming transactions...") ## Deposit to account1 print("\nDepositing $250 to account1:") account1.deposit(250) print(account1) ## Withdraw from account2 print("\nWithdrawing $100 from account2:") account2.withdraw(100) print(account2) ## Try to withdraw too much from account2 print("\nTrying to withdraw $1000 from account2:") account2.withdraw(1000) print(account2) ## Try to deposit a negative amount to account1 print("\nTrying to deposit -$50 to account1:") account1.deposit(-50) print(account1) ## Display transaction history print("\nTransaction history for account1:") for transaction in account1.get_transaction_history(): print(f"- {transaction}") print("\nTransaction history for account2:") for transaction in account2.get_transaction_history(): print(f"- {transaction}") ## Recreate an account using repr print("\nRecreating account1 using repr:") account1_repr = repr(account1) print(f"Representation: {account1_repr}") recreated_account = eval(account1_repr) print(f"Recreated account: {recreated_account}")Guarde el archivo.
Ejecute el script:
python3 bank_test.py
Debería ver una salida similar a esta:
Initial Account Status:
Account 12345 | Owner: John Doe | Balance: $1000.00
Account 67890 | Owner: Jane Smith | Balance: $500.00
Performing transactions...
Depositing $250 to account1:
Account 12345 | Owner: John Doe | Balance: $1250.00
Withdrawing $100 from account2:
Account 67890 | Owner: Jane Smith | Balance: $400.00
Trying to withdraw $1000 from account2:
Insufficient funds
Account 67890 | Owner: Jane Smith | Balance: $400.00
Trying to deposit -$50 to account1:
Deposit amount must be positive
Account 12345 | Owner: John Doe | Balance: $1250.00
Transaction history for account1:
- Deposit: +$250.00
Transaction history for account2:
- Withdrawal: -$100.00
Recreating account1 using repr:
Representation: BankAccount("12345", "John Doe", 1250.0)
Recreated account: Account 12345 | Owner: John Doe | Balance: $1250.00
Entendiendo la Implementación
En esta aplicación bancaria:
El método
__init__inicializa una cuenta bancaria con un número de cuenta, el nombre del propietario y un saldo inicial opcional. También crea una lista vacía para rastrear las transacciones.Los métodos
depositywithdrawmanejan las transacciones y actualizan el saldo y el historial de transacciones.El método
__str__proporciona una representación fácil de usar de la cuenta, mostrando el número de cuenta, el nombre del propietario y el saldo actual.El método
__repr__proporciona una cadena que puede recrear el objeto de la cuenta (aunque tenga en cuenta que el historial de transacciones no se conserva en esta implementación).
Este ejemplo demuestra cómo estos métodos especiales se pueden usar en una aplicación práctica para crear objetos más intuitivos y fáciles de usar.
Extensión de la Aplicación
Como ejercicio final, creemos un sistema bancario simple que gestione múltiples cuentas:
Cree un nuevo archivo llamado
banking_system.py:Agregue el siguiente código:
from bank_account import BankAccount class BankingSystem: def __init__(self, bank_name): self.bank_name = bank_name self.accounts = {} def create_account(self, account_number, owner_name, initial_balance=0.0): if account_number in self.accounts: print(f"Account {account_number} already exists") return None account = BankAccount(account_number, owner_name, initial_balance) self.accounts[account_number] = account return account def get_account(self, account_number): return self.accounts.get(account_number) def list_accounts(self): return list(self.accounts.values()) def __str__(self): return f"{self.bank_name} - Managing {len(self.accounts)} accounts" def __repr__(self): return f'BankingSystem("{self.bank_name}")' ## Create a banking system bank = BankingSystem("Python First Bank") print(bank) ## Create some accounts bank.create_account("A001", "John Doe", 1000) bank.create_account("A002", "Jane Smith", 500) bank.create_account("A003", "Bob Johnson", 250) ## List all accounts print("\nAll accounts:") for account in bank.list_accounts(): print(account) ## Make some transactions account = bank.get_account("A001") if account: print(f"\nBefore deposit: {account}") account.deposit(500) print(f"After deposit: {account}") account = bank.get_account("A002") if account: print(f"\nBefore withdrawal: {account}") account.withdraw(200) print(f"After withdrawal: {account}") ## Try to create an existing account print("\nTrying to create an existing account:") bank.create_account("A001", "Someone Else", 300) ## Final state of the banking system print(f"\n{bank}")Guarde el archivo.
Ejecute el script:
python3 banking_system.py
Debería ver una salida similar a:
Python First Bank - Managing 0 accounts
All accounts:
Account A001 | Owner: John Doe | Balance: $1000.00
Account A002 | Owner: Jane Smith | Balance: $500.00
Account A003 | Owner: Bob Johnson | Balance: $250.00
Before deposit: Account A001 | Owner: John Doe | Balance: $1000.00
After deposit: Account A001 | Owner: John Doe | Balance: $1500.00
Before withdrawal: Account A002 | Owner: Jane Smith | Balance: $500.00
After withdrawal: Account A002 | Owner: Jane Smith | Balance: $300.00
Trying to create an existing account:
Account A001 already exists
Python First Bank - Managing 3 accounts
Este ejemplo demuestra cómo usar la clase BankAccount que creamos anteriormente en una aplicación más completa. Muestra cómo los métodos especiales __init__, __str__ y __repr__ proporcionan una base sólida para crear clases intuitivas y fáciles de usar.
Resumen
En este laboratorio, aprendió sobre tres métodos especiales esenciales en Python que le ayudan a crear objetos más expresivos y fáciles de usar:
__init__: El método constructor que inicializa los atributos de un objeto cuando se crea.__str__: Un método que proporciona una representación de cadena legible por humanos de un objeto, principalmente para usuarios finales.__repr__: Un método que proporciona una representación de cadena inequívoca de un objeto, principalmente para desarrolladores y depuración.
Implementó estos métodos en varios ejemplos, desde clases simples como Person y Book hasta aplicaciones más complejas como un sistema bancario. También aprendió las diferencias entre __str__ y __repr__ y cuándo usar cada uno.
Al dominar estos métodos especiales, puede crear objetos Python más intuitivos y personalizados que se integran a la perfección con las funciones integradas del lenguaje. Este conocimiento forma una parte esencial de la programación orientada a objetos en Python y le ayudará a escribir código más limpio y fácil de mantener.
A medida que continúa su viaje en Python, recuerde que hay muchos otros métodos especiales disponibles que le permiten personalizar aún más el comportamiento de sus objetos. Explorar estos métodos le dará aún más control sobre cómo se comportan sus objetos en diferentes contextos.



