📖 Теория: атрибуты объекта и класса

⚡ Кратко

Атрибут класса создаётся в теле класса и общий для всех экземпляров. Атрибут объекта создаётся в __init__ через self. При чтении через объект Python сначала ищет атрибут в экземпляре, затем в классе. При присваивании через объект всегда создаётся атрибут экземпляра.

  • @classmethod работает с классом и получает cls.
  • @staticmethod — обычная функция внутри класса.
  • __str__ возвращает читаемую строку, __repr__ — техническое представление.

Атрибуты класса

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

class Book:
    library_name: str = "Central Library"  # атрибут класса

    def __init__(self, title: str, author: str) -> None:
        self.title = title    # атрибут объекта
        self.author = author  # атрибут объекта


book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")

print(Book.library_name)   # Central Library — через класс
print(book1.library_name)  # Central Library — через объект
print(book2.library_name)  # Central Library
💡 На заметку: атрибуты класса обычно используют для настроек, категорий, счётчиков и других значений, которые одинаковы для всех объектов.

Доступ к атрибутам

Поля класса можно читать и изменять как внутри класса, так и снаружи. Но поведение при изменении отличается в зависимости от того, обращаемся ли мы через класс или через объект.

Чтение поля класса

class Book:
    library_name: str = "Central Library"

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author

    def show_library(self) -> None:
        print(self.library_name)  # доступ через self
        print(Book.library_name)  # доступ через имя класса


print(Book.library_name)  # через класс
book = Book("1984", "George Orwell")
print(book.library_name)  # через объект
book.show_library()       # изнутри метода

Изменение через класс

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

class Book:
    library_name: str = "Central Library"

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author


book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")

Book.library_name = "City Library"
print(book1.library_name)  # City Library
print(book2.library_name)  # City Library

Изменение через объект создаёт атрибут объекта

При присваивании через объект Python создаёт новый атрибут в экземпляре. Атрибут класса при этом не меняется.

class Book:
    library_name: str = "Central Library"

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author


book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")

book1.library_name = "Private Shelf"  # создаёт атрибут объекта
print(book1.library_name)  # Private Shelf
print(book2.library_name)  # Central Library — из класса
print(Book.library_name)   # Central Library

Механизм поиска атрибутов

Когда мы обращаемся к полю через объект (obj.attr), Python сначала ищет этот атрибут у самого объекта. Если не находит — продолжает поиск в классе, из которого объект был создан.

⚠️ Важно: присваивание obj.attr = value всегда создаёт или изменяет атрибут объекта, если только attr не является дескриптором с методом __set__ (например, property). В рамках этого урока думайте так: запись — в объект, чтение — сначала объект, потом класс.

Магическое поле __dict__

__dict__ — это словарь, в котором хранятся явно заданные атрибуты объекта или класса. У объекта в нём только собственные атрибуты, у класса — атрибуты класса и методы.

class Book:
    library_name: str = "Central Library"

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author


book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")
book1.library_name = "Private Shelf"

print(book1.__dict__)  # {'title': '1984', 'author': 'George Orwell', 'library_name': 'Private Shelf'}
print(book2.__dict__)  # {'title': 'Brave New World', 'author': 'Aldous Huxley'}
print(Book.__dict__.keys())  # ключи атрибутов и методов класса
Проверить по документации: словарь __dict__ и встроенная функция vars() описаны в разделе object.__dict__ документации Python.

Добавление атрибутов извне

В Python можно добавить атрибут к классу или объекту в любой момент. Но так делать не рекомендуется: такие поля неочевидны, их сложно отслеживать, и они могут привести к ошибкам.

class Book:
    library_name: str = "Central Library"

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author


book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")

book1.book_color = "black"        # атрибут объекта извне
Book.default_language = "eng"     # атрибут класса извне

print(book1.book_color)       # black
print(book1.default_language) # eng
# print(book2.book_color)     # AttributeError

Поля объекта по умолчанию

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

class Book:
    def __init__(self, title: str, author: str, language: str = "English") -> None:
        self.title = title
        self.author = author
        self.language = language


book1 = Book("1984", "George Orwell")
book2 = Book("Cien años de soledad", "Gabriel García Márquez", "Spanish")

print(book1.language)  # English
print(book2.language)  # Spanish

Классовые методы

Классовый метод работает не с конкретным объектом, а с самим классом. Вместо self он получает cls — ссылку на класс, из которого вызван. Для объявления используется декоратор @classmethod.

class Book:
    total_books: int = 0  # атрибут класса

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author
        Book.total_books += 1

    @classmethod
    def show_total(cls) -> None:
        print(f"Total books: {cls.total_books}")


Book.show_total()              # Total books: 0
book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")
Book.show_total()              # Total books: 2
book1.show_total()             # Total books: 2 — можно вызвать и через объект

Альтернативный конструктор

Классовые методы часто используют для создания объектов из других форматов данных.

class Book:
    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author

    @classmethod
    def from_string(cls, data: str) -> "Book":
        title, author = data.split(" - ")
        return cls(title, author)


book = Book.from_string("1984 - George Orwell")
print(book.title)   # 1984
print(book.author)  # George Orwell

Статические методы

Статический метод не зависит ни от объекта, ни от класса. Это обычная функция, логически связанная с классом. Для объявления используется декоратор @staticmethod.

class Book:
    def __init__(self, title: str) -> None:
        self.title = title

    @staticmethod
    def is_title_valid(title: str) -> bool:
        return isinstance(title, str) and len(title) > 0


print(Book.is_title_valid("1984"))  # True
print(Book.is_title_valid(""))      # False
print(Book.is_title_valid(123))     # False

Магические методы __str__ и __repr__

По умолчанию print(obj) выводит техническую информацию об объекте. Чтобы получить читаемый вывод, определяют метод __str__. Для отладки и коллекций используют __repr__.

class Book:
    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author

    def __str__(self) -> str:
        return f"{self.title} by {self.author}"

    def __repr__(self) -> str:
        return f"Book(title={self.title!r}, author={self.author!r})"


book = Book("1984", "George Orwell")
print(book)        # 1984 by George Orwell
print(str(book))   # 1984 by George Orwell
print(repr(book))  # Book(title='1984', author='George Orwell')
💡 На заметку: __str__ должен вернуть строку, а не печатать её. Если __str__ не определён, Python использует __repr__.
Проверить по документации: особенности __str__ и __repr__, а также рекомендации по их реализации, описаны в Data model.
Проверить по документации: современные рекомендации по стилю кода (PEP 8) и аннотации типов выходят за рамки исходного материала; подробнее см. PEP 8 и typing.