Атрибуты и методы в Python
В прошлом уроке мы видели атрибуты (self.name) и методы (def greet(self)). Сейчас копнём глубже: бывают ещё атрибуты класса (общие для всех экземпляров), методы делятся на три вида (обычные, классовые, статические), а в __init__ живёт классическая ловушка с изменяемыми значениями по умолчанию, которая ловит даже опытных разработчиков.
Типы атрибутов
- Атрибуты экземпляра: уникальны для каждого объекта.
- Атрибуты класса: общие для всех экземпляров.
Атрибуты экземпляра
Атрибуты экземпляра: переменные, которые хранят данные конкретного объекта. Обычно создаются в __init__ через self.имя = ....
Python 3.13class 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.13class 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}")Алексей, Гимназия №5print(f"{student2.name}, {student2.school}")Екатерина, Гимназия №5

Когда какой использовать
- Атрибуты экземпляра: для данных, которые различаются между объектами (имя, возраст, id).
- Атрибуты класса: для:
- Констант и значений по умолчанию;
- Данных, общих для всех экземпляров (например, имя школы для всех студентов);
- Счётчиков уровня класса (например, сколько объектов уже создано).
Ловушка с изменяемыми значениями по умолчанию
Классическая засада, которая ловит даже опытных Python-разработчиков. Допустим, мы хотим, чтобы у студента по умолчанию был пустой список оценок. Кажется логичным написать так:
Python 3.13class 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.13class 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 есть несколько видов методов:
- Обычные методы (методы экземпляра): работают с конкретным объектом через self.
- Методы класса: работают с классом в целом, получают cls.
- Статические методы: не получают ни self, ни cls.
- Специальные методы: имеют особое значение для Python (например, __init__, __str__).
Обычные методы
Обычные методы: функции внутри класса, принимающие self первым параметром. Через self обращаются к атрибутам объекта и вызывают другие методы.
Python 3.13class 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()}")Площадь: 15print(f"Периметр: {rect.perimeter()}")Периметр: 16
Методы класса
Методы класса: получают сам класс первым параметром (обычно называется cls), а не экземпляр. Декорируются через @classmethod. Часто используются как альтернативные конструкторы.
Python 3.13class 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.13class 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 простым числом: Trueprint(f"Является ли 10 простым числом: {MathUtils.is_prime(10)}")Является ли 10 простым числом: False
Специальные методы
Специальные методы: методы с двойными подчёркиваниями в имени (__init__, __str__, __add__, __eq__). Python вызывает их сам при определённых операциях: создание объекта, печать, сложение, сравнение.
Python 3.13class 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__Falseprint(v1 == v3) # вызывает __eq__True
Без __eq__ сравнение v1 == v3 вернуло бы False, даже если координаты совпадают: Python по умолчанию сравнивает объекты по идентичности (один ли это объект в памяти), а не по содержимому. Определив __eq__, мы говорим: «считай эти объекты равными, если совпадают координаты».
Часто используемые специальные методы
Вот некоторые из наиболее часто используемых специальных методов в Python:
Проверка понимания
Какой декоратор используется для создания методов класса в Python?
В следующем уроке возьмём четыре принципа ООП по одному. Начнём с наследования: как один класс продолжает другой, переиспользуя его атрибуты и методы.
