Отображение класса при выводе данных

Раздел: Основы Python -> Операции вывода

Вывод класса в Python

Эффективное решение: переопределение метода __str__

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

class Book:
    def __init__(self, title, author, year):
        self.title = title
        self.author = author
        self.year = year

    def __str__(self):
        return f"'{self.title}' by {self.author}, published in {self.year}"

book = Book("1984", "George Orwell", 1949)
print(book)

Python вывод (вывод данных на экран в python)

'1984' by George Orwell, published in 1949

вывод класса python (вывод класса в python)

Без переопределения __str__ вывод был бы неинформативным: <__main__.Book object at 0x...>. Переопределение метода решает эту проблему и делает отладку и логирование удобнее.

Типичные ошибки:

  • Метод __str__ возвращает не строку, а другой тип (например, число) - возникает ошибка TypeError.
  • Если внутри __str__ происходит обращение к атрибуту, который не определён, возникает AttributeError. Рекомендуется проверять наличие атрибутов через getattr() или гарантировать их существование.
  • При циклических ссылках (объект A содержит ссылку на B, а B - на A) вызов __str__ может привести к бесконечной рекурсии. Решение - ограничить глубину вывода или использовать специализированные инструменты, такие как reprlib.

Как получить отладочное представление объекта?

Для разработчиков, нуждающихся в однозначном представлении, которое, по возможности, можно использовать для восстановления объекта, переопределяется метод __repr__. В отличие от __str__, он должен содержать всю информацию, необходимую для создания копии объекта.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 4)
print(repr(p))   # Вывод через repr()
print(p)         # По умолчанию вызовет __repr__, если __str__ не определён

форматы вывода python (форматы вывода в python)

Point(3, 4)
Point(3, 4)

Проблема: Если __repr__ не возвращает строку, которую можно интерпретировать как корректное выражение Python, теряется цель отладочного представления. Желательно, чтобы строка была валидным кодом для воссоздания объекта.

Как вывести только отдельные атрибуты без изменения класса?

Если нет возможности изменить класс (например, библиотечный класс), можно использовать f-строки или метод format() при выводе, обращаясь к атрибутам напрямую.

class Car:
    def __init__(self, model, year):
        self.model = model
        self.year = year

car = Car("Tesla Model 3", 2023)
print(f"Модель: {car.model}, год: {car.year}")
print("Модель: {0.model}, год: {0.year}".format(car))
Модель: Tesla Model 3, год: 2023
Модель: Tesla Model 3, год: 2023

Ошибка: Если атрибут отсутствует, возникнет AttributeError. Рекомендуется использовать getattr() с значением по умолчанию.

Как настроить формат вывода с помощью format() и спецификаторов?

Метод __format__ позволяет определить, как объект будет отображаться при использовании f-строк, format() и даже встроенного str.format().

import math

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __format__(self, spec):
        if spec == 'polar':
            r = math.hypot(self.x, self.y)
            phi = math.atan2(self.y, self.x)
            return f'({r:.2f} ∠ {phi:.2f} rad)'
        return f'(x={self.x}, y={self.y})'

v = Vector(3, 4)
print(f'{v}')
print(f'{v:polar}')
(x=3, y=4)
(5.00 ∠ 0.93 rad)

Если спецификатор формата не распознан, можно вернуть стандартное представление через str(self) или repr(self).

Как узнать тип (класс) объекта при отладке?

Функция type() возвращает объект класса, который можно вывести как строку. Чаще всего это используется для логирования или проверки типа.

class Dog:
    pass

d = Dog()
print(type(d))
print(type(d).__name__)
<class '__main__.Dog'>
Dog

Сравнение с isinstance() даёт логический результат, а type() - сам класс. Это может быть неочевидно для новичков.

Как получить полный список атрибутов объекта и их значений?

Функция vars() (без аргументов - локальные переменные, с объектом - его словарь __dict__) и dir() (список имён) помогают вывести содержимое объекта.

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

e = Employee("Alice", 60000)
print(vars(e))
print([attr for attr in dir(e) if not attr.startswith('_')])
{'name': 'Alice', 'salary': 60000}
['name', 'salary']

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

Как быстро получить читаемый вывод для классов-контейнеров?

Декоратор @dataclass из модуля dataclasses автоматически генерирует методы __init__, __repr__ (и другие), что избавляет от ручного написания кода для вывода.

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    active: bool = True

u = User(1, "John", False)
print(u)
print(repr(u))
User(id=1, name='John', active=False)
User(id=1, name='John', active=False)

По умолчанию dataclass генерирует __repr__, но не __str__ (если не задан order=True или не переопределён). Можно передать repr=False, если вывод не нужен.

Расширенные примеры вывода класса

Кастомный __str__ с форматированием и обработкой None

Пример
class Contact:
    def __init__(self, name, phone, email=None):
        self.name = name
        self.phone = phone
        self.email = email

    def __str__(self):
        parts = [f"{self.name}: phone {self.phone}"]
        if self.email:
            parts.append(f", email {self.email}")
        return ''.join(parts)

c = Contact("Anna", "+7-123-456-78-90")
print(c)
c2 = Contact("Bob", "+1-555-1234", "bob@example.com")
print(c2)
Anna: phone +7-123-456-78-90
Bob: phone +1-555-1234, email bob@example.com

__repr__ с возможностью eval (восстановление объекта)

Пример
class Fraction:
    def __init__(self, numerator, denominator):
        self.numer = numerator
        self.denom = denominator

    def __repr__(self):
        return f'Fraction({self.numer}, {self.denom})'

f = Fraction(3, 4)
f_repr = repr(f)
print(f_repr)
f_new = eval(f_repr)  # Восстанавливаем объект
print(f_new.numer, f_new.denom)
Fraction(3, 4)
3 4

Внимание: eval() может быть опасен, если строка получена из ненадёжного источника. В отладочных целях - допустимо.

Переопределение __format__ для сложной логики форматирования

Пример
from datetime import datetime

class Event:
    def __init__(self, name, date):
        self.name = name
        self.date = date  # datetime объект

    def __format__(self, spec):
        if spec == 'short':
            return f'{self.name} ({self.date:%d.%m.%Y})'
        return f'Event "{self.name}" on {self.date:%Y-%m-%d %H:%M}'

e = Event("Conference", datetime(2025, 3, 15, 9, 0))
print(f'{e}')
print(f'{e:short}')
Event "Conference" on 2025-03-15 09:00
Conference (15.03.2025)

Использование dataclass с дополнительными методами вывода

Пример
from dataclasses import dataclass, field

@dataclass
class Employee:
    name: str
    position: str
    salary: float = field(repr=False)  # скрыть зарплату в repr

    def __str__(self):
        return f'{self.name} ({self.position})'

e = Employee("Eve", "Engineer", 85000)
print(e)
print(repr(e))
Eve (Engineer)
Employee(name='Eve', position='Engineer')

Вывод с помощью модуля pprint для сложных вложенных объектов

Пример
from pprint import pprint

class Node:
    def __init__(self, value, children=None):
        self.value = value
        self.children = children or []

    def __repr__(self):
        return f'Node({self.value!r}, {self.children!r})'

tree = Node(1, [Node(2, [Node(4), Node(5)]), Node(3)])
pprint(tree)
Node(1, [Node(2, [Node(4, []), Node(5, [])]), Node(3, [])])

Избегание рекурсии при выводе объектов с циклическими ссылками

Пример
import reprlib

class LinkedListNode:
    def __init__(self, value, next_node=None):
        self.value = value
        self.next = next_node

    def __repr__(self):
        return f'LinkedListNode({self.value!r})'

# Создаём цикл: node3 -> node1
node1 = LinkedListNode(1)
node2 = LinkedListNode(2, node1)
node3 = LinkedListNode(3, node2)
node1.next = node3

# reprlib.Repr позволяет ограничить глубину
r = reprlib.Repr()
r.maxlevel = 3
print(r.repr(node1))
LinkedListNode(1)

Если использовать стандартный repr(), будет RecursionError. reprlib помогает безопасно вывести объект.

Вывод класса в Python - comments

En
вывод класса python (python)