Введение
Объектно-ориентированное программирование (ООП) в Python предоставляет мощные инструменты для создания выразительных и настраиваемых объектов. Среди них специальные методы __init__, __str__ и __repr__ играют решающую роль в определении поведения и представления ваших объектов Python.
В этом руководстве мы рассмотрим эти специальные методы, поймем их назначение и научимся эффективно реализовывать их в ваших классах Python. К концу этой лабораторной работы вы сможете создавать более интуитивно понятные и удобные для пользователя объекты Python.
Создание классов с методом __init__
Первый специальный метод, который мы рассмотрим, — это __init__, который вызывается при создании объекта. Этот метод позволяет инициализировать атрибуты вашего объекта.
Понимание метода __init__
Метод __init__, также известный как конструктор, автоматически вызывается при создании нового экземпляра класса. Его основная задача — установить начальное состояние объекта, присваивая значения атрибутам.
Давайте начнем с создания простого класса Python с методом __init__:
Откройте терминал в вашей среде LabEx.
Перейдите в каталог проекта:
cd ~/projectСоздайте новый файл Python с именем
person.pyс помощью редактора кода:В редакторе добавьте следующий код в
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}")Сохраните файл.
Запустите скрипт Python:
python3 person.py
Вы должны увидеть следующий вывод:
Name: Alice
Age: 30
Объяснение метода __init__
В приведенном выше коде:
- Мы определили класс
Personс методом__init__, который принимает три параметра:self,nameиage. - Параметр
selfотносится к создаваемому экземпляру и автоматически передается при создании объекта. - Внутри метода
__init__мы присваиваем значенияnameиageатрибутам объекта, используяself.nameиself.age. - Когда мы создаем новый объект
Personс помощьюperson1 = Person("Alice", 30), метод__init__вызывается автоматически. - Затем мы можем получить доступ к атрибутам, используя точечную нотацию:
person1.nameиperson1.age.
Добавление большей функциональности
Давайте улучшим наш класс Person, добавив метод для вычисления года рождения на основе текущего года и возраста человека:
Откройте
person.pyв редакторе.Обновите код, чтобы включить метод для вычисления года рождения:
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()}")Сохраните файл.
Запустите скрипт Python:
python3 person.py
Вывод теперь должен включать вычисленный год рождения:
Name: Alice
Age: 30
Birth Year: 1993
(Фактический год рождения будет зависеть от текущего года, когда вы запустите скрипт)
Теперь вы создали класс Python с методом __init__ для инициализации атрибутов объекта и добавили метод для выполнения вычислений на основе этих атрибутов.
Реализация метода __str__
Теперь, когда мы понимаем, как создать класс с методом __init__, давайте рассмотрим еще один специальный метод под названием __str__. Этот метод позволяет нам определить, как объект должен быть представлен в виде строки.
Понимание метода __str__
Метод __str__ вызывается, когда вы используете функцию str() для объекта или когда вы печатаете объект с помощью функции print(). Он должен возвращать удобочитаемое строковое представление объекта.
Давайте обновим наш класс Person, чтобы включить метод __str__:
Откройте
person.pyв редакторе.Обновите код, чтобы включить метод
__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))Сохраните файл.
Запустите скрипт Python:
python3 person.py
Вы должны увидеть вывод, похожий на этот:
Name: Alice
Age: 30
Birth Year: 1993
String representation of the object:
Alice, 30 years old
Explicit string conversion:
Alice, 30 years old
Как работает __str__
В приведенном выше коде:
- Мы определили метод
__str__, который возвращает отформатированную строку с именем и возрастом человека. - Когда мы вызываем
print(person1), Python автоматически вызывает метод__str__, чтобы определить, что отображать. - Мы также можем явно преобразовать объект в строку, используя
str(person1), что также вызывает метод__str__.
Что происходит без __str__
Чтобы понять важность метода __str__, давайте посмотрим, что произойдет, если мы его не определим:
Создайте новый файл с именем
without_str.py:Добавьте следующий код:
class SimpleClass: def __init__(self, value): self.value = value ## Create an object obj = SimpleClass(42) ## Print the object print(obj)Сохраните файл.
Запустите скрипт:
python3 without_str.py
Вы должны увидеть вывод, подобный:
<__main__.SimpleClass object at 0x7f2d8c3e9d90>
Вывод не очень информативен. Он показывает имя класса и адрес памяти объекта, но не его содержимое. Вот почему реализация правильного метода __str__ важна для того, чтобы ваши объекты были более удобными для пользователя.
Практическое упражнение
Давайте создадим новый класс под названием Book с методом __str__:
Создайте новый файл с именем
book.py:Добавьте следующий код:
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)Сохраните файл.
Запустите скрипт:
python3 book.py
Вывод должен быть:
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)
Теперь вы понимаете, как использовать метод __str__ для создания удобочитаемых строковых представлений ваших объектов.
Реализация метода __repr__
В дополнение к __str__, Python предоставляет еще один специальный метод для строкового представления: __repr__. В то время как __str__ предназначен для предоставления удобочитаемого представления, __repr__ предназначен для предоставления однозначного представления объекта, которое можно использовать для повторного создания объекта, если это возможно.
Понимание метода __repr__
Метод __repr__ вызывается, когда вы используете функцию repr() для объекта или когда вы отображаете объект в интерактивной сессии. Он должен возвращать строку, которая при передаче в eval() создаст эквивалентный объект (если это возможно).
Давайте обновим наш класс Book, чтобы включить метод __repr__:
Откройте
book.pyв редакторе.Обновите код, чтобы включить метод
__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))Сохраните файл.
Запустите скрипт:
python3 book.py
Вы должны увидеть вывод, подобный:
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)
Различия между __str__ и __repr__
Основные различия между __str__ и __repr__ заключаются в следующем:
__str__предназначен для удобочитаемого вывода, в то время как__repr__предназначен для разработчиков и отладки.- Если
__str__не определен, но определен__repr__, Python будет использовать__repr__в качестве резервного варианта дляstr()илиprint(). __repr__в идеале должен возвращать строку, которая может воссоздать объект при передаче вeval(), хотя это не всегда возможно или необходимо.
Функция eval() с __repr__
При правильной реализации строка, возвращаемая __repr__, может быть использована с eval() для повторного создания объекта. Давайте посмотрим это в действии:
Создайте новый файл с именем
repr_eval.py:Добавьте следующий код:
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}")Сохраните файл.
Запустите скрипт:
python3 repr_eval.py
Вы должны увидеть вывод, подобный:
Representation: Point(3, 4)
Recreated object: Point at (3, 4)
p1.x = 3, p1.y = 4
p2.x = 3, p2.y = 4
Это демонстрирует, что мы можем воссоздать исходный объект, используя строку, возвращаемую __repr__, и функцию eval().
Когда использовать каждый метод
- Используйте
__init__для установки начального состояния ваших объектов. - Используйте
__str__для предоставления удобочитаемого представления для конечных пользователей. - Используйте
__repr__для предоставления точного, однозначного представления для разработчиков и отладки.
Практическое упражнение: Полный пример
Давайте объединим все это, создав класс Rectangle со всеми тремя специальными методами:
Создайте новый файл с именем
rectangle.py:Добавьте следующий код:
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()}")Сохраните файл.
Запустите скрипт:
python3 rectangle.py
Вы должны увидеть вывод, подобный:
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
Этот пример демонстрирует, как все три специальных метода (__init__, __str__ и __repr__) работают вместе для создания хорошо спроектированного класса.
Создание практического приложения
Теперь, когда мы понимаем три специальных метода __init__, __str__ и __repr__, давайте создадим более практичное приложение, которое демонстрирует их использование в реальном сценарии. Мы создадим простую банковскую систему с классом BankAccount.
Создание класса Bank Account
Создайте новый файл с именем
bank_account.py:Добавьте следующий код:
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})'Сохраните файл.
Тестирование класса Bank Account
Теперь давайте протестируем наш класс BankAccount:
Создайте новый файл с именем
bank_test.py:Добавьте следующий код:
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}")Сохраните файл.
Запустите скрипт:
python3 bank_test.py
Вы должны увидеть вывод, подобный этому:
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
Понимание реализации
В этом банковском приложении:
Метод
__init__инициализирует банковский счет с номером счета, именем владельца и необязательным начальным балансом. Он также создает пустой список для отслеживания транзакций.Методы
depositиwithdrawобрабатывают транзакции и обновляют баланс и историю транзакций.Метод
__str__предоставляет удобное для пользователя представление счета, показывая номер счета, имя владельца и текущий баланс.Метод
__repr__предоставляет строку, которая может воссоздать объект счета (хотя обратите внимание, что история транзакций не сохраняется в этой реализации).
Этот пример демонстрирует, как эти специальные методы могут быть использованы в практическом приложении для создания более интуитивных и удобных для пользователя объектов.
Расширение приложения
В качестве заключительного упражнения давайте создадим простую банковскую систему, которая управляет несколькими счетами:
Создайте новый файл с именем
banking_system.py:Добавьте следующий код:
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}")Сохраните файл.
Запустите скрипт:
python3 banking_system.py
Вы должны увидеть вывод, подобный:
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
Этот пример демонстрирует, как использовать класс BankAccount, который мы создали ранее, в более полном приложении. Он показывает, как специальные методы __init__, __str__ и __repr__ обеспечивают прочную основу для создания интуитивно понятных и удобных для пользователя классов.
Резюме
В этой лабораторной работе вы узнали о трех основных специальных методах в Python, которые помогают создавать более выразительные и удобные для пользователя объекты:
__init__: Метод-конструктор, который инициализирует атрибуты объекта при его создании.__str__: Метод, который предоставляет удобочитаемое строковое представление объекта, в основном для конечных пользователей.__repr__: Метод, который предоставляет однозначное строковое представление объекта, в основном для разработчиков и отладки.
Вы реализовали эти методы в нескольких примерах, от простых классов, таких как Person и Book, до более сложных приложений, таких как банковская система. Вы также узнали о различиях между __str__ и __repr__ и о том, когда использовать каждый из них.
Освоив эти специальные методы, вы сможете создавать более интуитивные и настраиваемые объекты Python, которые легко интегрируются со встроенными функциями языка. Эти знания являются важной частью объектно-ориентированного программирования в Python и помогут вам писать более чистый и удобный для сопровождения код.
Продолжая свой путь в Python, помните, что существует множество других специальных методов, которые позволяют вам настраивать поведение ваших объектов еще больше. Изучение этих методов даст вам еще больше контроля над тем, как ваши объекты ведут себя в разных контекстах.



