<div><img src="https://top-fwz1.mail.ru/counter?id=3548135;js=na" style="position:absolute;left:-9999px;" alt="Top.Mail.Ru" /></div>
Бесплатный перенос IT-инфраструктуры в облако

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

Миша Курушин
Миша Курушин
Технический писатель
28 декабря 2024 г.
63
12 минут чтения
Средний рейтинг статьи: 5

Язык программирования Python имеет множество синтаксических конструкций. Большая часть из них — операторы различных типов:

  • Арифметические
  • Битовые
  • Логические
  • Условные
  • Принадлежности
  • Сравнения
  • Тождественности
  • Присваивания

Так вот, каждый раз, когда в коде встречается какой-либо оператор (+, -, *, /), Python неявно вызывает соответствующую ему специальную функцию, которая и выполняет преобразование данных.

Такие функции доступны для пользовательского вызова, хотя и имеют специфические названия. Вот несколько из таких специальных методов:

  • __new__ : Создает экземпляр объекта.
  • __del__ : Выполняет уничтожение объекта.
  • __add__(self, other): Выполняет сложение.
  • __sub__(self, other): Выполняет вычитание.
  • __mul__(self, other): Выполняет умножение.
  • __truediv__(self, other): Выполняет деление.
  • __eq__ (self, other): Выполняет сравнение.

На их «специальность» указывают два символа нижнего подчеркивания (__) в начале и в конце названия. По этой причине подобные методы называют dunder- (double underscore) методами.

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

Кстати, язык Python позволяет посмотреть специальные методы для каждого предопределенного типа с помощью функции dir().

Если ее вызвать без указания аргумента, то она вернет список имен из глобальной области. Если же аргумент указать (это будет имя какого-то типа), то метод dir(type) вернет список имен из переданного в качестве аргумента типа.

Например, для int вызов функции будет таким:

print(dir(int)) # вывод списка имен из типа int

После этого в консольном терминале появится длинный список имен.

Image1

Если же аргумент не указывать:

print(dir()) # вывод списка имен из глобальной области

То в консольном терминале отобразится относительно короткий список:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '_pyodide_core', 'datetime', 'mydate']

Среди специальных методов особый интерес представляют две функции:

  • __str__(self)

  • __repr__(self)

Предназначение обоих методов — предоставление дополнительной информации о созданных переменных и их значениях.

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

Именно использование методов __str__() и __repr__() и будет рассмотрено в этом руководстве.

Все показанные примеры кода запускались на облачном сервере Timeweb Cloud под управлением операционной системы Ubuntu 22.04 с использованием интерпретатора Python версии 3.10.12.

Разница между __str__() и __repr__()

Методы связаны между собой, однако имеют существенные различия по части вывода дополнительной информации:

  • Метод __str__() возвращает фактическое значение переменной в формате строки. Он менее информативен. А еще вызывается функциями print(), str(), format().

  • Метод __repr__() возвращает репрезентируемое значение переменной в формате кода. Он более информативен. А еще вызывается функцией repr().

При этом, в отличие от __str__(), результат метода __repr__() можно использовать для воссоздания переменной с помощью функции eval(), которая выполняет код (выражения языка Python) в формате строки.

Грубо говоря, возвращаемое значение метода __str__() ориентировано на пользователя, в то время как результат функции __repr__() больше предназначен для разработчика.

vds

Пример: встроенные типы данные

Для более глубокого понимания отличия между обоими методами лучше всего привести простой, но наглядный пример:

variableInt = 13 # переменная с целочисленным значением
variableString = "13" # переменная со строковым значением

# вывод в консоль типов переменных

print(type(variableInt))
print(type(variableString))

# вывод в консоль целочисленной переменной

print(variableInt.__str__())
print(variableInt.__repr__())

# вывод в консоль строковой переменной

print(variableString.__str__())
print(variableString.__repr__())

Консольный вывод этого примера будет следующим:

<class 'int'>
<class 'str'>
13
13
13
'13'

Во-первых, переменные однозначно имеют разные типы — целочисленный и строковый.

Во-вторых, для целочисленной переменной методы __str__() и __repr__() возвращают одно и тоже значение. Однако ситуация полностью противоположная для строковой переменной возвращаемой значение метода __repr__() обрамлено одинарными кавычками.

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

variableInt = 13
variableString = "13"

print(type(variableInt))
print(type(variableString))

print(variableInt.__str__())
print(variableInt.__repr__())

print(variableString.__str__())
print(variableString.__repr__())

# создание новых переменных (целочисленной и строковой) с помощью функции eval() на основе дополнительной информации, репрезентирующей их

variableIntNew = eval(variableInt.__repr__())
variableStringNew = eval(variableString.__repr__())

# вывод в консольный терминал типов новых переменных

print(type(variableIntNew))
print(type(variableStringNew))

# вывод в консольный терминал значения новых переменных

print(variableIntNew)
print(variableStringNew)

На этот раз вывод в консольном терминале будет уже таким:

<class 'int'>
<class 'str'>
13
13
13
'13'
<class 'int'>
<class 'str'>
13
13

Как можно заметить, на основе возвращаемого значения метода __repr__() создаются полноценные копии уже существующих переменных — целочисленная и строковая соответственно.

Однако если бы вместо __repr__() использовалась __str__(), то такой результат не был бы достигнут — вместо новой строковой появилась бы новая целочисленная переменная:

variableInt = 13
variableString = "13"

print(type(variableInt))
print(type(variableString))

variableIntNew = eval(variableInt.__str__())
variableStringNew = eval(variableString.__str__())

print(type(variableIntNew))
print(type(variableStringNew))

Вывод в консольный терминал после запуска этого примера будет следующим:

<class 'int'>
<class 'str'>
<class 'int'>
<class 'int'>

Как видно, обе новые переменные имеют один и тот же тип — целочисленный. Очевидно, это не то поведение, которое хотелось достигнуть.

Таким образом, можно сформулировать разницу между обоими методами довольно простым языком:

  • Функция __str__() возвращает текущее значение переменной.

  • Функция __repr__() возвращает код, способный создать аналогичную переменную с таким же значением.

Пример: классы

import datetime # импорт стандартного модуля для работы с датой и временем

dateNow = datetime.datetime.now() # создание переменной типа даты и времени, которая будет содержать текущее время

print("Тип:", type(dateNow)) # вывод в консольный терминал типа переменной
print("Значение:", dateNow.__str__()) # вывод в консольный терминал значения переменной
print("Репрезентация:", dateNow.__repr__()) # вывод в консольный терминал репрезентации переменной

Консольный вывод этого примера будет содержать дополнительную информацию о переменной:

Тип: <class 'datetime.datetime'>
Значение: 2024-12-20 05:31:58.749833
Репрезентация: datetime.datetime(2024, 12, 20, 5, 31, 58, 749833)

На примере библиотечного класса (в отличие от встроенных типов) хорошо видно, насколько сильно значение переменной (__str__()) может отличатся от ее репрезентации (__repr__()), которая может быть выполнена в качестве выражения Python.

Благодаря такому поведению показанный код можно усложнить, добавив операцию с уже знакомой функцией eval(), создающую копию оригинальной переменной в данном конкретном случае:

import datetime

dateNow = datetime.datetime.now()

print("Тип:", type(dateNow))
print("Значение:", dateNow.__str__())
print("Репрезентация:", dateNow.__repr__())

print("") # вывод в консольный терминал пустой строки для визуального разделения двух других выводов

dateNowSecond = eval(dateNow.__repr__()) # создаем копию переменной времени на основе репрезентации оригинальной переменной

print("Тип:", type(dateNowSecond))
print("Значение:", dateNowSecond.__str__())
print("Репрезентация:", dateNowSecond.__repr__())

Вывод в консольный терминал при запуске этого примера будет следующим:

Тип: <class 'datetime.datetime'>
Значение: 2024-12-20 11:52:47.243953
Репрезентация: datetime.datetime(2024, 12, 20, 11, 52, 47, 243953)

Тип: <class 'datetime.datetime'>
Значение: 2024-12-20 11:52:47.243953
Репрезентация: datetime.datetime(2024, 12, 20, 11, 52, 47, 243953)

Таким образом, на основе строковой репрезентации оригинальной переменной класса (а не базового типа данных), полученной с помощью метода __repr__(), была создана ее точная копия через функцию eval().

Аналогично примерам с базовыми типами данных, такого результата нельзя достигнуть с помощью метода __str__():

import datetime

dateNow = datetime.datetime.now()

print("Тип:", type(dateNow))
print("Значение:", dateNow.__str__())
print("Репрезентация:", dateNow.__repr__())

print("")

dateNowSecond = eval(dateNow.__str__()) # создаем копию переменной времени на основе значения оригинальной переменной

print("Тип:", type(dateNowSecond))
print("Значение:", dateNowSecond.__str__())
print("Репрезентация:", dateNowSecond.__repr__())

После запуска этого примера в консольном терминале возникнет ошибка:

Тип: <class 'datetime.datetime'>
Значение: 2024-12-20 21:32:20.088363
Репрезентация: datetime.datetime(2024, 12, 20, 21, 32, 20, 88363)

Traceback (most recent call last):
  File "/home/jdoodle.py", line 11, in <module>
    dateNowSecond = eval(dateNow.__str__()) # создаем копию переменной времени на основе значения оригинальной переменной
                    ^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1
    2024-12-20 21:32:20.088363
               ^^
SyntaxError: invalid syntax

Это происходит потому, что эта запись:

dateNowSecond = eval(dateNow.__str__())

Неявно преобразуется в эту:

dateNowSecond = 2024-12-20 21:32:20.088363

Разумеется, такое выражение некорректно в языке Python.

Как минимум в показанном примере выполняемый внутри функции eval() код можно поместить в одинарные кавычки:

import datetime

dateNow = datetime.datetime.now()

print("Тип:", type(dateNow))
print("Значение:", dateNow.__str__())
print("Репрезентация:", dateNow.__repr__())

print("")

dateNowSecond = eval("'" + dateNow.__str__() + "'") # выражение обернуто в одинарные кавычки

print("Тип:", type(dateNowSecond))
print("Значение:", dateNowSecond.__str__())
print("Репрезентация:", dateNowSecond.__repr__())

Это позволит избежать ошибки, но исказит требуемое поведение программы. Консольный вывод будет таким:

Тип: <class 'datetime.datetime'>
Значение: 2024-12-20 21:37:59.112795
Репрезентация: datetime.datetime(2024, 12, 20, 21, 37, 59, 112795)

Тип: <class 'str'>
Значение: 2024-12-20 21:37:59.112795
Репрезентация: '2024-12-20 21:37:59.112795'

Как видно, была создана обычная строковая переменная, содержащая текстовую информацию о дате и времени.

Функции str() и repr()

Каждый раз, когда необходимо получить строковое представление значения переменной или строковую репрезентацию переменной, вызываются методы __str__() и __repr__().

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

someVariable = "Здесь притаилась какая-то строка."

print(someVariable.__str__())
print(someVariable.__repr__())

Такая запись выглядит некрасивой, нелаконичной и сложной. К тому же явное обращение к dunder-методам нельзя назвать хорошей практикой.

Поэтому в языке Python есть специальные функции, принимающие в качестве аргумента переменную и вызывающие ее методы __str__() или __repr__(). Они имеют похожие название — str() и repr() соответственно:

import datetime

dateNow = datetime.datetime.now()

print("Значение (__str__()):", dateNow.__str__())
print("Репрезентация (__repr__()):", dateNow.__repr__())

print("")

print("Значение (str()):", str(dateNow))
print("Репрезентация (repr()):", repr(dateNow))

Консольный вывод этого примера будет таким:

Значение (__str__()): 2024-12-20 21:53:03.167788
Репрезентация (__repr__()): datetime.datetime(2024, 12, 20, 21, 53, 3, 167788)

Значение (str()): 2024-12-20 21:53:03.167788
Репрезентация (repr()): datetime.datetime(2024, 12, 20, 21, 53, 3, 167788)

Как видно, результаты __str__() и str(), ровно как __repr__() и repr(), полностью совпадают.

Переопределение __str__() и __repr__()

В каждом новом классе, созданном пользователем, язык Python разрешает переопределять методы __str__() и __repr__(), получая уникальные значение и репрезентацию переменной.

При этом важно понимать, что в пользовательских классах Python автоматически не формирует возвращаемые значения этих методов:

class Car:
	model = 'Tesla'
	version = 2

	def __init__(self, model, version):
		self.model = model
		self.version = version

someCar = Car("Cybertruck", 1)

print(str(someCar)) # значение переменной
print(repr(someCar)) # репрезентация переменной

Консольный вывод этого примера будет таким:

<__main__.Car object at 0x7acf8c5c5580>
<__main__.Car object at 0x7acf8c5c5580>

По умолчанию Python возвращает представление объекта во внутреннем формате. При этом, если в классе не определен метод __str__(), то вместо него вызывается __repr__().

Поэтому хорошей практикой будет переопределение методов __str__() и __repr__() для каждого нового пользовательского класса:

class Car:
	model = 'Tesla'
	version = 2

	def __init__(self, model, version):
		self.model = model
		self.version = version

	def __str__(self):
		return str(self.model + " " + str(self.version) + ".0.0v")

	def __repr__(self):
		return "Car(" + repr(self.model) + ", " + str(self.version) + ")"

someCar = Car("Cybertruck", 1)
someOtherCar = eval(repr(someCar))

print(type(someCar))
print(type(someOtherCar))

print("")

print(str(someCar))
print(repr(someCar))

print("")

print(str(someOtherCar))
print(repr(someOtherCar))

После запуска этого примера в консольном терминале появится вот такой вывод:

<class '__main__.Car'>
<class '__main__.Car'>

Cybertruck 1.0.0v
Car('Cybertruck', 1)

Cybertruck 1.0.0v
Car('Cybertruck', 1)

Таким образом, благодаря переопределенным методам __str__() и __repr__() была создана точная копия оригинальной переменной пользовательского класса.

Надежные VDS/VPS для ваших Python-проектов

Заключение

Методы __str__() и __repr__(), являющиеся частью созданных переменных, возвращают их значения в различном виде:

  • Метод __str__() возвращает чистое значение.

  • Метод __repr__() возвращает репрезентируемое значение.

Именно поэтому результат метода __repr__() можно использовать в качестве выражения Python в функции eval() для создания точной копии оригинальной переменной.

При этом в каждом пользовательском классе методы __str__() и __repr__() можно (и нужно) переопределять.

Впоследствии эти методы могут быть вызваны с помощью коротких функций str() и repr().

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
28 декабря 2024 г.
63
12 минут чтения
Средний рейтинг статьи: 5
Пока нет комментариев