Основы синтаксиса Python в ООП
Что такое self? Как реализовать метод объекта?
подробнее
self — ссылка на текущий экземпляр класса. Позволяет обращаться к атрибутам и методам объекта (а также класса).
Реализация метода объекта:
class MyClass:
def __init__(self, value):
self.value = value
def my_method(self, x):
return self.value + x
Что такое cls? Как реализовать метод класса?
подробнее
cls — ссылка на сам класс (не экземпляр). Используется в методах класса.
Реализация метода класса:
class MyClass:
class_attr = 10
@classmethod
def my_classmethod(cls, x):
return cls.class_attr + x
@classmethod
def create_instance(cls, value):
return cls(value)
@classmethod — декоратор для создания методов класса.
Особенности:
- Метод принимает
clsвместоself - Вызывается через класс или экземпляр
- Метод класса не имеет доступа до аттрибутов объекта (экземпляра класса)
Что такое статический метод?
подробнее
Статический метод — метод, который не имеет доступа ни к self, ни к cls.
Особенности:
- Декоратор
@staticmethod - Не принимает автоматических параметров
- Не имеет доступа к атрибутам класса или экземпляра
- По сути обычная функция, принадлежащая классу
- Можно вызывать как через класс, так и через объект (экземпляр класса)
Используется для:
- Вспомогательных функций
- Логики, связанной с классом, но не зависящей от его состояния
Пример:
class MyClass:
@staticmethod
def utility_function(x, y):
return x + y
result = MyClass.utility_function(5, 3) # вызов через класс
Разница между __new__ и __init__?
подробнее
__new__— создает новый экземпляр класса и возвращает его__init__— инициализирует уже созданный экземпляр
Другими словами: __new__ отвечает за создание объекта, а __init__ — за его настройку.
Подробнее:
__new__(магический метод создания)- Статический метод (хотя и не требует декоратора @staticmethod)
- Первым аргументом принимает класс (cls)
- Должен вернуть экземпляр класса (обычно вызывая super().new(cls))
- Вызывается первым при создании объекта
- Используется редко, в основном для метапрограммирования, singleton’ов, фабричных методов, immutable объектов
__new__должен вернуть экземпляр — иначе__init__не будет вызван
__init__(конструктор инициализации)- Метод экземпляра
- Первым аргументом принимает self (уже созданный экземпляр)
- Ничего не возвращает (всегда None)
- Вызывается вторым после
__new__ - Используется часто для настройки объекта
Примеры:
# Пример 1: Нормальное использование
class Person:
def __new__(cls, name, age):
print("Создание экземпляра Person")
instance = super().__new__(cls)
return instance
def __init__(self, name, age):
print("Инициализация Person")
self.name = name
self.age = age
person = Person("Алиса", 25)
# Вывод:
# Создание экземпляра Person
# Инициализация Person
# Пример 2: Singleton (одиночка)
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
print("Создан новый экземпляр")
else:
print("Возвращен существующий экземпляр")
return cls._instance
def __init__(self):
if not hasattr(self, 'initialized'):
self.initialized = True
print("Инициализация Singleton")
# Использование
s1 = Singleton() # Создан новый экземпляр
s2 = Singleton() # Возвращен существующий экземпляр
print(s1 is s2) # True
# Пример 3: Immutable объекты
class ImmutablePoint:
def __new__(cls, x, y):
instance = super().__new__(cls)
# Устанавливаем атрибуты до __init__
instance._x = x
instance._y = y
return instance
def __init__(self, x, y):
# Нельзя изменить атрибуты, они уже установлены
pass
@property
def x(self):
return self._x
@property
def y(self):
return self._y
point = ImmutablePoint(1, 2)
# point.x = 5 # Ошибка! Нельзя изменить
# Пример 4: Фабрика объектов
class Animal:
def __new__(cls, animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
else:
return super().__new__(cls)
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Гав!"
class Cat(Animal):
def speak(self):
return "Мяу!"
# Использование
dog = Animal("dog")
cat = Animal("cat")
print(dog.speak()) # Гав!
print(cat.speak()) # Мяу!
Разница между __str__ и __repr__?
подробнее
__str__— возвращает читаемое строковое представление объекта для конечного пользователя. Используется функциейstr()иprint()__repr__— возвращает однозначное строковое представление объекта для разработчиков (в идеале, код, который можно выполнить для воссоздания объекта). Используется функциейrepr()и в интерактивной оболочке
Пример __repr__:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person('{self.name}', {self.age})"
person = Person("Алиса", 25)
print(repr(person)) # Person('Алиса', 25)
# В интерактивной оболочке:
# >>> person
# Person('Алиса', 25)
Порядок приоритета:
- Если определен
__str__— используется дляprint()иstr() - Если
__str__не определен, но есть__repr__— используется__repr__ - Если определен
__repr__— используется дляrepr()и в интерактивной оболочке -
Если ничего не определено — используется стандартное представление
class Example: def __init__(self, value): self.value = value obj = Example(42) print(obj) # <__main__.Example object at 0x...> print(repr(obj)) # <__main__.Example object at 0x...>
Как в python реализуются public, private и protected методы и аттрибуты?
подробнее
В python нет модификаторов доступа и всё по умолчанию public. Вместо модификаторов Python использует соглашения об именовании для обозначения уровня доступа.
- Public (Публичные)
- Доступ: Отовсюду — изнутри и снаружи класса
- Обозначение: Обычные имена без подчеркиваний
- Использование: Для нормального взаимодействия с объектом
- Protected (Защищенные)
- Доступ: Внутри класса и его подклассов
- Обозначение: Одно подчеркивание в начале _attribute
- Соглашение: “Защищенный” элемент, не рекомендуется использовать извне
- Важно: Доступ всё ещё возможен, но это считается плохой практикой
class MyClass: def __init__(self): self._protected_attr = "Защищенный атрибут" def _protected_method(self): return "Защищенный метод" class SubClass(MyClass): def access_protected(self): # Доступ из подкласса разрешен return self._protected_attr obj = MyClass() print(obj._protected_attr) # Работает, но не рекомендуется print(obj._protected_method()) # Работает, но не рекомендуется - Private (Приватные)
- Доступ: Только внутри самого класса
- Обозначение: Два подчеркивания в начале __attribute
- Механизм: Name mangling (искажение имен)
- Важно: Python изменяет имя атрибута для затруднения доступа (Name Mangling)
__attribute→_ClassName__attribute
class MyClass: def __init__(self): self.__private_attr = "Приватный атрибут" def __private_method(self): return "Приватный метод" def access_private(self): # Доступ внутри класса разрешен return self.__private_attr obj = MyClass() # print(obj.__private_attr) # Ошибка! AttributeError # print(obj.__private_method()) # Ошибка! AttributeError # Но доступ всё же возможен через name mangling: print(obj._MyClass__private_attr) # Работает, но не рекомендуется
Рекомендации:
- Используйте public для нормального API класса
- Используйте protected для внутренних методов, которые могут быть полезны подклассам
- Используйте private для действительно внутренней реализации
- Следуйте соглашениям — уважайте договоренности сообщества
- Не нарушайте инкапсуляцию — не используйте name mangling для доступа к приватным атрибутам
Зачем используется super()?
подробнее
super() — это встроенная функция, которая позволяет получить доступ к методам и атрибутам родительского класса из дочернего класса.
Она используется для:
- Вызова методов родителя
- Получения доступо до аттрибутов родителя
- Избежания дублирования кода
- Поддержки множественного наследования
- Правильного разрешения методов (MRO)
super() возвращает специальный объект-прокси, который предоставляет доступ к атрибутам класса-родителя. Это не экземпляр и не сам класс, а промежуточный объект, который делегирует доступ к родительским атрибутам.
class Parent:
def method(self):
return "Parent method"
class_attr = "Parent class attribute"
class Child(Parent):
def test_super(self):
s = super() # Возвращает объект super
print(type(s)) # <class 'super'>
print(s) # <super: Child, <Child object>>
return s
child = Child()
proxy = child.test_super()
# Вывод:
# <class 'super'>
# <super: Child, <__main__.Child object at 0x...>>
Декораторы @property, @attr.setter, @attr.deleter
подробнее
- @property — превращает метод в свойство (getter)
- @attr.setter — сеттер для свойства
- @attr.deleter — делитер для свойства
class MyClass:
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
if new_value > 0:
self._value = new_value
@value.deleter
def value(self):
del self._value
# Использование
obj = MyClass(10)
print(obj.value) # getter
obj.value = 20 # setter
del obj.value # deleter
Что такое дескрипторы?
подробнее
Дескриптор - когда у класса есть аттрибут, который реализует хотя бы 1 из методов:
__get__(),__set__(),__delete__().
Проще говоря: это умный посредник, который перехватывает обращение к атрибуту и вместо обычного чтения/записи выполняет кастомный код.
Важное ограничение: Дескрипторы работают, только если они определены на уровне класса, а не внутри метода__init__()экземпляра.
Дополнения:
@property- это встроенный готовый дескриптор.- Если реализован
__set__(), дескриптор всегда «сильнее» обычного значения вself.__dict__.
Протокол дескриптора:
__get__(self, instance, owner)- вызывается при чтении атрибута.__set__(self, instance, value)- вызывается при присваивании значения.__delete__(self, instance)- вызывается при удалении атрибута.__set_name__(self, owner, name)- не обязательный метод, который вызывается в момент создания класса для каждого дескриптора, что позволяет дескриптору знать имя атрибута, к которому он привязан.
Пример:
class NonNegative:
def __set_name__(self, owner, name):
# Сохраняем имя атрибута, чтобы знать, где лежат данные
self.name = "_" + name
def __get__(self, instance, owner):
# instance — это объект (напр. user), owner — класс (User)
return getattr(instance, self.name)
def __set__(self, instance, value):
if value < 0:
raise ValueError(f"Поле {self.name} не может быть отрицательным!")
setattr(instance, self.name, value)
class User:
age = NonNegative() # дескриптор
# Тест
user = User()
user.age = 25 # __set__ -> Ок
print(user.age)
user.age = -5 # __set__ -> ValueError