Переопределение специальных методов

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

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

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

Кроме того, вы научитесь создавать менеджеры контекста. Файл, который нужно изменить в этом практическом занятии, - stock.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/FileHandlingGroup(["File Handling"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("Encapsulation") python/FileHandlingGroup -.-> python/file_opening_closing("Opening and Closing Files") python/FileHandlingGroup -.-> python/file_operations("File Operations") python/AdvancedTopicsGroup -.-> python/context_managers("Context Managers") subgraph Lab Skills python/build_in_functions -.-> lab-132496{{"Переопределение специальных методов"}} python/classes_objects -.-> lab-132496{{"Переопределение специальных методов"}} python/encapsulation -.-> lab-132496{{"Переопределение специальных методов"}} python/file_opening_closing -.-> lab-132496{{"Переопределение специальных методов"}} python/file_operations -.-> lab-132496{{"Переопределение специальных методов"}} python/context_managers -.-> lab-132496{{"Переопределение специальных методов"}} end

Улучшение представления объектов с помощью __repr__

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

Первый тип - это строковое представление. Оно создается функцией str(), которая автоматически вызывается при использовании функции print(). Строковое представление предназначено для удобного чтения человеком. Оно представляет объект в формате, который легко понять и интерпретировать.

Второй тип - это кодовое представление. Оно генерируется функцией repr(). Кодовое представление показывает код, который нужно написать, чтобы воссоздать объект. Оно больше ориентировано на точное и однозначное представление объекта в коде.

Давайте рассмотрим конкретный пример с использованием встроенного класса date в Python. Это поможет вам увидеть разницу между строковым и кодовым представлениями на практике.

>>> from datetime import date
>>> d = date(2008, 7, 5)
>>> print(d)              ## Uses str()
2008-07-05
>>> d                     ## Uses repr()
datetime.date(2008, 7, 5)

В этом примере, когда мы используем print(d), Python вызывает функцию str() для объекта date d, и мы получаем человекочитаемую дату в формате ГГГГ-ММ-ДД. Когда мы просто вводим d в интерактивной оболочке, Python вызывает функцию repr(), и мы видим код, необходимый для воссоздания объекта date.

Вы можете явно получить строку repr() различными способами. Вот несколько примеров:

>>> print('The date is', repr(d))
The date is datetime.date(2008, 7, 5)
>>> print(f'The date is {d!r}')
The date is datetime.date(2008, 7, 5)
>>> print('The date is %r' % d)
The date is datetime.date(2008, 7, 5)

Теперь давайте применим этот концепт к нашему классу Stock. Мы будем улучшать класс, реализовав метод __repr__. Этот специальный метод вызывается Python, когда ему нужен кодовое представление объекта.

Для этого откройте файл stock.py в вашем редакторе. Затем добавьте метод __repr__ в класс Stock. Метод __repr__ должен возвращать строку, которая показывает код, необходимый для воссоздания объекта Stock.

def __repr__(self):
    return f"Stock('{self.name}', {self.shares}, {self.price})"

После добавления метода __repr__ ваш полный класс Stock должен выглядеть следующим образом:

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, shares):
        self.shares -= shares

    def __repr__(self):
        return f"Stock('{self.name}', {self.shares}, {self.price})"

Теперь давайте протестируем наш модифицированный класс Stock. Откройте интерактивную оболочку Python, выполнив следующую команду в терминале:

python3

После открытия интерактивной оболочки попробуйте следующие команды:

>>> import stock
>>> goog = stock.Stock('GOOG', 100, 490.10)
>>> goog
Stock('GOOG', 100, 490.1)

Вы также можете увидеть, как метод __repr__ работает с портфелем акций. Вот пример:

>>> import reader
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> portfolio
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]

Как вы можете видеть, метод __repr__ сделал наши объекты Stock гораздо более информативными при отображении в интерактивной оболочке или отладчике. Теперь он показывает код, необходимый для воссоздания каждого объекта, что очень полезно для отладки и понимания состояния объектов.

После завершения тестирования вы можете выйти из интерпретатора Python, выполнив следующую команду:

>>> exit()
✨ Проверить решение и практиковаться

Сравнение объектов с использованием __eq__

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

Давайте рассмотрим пример. Предположим, у нас есть класс Stock, и мы создаем два объекта этого класса с одинаковыми значениями. Затем мы пытаемся сравнить их с помощью оператора ==. Вот как это можно сделать в интерпретаторе Python:

Сначала запустите интерпретатор Python, выполнив следующую команду в терминале:

python3

Затем в интерпретаторе Python выполните следующий код:

>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
False

Как вы видите, даже несмотря на то, что два объекта Stock a и b имеют одинаковые значения своих атрибутов (name, shares и price), Python считает их разными объектами, потому что они хранятся по разным адресам в памяти.

Чтобы исправить эту проблему, мы можем реализовать метод __eq__ в нашем классе Stock. Этот метод будет вызываться каждый раз, когда на объектах класса Stock используется оператор ==.

Теперь откройте файл stock.py снова. Внутри класса Stock добавьте следующий метод __eq__:

def __eq__(self, other):
    return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
                                         (other.name, other.shares, other.price))

Разберем, что делает этот метод:

  1. Сначала он использует функцию isinstance для проверки, является ли объект other экземпляром класса Stock. Это важно, потому что мы хотим сравнивать объекты Stock только с другими объектами Stock.
  2. Если other является объектом Stock, то он сравнивает атрибуты (name, shares и price) как объекта self, так и объекта other.
  3. Он возвращает True только в том случае, если оба объекта являются экземплярами Stock и их атрибуты идентичны.

После добавления метода __eq__ ваш полный класс Stock должен выглядеть следующим образом:

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, shares):
        self.shares -= shares

    def __repr__(self):
        return f"Stock('{self.name}', {self.shares}, {self.price})"

    def __eq__(self, other):
        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
                                             (other.name, other.shares, other.price))

Теперь давайте протестируем наш улучшенный класс Stock. Запустите интерпретатор Python снова:

python3

Затем выполните следующий код в интерпретаторе Python:

>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
True
>>> c = stock.Stock('GOOG', 200, 490.1)
>>> a == c
False

Отлично! Теперь наши объекты Stock можно корректно сравнивать по их содержимому, а не по адресам в памяти.

Проверка isinstance в методе __eq__ является важной. Она гарантирует, что мы сравниваем только объекты Stock. Если бы этой проверки не было, сравнение объекта Stock с чем-то, что не является объектом Stock, могло бы вызвать ошибки.

После завершения тестирования вы можете выйти из интерпретатора Python, выполнив следующую команду:

>>> exit()
✨ Проверить решение и практиковаться

Создание менеджера контекста

Менеджер контекста - это специальный тип объекта в Python. В Python объекты могут иметь различные методы, которые определяют их поведение. Менеджер контекста в частности определяет два важных метода: __enter__ и __exit__. Эти методы работают совместно с оператором with. Оператор with используется для настройки определенного контекста для блока кода. Представьте, что он создает небольшую среду, где происходят определенные действия, и когда блок кода завершен, менеджер контекста занимается очисткой.

На этом этапе мы создадим менеджер контекста, который имеет очень полезную функцию. Он временно перенаправит стандартный вывод (sys.stdout). Стандартный вывод - это место, куда идет обычный вывод вашей Python-программы, обычно консоль. Перенаправив его, мы можем отправить вывод в файл вместо этого. Это удобно, когда вы хотите сохранить вывод, который иначе просто отобразится на консоли.

Сначала нам нужно создать новый файл для написания кода нашего менеджера контекста. Мы назовем этот файл redirect.py. Вы можете создать его, используя следующую команду в терминале:

touch /home/labex/project/redirect.py

Теперь, когда файл создан, откройте его в редакторе. После открытия добавьте следующий Python-код в файл:

import sys

class redirect_stdout:
    def __init__(self, out_file):
        self.out_file = out_file

    def __enter__(self):
        self.stdout = sys.stdout
        sys.stdout = self.out_file
        return self.out_file

    def __exit__(self, ty, val, tb):
        sys.stdout = self.stdout

Разберем, что делает этот менеджер контекста:

  1. __init__: Это метод инициализации. Когда мы создаем экземпляр класса redirect_stdout, мы передаем в него объект файла. Этот метод сохраняет этот объект файла в экземплярной переменной self.out_file. Таким образом, он запоминает, куда мы хотим перенаправить вывод.
  2. __enter__:
    • Сначала он сохраняет текущий sys.stdout. Это важно, потому что нам нужно восстановить его позже.
    • Затем он заменяет текущий sys.stdout на наш объект файла. С этого момента любой вывод, который обычно бы отправлялся на консоль, будет направлен в файл.
    • Наконец, он возвращает объект файла. Это полезно, так как мы можем захотеть использовать объект файла внутри блока with.
  3. __exit__:
    • Этот метод восстанавливает исходный sys.stdout. Таким образом, после завершения блока with вывод снова будет направляться на консоль, как обычно.
    • Он принимает три параметра: тип исключения (ty), значение исключения (val) и трассировку стека (tb). Эти параметры требуются протоколом менеджера контекста. Они используются для обработки любых исключений, которые могут возникнуть внутри блока with.

Теперь давайте протестируем наш менеджер контекста. Мы используем его для перенаправления вывода таблицы в файл. Сначала запустите интерпретатор Python:

python3

Затем выполните следующий Python-код в интерпретаторе:

>>> import stock, reader, tableformat
>>> from redirect import redirect_stdout
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> formatter = tableformat.create_formatter('text')
>>> with redirect_stdout(open('out.txt', 'w')) as file:
...     tableformat.print_table(portfolio, ['name','shares','price'], formatter)
...     file.close()
...
>>> ## Let's check the content of the output file
>>> print(open('out.txt').read())
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

Отлично! Наш менеджер контекста работал как ожидалось. Он успешно перенаправил вывод таблицы в файл out.txt.

Менеджеры контекста - это очень мощная функция в Python. Они помогают вам правильно управлять ресурсами. Вот несколько общих случаев использования менеджеров контекста:

  • Файловые операции: Когда вы открываете файл, менеджер контекста может убедиться, что файл будет правильно закрыт, даже если произошла ошибка.
  • Соединения с базами данных: Он может гарантировать, что соединение с базой данных будет закрыто после того, как вы закончите использовать его.
  • Блокировки в многопоточных программах: Менеджеры контекста могут безопасно обрабатывать блокировку и разблокировку ресурсов.
  • Временное изменение параметров среды: Вы можете изменить некоторые настройки для блока кода и затем автоматически восстановить их.

Этот шаблон очень важен, потому что он гарантирует, что ресурсы будут очищены, даже если внутри блока with произошло исключение.

После завершения тестирования вы можете выйти из интерпретатора Python:

>>> exit()

Резюме

В этом практическом занятии вы узнали, как настраивать строковое представление объектов с помощью метода __repr__, сравнивать объекты с использованием метода __eq__ и создавать менеджер контекста с помощью методов __enter__ и __exit__. Эти специальные "дандер - методы" являются основой объектно - ориентированных возможностей Python.

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