Как использовать методы __init__, __str__ и __repr__ в Python

PythonBeginner
Практиковаться сейчас

Введение

Объектно-ориентированное программирование (ООП) в Python предоставляет мощные инструменты для создания выразительных и настраиваемых объектов. Среди них специальные методы __init__, __str__ и __repr__ играют решающую роль в определении поведения и представления ваших объектов Python.

В этом руководстве мы рассмотрим эти специальные методы, поймем их назначение и научимся эффективно реализовывать их в ваших классах Python. К концу этой лабораторной работы вы сможете создавать более интуитивно понятные и удобные для пользователя объекты Python.

Создание классов с методом __init__

Первый специальный метод, который мы рассмотрим, — это __init__, который вызывается при создании объекта. Этот метод позволяет инициализировать атрибуты вашего объекта.

Понимание метода __init__

Метод __init__, также известный как конструктор, автоматически вызывается при создании нового экземпляра класса. Его основная задача — установить начальное состояние объекта, присваивая значения атрибутам.

Давайте начнем с создания простого класса Python с методом __init__:

  1. Откройте терминал в вашей среде LabEx.

  2. Перейдите в каталог проекта:

    cd ~/project
  3. Создайте новый файл Python с именем person.py с помощью редактора кода:

  4. В редакторе добавьте следующий код в 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}")
  5. Сохраните файл.

  6. Запустите скрипт 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, добавив метод для вычисления года рождения на основе текущего года и возраста человека:

  1. Откройте person.py в редакторе.

  2. Обновите код, чтобы включить метод для вычисления года рождения:

    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()}")
  3. Сохраните файл.

  4. Запустите скрипт Python:

    python3 person.py

Вывод теперь должен включать вычисленный год рождения:

Name: Alice
Age: 30
Birth Year: 1993

(Фактический год рождения будет зависеть от текущего года, когда вы запустите скрипт)

Теперь вы создали класс Python с методом __init__ для инициализации атрибутов объекта и добавили метод для выполнения вычислений на основе этих атрибутов.

Реализация метода __str__

Теперь, когда мы понимаем, как создать класс с методом __init__, давайте рассмотрим еще один специальный метод под названием __str__. Этот метод позволяет нам определить, как объект должен быть представлен в виде строки.

Понимание метода __str__

Метод __str__ вызывается, когда вы используете функцию str() для объекта или когда вы печатаете объект с помощью функции print(). Он должен возвращать удобочитаемое строковое представление объекта.

Давайте обновим наш класс Person, чтобы включить метод __str__:

  1. Откройте person.py в редакторе.

  2. Обновите код, чтобы включить метод __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))
  3. Сохраните файл.

  4. Запустите скрипт 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__, давайте посмотрим, что произойдет, если мы его не определим:

  1. Создайте новый файл с именем without_str.py:

  2. Добавьте следующий код:

    class SimpleClass:
        def __init__(self, value):
            self.value = value
    
    ## Create an object
    obj = SimpleClass(42)
    
    ## Print the object
    print(obj)
  3. Сохраните файл.

  4. Запустите скрипт:

    python3 without_str.py

Вы должны увидеть вывод, подобный:

<__main__.SimpleClass object at 0x7f2d8c3e9d90>

Вывод не очень информативен. Он показывает имя класса и адрес памяти объекта, но не его содержимое. Вот почему реализация правильного метода __str__ важна для того, чтобы ваши объекты были более удобными для пользователя.

Практическое упражнение

Давайте создадим новый класс под названием Book с методом __str__:

  1. Создайте новый файл с именем book.py:

  2. Добавьте следующий код:

    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)
  3. Сохраните файл.

  4. Запустите скрипт:

    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__:

  1. Откройте book.py в редакторе.

  2. Обновите код, чтобы включить метод __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))
  3. Сохраните файл.

  4. Запустите скрипт:

    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() для повторного создания объекта. Давайте посмотрим это в действии:

  1. Создайте новый файл с именем repr_eval.py:

  2. Добавьте следующий код:

    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}")
  3. Сохраните файл.

  4. Запустите скрипт:

    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 со всеми тремя специальными методами:

  1. Создайте новый файл с именем rectangle.py:

  2. Добавьте следующий код:

    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()}")
  3. Сохраните файл.

  4. Запустите скрипт:

    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

  1. Создайте новый файл с именем bank_account.py:

  2. Добавьте следующий код:

    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})'
  3. Сохраните файл.

Тестирование класса Bank Account

Теперь давайте протестируем наш класс BankAccount:

  1. Создайте новый файл с именем bank_test.py:

  2. Добавьте следующий код:

    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}")
  3. Сохраните файл.

  4. Запустите скрипт:

    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

Понимание реализации

В этом банковском приложении:

  1. Метод __init__ инициализирует банковский счет с номером счета, именем владельца и необязательным начальным балансом. Он также создает пустой список для отслеживания транзакций.

  2. Методы deposit и withdraw обрабатывают транзакции и обновляют баланс и историю транзакций.

  3. Метод __str__ предоставляет удобное для пользователя представление счета, показывая номер счета, имя владельца и текущий баланс.

  4. Метод __repr__ предоставляет строку, которая может воссоздать объект счета (хотя обратите внимание, что история транзакций не сохраняется в этой реализации).

Этот пример демонстрирует, как эти специальные методы могут быть использованы в практическом приложении для создания более интуитивных и удобных для пользователя объектов.

Расширение приложения

В качестве заключительного упражнения давайте создадим простую банковскую систему, которая управляет несколькими счетами:

  1. Создайте новый файл с именем banking_system.py:

  2. Добавьте следующий код:

    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}")
  3. Сохраните файл.

  4. Запустите скрипт:

    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, которые помогают создавать более выразительные и удобные для пользователя объекты:

  1. __init__: Метод-конструктор, который инициализирует атрибуты объекта при его создании.

  2. __str__: Метод, который предоставляет удобочитаемое строковое представление объекта, в основном для конечных пользователей.

  3. __repr__: Метод, который предоставляет однозначное строковое представление объекта, в основном для разработчиков и отладки.

Вы реализовали эти методы в нескольких примерах, от простых классов, таких как Person и Book, до более сложных приложений, таких как банковская система. Вы также узнали о различиях между __str__ и __repr__ и о том, когда использовать каждый из них.

Освоив эти специальные методы, вы сможете создавать более интуитивные и настраиваемые объекты Python, которые легко интегрируются со встроенными функциями языка. Эти знания являются важной частью объектно-ориентированного программирования в Python и помогут вам писать более чистый и удобный для сопровождения код.

Продолжая свой путь в Python, помните, что существует множество других специальных методов, которые позволяют вам настраивать поведение ваших объектов еще больше. Изучение этих методов даст вам еще больше контроля над тем, как ваши объекты ведут себя в разных контекстах.