Атрибуты и методы в Python

В прошлом уроке мы видели атрибуты (self.name) и методы (def greet(self)). Сейчас копнём глубже: бывают ещё атрибуты класса (общие для всех экземпляров), методы делятся на три вида (обычные, классовые, статические), а в __init__ живёт классическая ловушка с изменяемыми значениями по умолчанию, которая ловит даже опытных разработчиков.

Типы атрибутов

  1. Атрибуты экземпляра: уникальны для каждого объекта.
  2. Атрибуты класса: общие для всех экземпляров.

Атрибуты экземпляра

Атрибуты экземпляра: переменные, которые хранят данные конкретного объекта. Обычно создаются в __init__ через self.имя = ....

Python 3.13
class Person:
    def __init__(self, name, age):
        # Атрибуты экземпляра
        self.name = name
        self.age = age

    def birthday(self):
        self.age += 1
        return f"{self.name} теперь {self.age} лет!"

# Создаём два разных объекта
person1 = Person("Анна", 25)
person2 = Person("Иван", 30)

# У каждого объекта свои значения атрибутов
print(f"{person1.name}: {person1.age} лет")
Анна: 25 лет
# Изменение атрибута одного объекта не влияет на другой
print(person1.birthday())
Анна теперь 26 лет!
print(f"{person2.name}: {person2.age} лет")  # Возраст не изменился
Иван: 30 лет

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

Атрибуты класса: переменные, объявленные прямо в теле класса (вне методов). Они общие для всех экземпляров.

Python 3.13
class Student:
    # Атрибут класса: общий для всех экземпляров
    school = "Школа №1"

    def __init__(self, name):
        # Атрибут экземпляра: у каждого студента свой
        self.name = name

# Создаём студентов
student1 = Student("Алексей")
student2 = Student("Екатерина")

# Имя у каждого своё, школа одна
print(f"{student1.name}, {student1.school}")
Алексей, Школа №1
# Меняем атрибут класса: это видят сразу все экземпляры
Student.school = "Гимназия №5"
print(f"{student1.name}, {student1.school}")
Алексей, Гимназия №5
print(f"{student2.name}, {student2.school}")
Екатерина, Гимназия №5

Иллюстрация: класс Student с атрибутом класса school = "Школа №1" наверху, и три экземпляра (student1, student2, student3) снизу, у каждого свой атрибут name

Когда какой использовать

  • Атрибуты экземпляра: для данных, которые различаются между объектами (имя, возраст, id).
  • Атрибуты класса: для:
    • Констант и значений по умолчанию;
    • Данных, общих для всех экземпляров (например, имя школы для всех студентов);
    • Счётчиков уровня класса (например, сколько объектов уже создано).

Ловушка с изменяемыми значениями по умолчанию

Классическая засада, которая ловит даже опытных Python-разработчиков. Допустим, мы хотим, чтобы у студента по умолчанию был пустой список оценок. Кажется логичным написать так:

Python 3.13
class Student:
    def __init__(self, name, grades=[]):   # выглядит безобидно
        self.name = name
        self.grades = grades

s1 = Student("Анна")
s1.grades.append(5)
print(s1.grades)
[5]
s2 = Student("Иван")
print(s2.grades)   # ожидаем []
[5]

Иван унаследовал оценку Анны, хотя мы ничего ему не добавляли. Почему?

Значения параметров по умолчанию вычисляются один раз, в момент определения функции, а не при каждом вызове. Список [] создан один раз и переиспользуется всеми вызовами Student(...) без аргумента grades. То есть s1.grades и s2.grades указывают на один и тот же список в памяти.

Правильный паттерн: ставить None по умолчанию, а реальный список создавать внутри:

Python 3.13
class Student:
    def __init__(self, name, grades=None):
        self.name = name
        self.grades = grades if grades is not None else []

s1 = Student("Анна")
s1.grades.append(5)
print(s1.grades)
[5]
s2 = Student("Иван")
print(s2.grades)
[]

Теперь каждый объект получает свой собственный пустой список. Тот же приём работает для словарей, множеств и любых других мутабельных значений.

Типы методов

В Python есть несколько видов методов:

  1. Обычные методы (методы экземпляра): работают с конкретным объектом через self.
  2. Методы класса: работают с классом в целом, получают cls.
  3. Статические методы: не получают ни self, ни cls.
  4. Специальные методы: имеют особое значение для Python (например, __init__, __str__).

Обычные методы

Обычные методы: функции внутри класса, принимающие self первым параметром. Через self обращаются к атрибутам объекта и вызывают другие методы.

Python 3.13
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)

# Создаём прямоугольник и вызываем методы
rect = Rectangle(5, 3)
print(f"Площадь: {rect.area()}")
Площадь: 15
print(f"Периметр: {rect.perimeter()}")
Периметр: 16

Методы класса

Методы класса: получают сам класс первым параметром (обычно называется cls), а не экземпляр. Декорируются через @classmethod. Часто используются как альтернативные конструкторы.

Python 3.13
class Date:
    def __init__(self, day, month, year):
        self.day = day
        self.month = month
        self.year = year

    def display(self):
        return f"{self.day:02d}.{self.month:02d}.{self.year}"

    # Метод класса: альтернативный конструктор
    @classmethod
    def from_string(cls, date_string):
        day, month, year = map(int, date_string.split('.'))
        return cls(day, month, year)

# Создаём объект стандартным способом
date1 = Date(15, 6, 2023)
print(date1.display())
15.06.2023
# Создаём объект, используя метод класса
date2 = Date.from_string("25.12.2023")
print(date2.display())
25.12.2023

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

Статические методы: не получают ни self, ни cls. Декорируются через @staticmethod. Используются для вспомогательных функций, логически связанных с классом, но не требующих доступа к его состоянию.

Python 3.13
class MathUtils:
    @staticmethod
    def is_prime(number):
        """Проверяет, является ли число простым"""
        if number < 2:
            return False
        for i in range(2, int(number**0.5) + 1):
            if number % i == 0:
                return False
        return True

# Вызываем статический метод через имя класса
print(f"Является ли 7 простым числом: {MathUtils.is_prime(7)}")
Является ли 7 простым числом: True
print(f"Является ли 10 простым числом: {MathUtils.is_prime(10)}")
Является ли 10 простым числом: False

Специальные методы

Специальные методы: методы с двойными подчёркиваниями в имени (__init__, __str__, __add__, __eq__). Python вызывает их сам при определённых операциях: создание объекта, печать, сложение, сравнение.

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

    # Строковое представление: вызывается через print() и str()
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    # Перегрузка оператора +
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    # Перегрузка оператора ==
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

# Создаём векторы и используем перегруженные операторы
v1 = Vector(3, 4)
v2 = Vector(1, 2)
v3 = Vector(3, 4)

print(f"v1 = {v1}")          # вызывает __str__
v1 = Vector(3, 4)
print(f"v1 + v2 = {v1 + v2}")  # вызывает __add__
v1 + v2 = Vector(4, 6)
print(v1 == v2)              # вызывает __eq__
False
print(v1 == v3)              # вызывает __eq__
True

Без __eq__ сравнение v1 == v3 вернуло бы False, даже если координаты совпадают: Python по умолчанию сравнивает объекты по идентичности (один ли это объект в памяти), а не по содержимому. Определив __eq__, мы говорим: «считай эти объекты равными, если совпадают координаты».

Часто используемые специальные методы

Вот некоторые из наиболее часто используемых специальных методов в Python:

МетодОписаниеПример использования
__init__(self, ...)Конструкторobj = MyClass()
__str__(self)Строковое представление для пользователяprint(obj)
__repr__(self)Строковое представление для разработчикаrepr(obj)
__len__(self)Длина объектаlen(obj)
__getitem__(self, key)Доступ по ключу/индексуobj[key]
__setitem__(self, key, value)Присваивание по ключу/индексуobj[key] = value
__call__(self, ...)Вызов объекта как функцииobj()
__add__(self, other)Оператор +obj + other
__sub__(self, other)Оператор -obj - other
__eq__(self, other)Оператор ==obj == other
__lt__(self, other)Оператор <obj < other

Проверка понимания

Какой декоратор используется для создания методов класса в Python?


В следующем уроке возьмём четыре принципа ООП по одному. Начнём с наследования: как один класс продолжает другой, переиспользуя его атрибуты и методы.