Кодим вместе: Путь к IT-Успеху
530 subscribers
112 photos
1 video
104 links
🌟Всем привет! Я начинающий Python-разработчик, вернувшийся к обучению. Здесь делюсь своими планами и достижениями, чтобы мотивировать вас и себя заниматься программированием каждый день! 🚀
Download Telegram
Хотите так же? Смотрите ниже🙃

Печатать вслепую я начал давно, пришлось этому обучиться в школе, чтобы не отставать от других😬В подробности углубляться не буду, вот что мне помогло:

Для изучения техники печати:

Тренажер Stamina: Проходил еще на sega с клавиатурой). Это изначально поставило мне верно пальцы и дало первый буст в печати. Кстати, если читать текст, там очень забавные фразы предлагают😁

Соло на клавиатуре: После полного прохождения вы будете ненавидеть или любить фамилию Шахиджанян, но уж точно запомните :D). А если серьёзно, то считаю, что это основа основ для слепой 10-ти пальцевой печати!

Codewar: Нет, это не про решение задач😄Относительно новый сайт. Завлекло то, что дает персональные рекомендации по улучшению и гемфикация процесса (но на любителя).

Для практики точности и скорости:

Клавогонки: Куча разных текстов
Monkeytype: Аналогично гонкам, но на англ.
TypingClub: Очень много упражнений для eng. lang.
Еще один сайт: Здесь ссылка именно на тренажер печати на Python.
🔥11❤‍🔥21🤝1
🔄 Итераторы в Python: "магия" __iter__ и __next__

Давайте разберемся, как работают итераторы в классах! Начнем с основы:

Для любого итерируемого объекта (например, список) мы можем создать итератор → с помощью next() читаем элементы последовательно → когда доходим до конца, получаем StopIteration

Пример с range():
 a = iter(range(5))
print(next(a)) # 0


Работает аналогично! 🔄 Вспомнили?

Тогда переходим к классам! 🚀

Честно: сам не очень люблю такие темы (наверно, потому что забываю что-то 😅), но знать НАДО!

Напомнило мне про боль в односвязных и двусвязных списках — уже решил штук 5-6 задач по ним за последнюю главу. Теперь выглядят не так страшно! 💪

Поехали разбираться! Создаем свой итератор — класс FRange (аналог range() для вещественных чисел):
class FRange:
def __init__(self, start=0.0, stop=0.0, step=1.0):
self.start = start
self.stop = stop
self.step = step

Добавляем метод для получения значений:
    def __next__(self):
if self.value + self.step < self.stop:
self.value += self.step
return self.value
raise StopIteration('конец') # 🛑

Используем так (записи эквивалентны):
fr = FRange(0, 2, 0.5)
print(next(fr)) # 0.0
print(fr.__next__()) # 0.5

Важный момент! 🤔

Так
происходит потому что функция next() неявно вызывает магический метод `__next.__` Соответственно, получаем то значение, которое возвращает метод, а объект fr выступает тут в роли итератора (объект, у которого есть магический метод next(), но и не только он).

Пробуем через for:
for x in fr:
print(x)

Получаем: TypeError: 'FRange' object is not iterable
Почему? Не можем создать итератор для fr (не можем вызвать iter()).
Исправляем — добавляем __iter__:
    def __iter__(self):
# self.value = self.start - self.step
return self

Нюанс: инициализацию self.value можно перенести в __iter__, но тогда print(next(fr)) сломается — не найдет self.value!

Итог: двумя методами (__iter__ и __next__) превратили класс в итерируемый объект + "сказали" как надо формировать очередное значение для этого итерируемого объекта. 🎉

В комментах выложу еще один пример - продложение этого.

Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы - в комментарии!

P.S Если что-то не так написал по теории - поправьте)

Еще пример для себя - всё равно это никто не прочитает уже))

Задача:, вам необходимо разработать итератор, который бы перебирал указанные столбцы двумерного списка. Список представляет собой двумерную таблицу из данных:
lst = [[x11, x12, ..., x1N],
[x21, x22, ..., x2N],
...
[xM1, xM2, ..., xMN]
]

Для этого в программе необходимо объявить класс с именем IterColumn, объекты которого создаются командой:
it = IterColumn(lst, column)

где lst - ссылка на двумерный список; column - индекс перебираемого столбца (отсчитывается от 0).
Затем, с объектами класса IterColumn должны быть доступны следующие операции:

it = IterColumn(lst, 1)
for x in it: # последовательный перебор всех элементов в столбцах списка: x12, x22, ..., xM2
print(x)

it_iter = iter(it)
x = next(it_iter)


Решение:
class IterColumn:
def __init__(self, lst, index_column):
self.lst = lst[:]
self.index_column = index_column
self.result = [self.lst[i][self.index_column] for i in range(len(self.lst))]


def __iter__(self): # iter как делать, вариант
for elem in self.result:
yield elem


def __iter__(self):
for row in self.__tables:
yield (
elem.data for elem in row)
🔥2🤔2
Вчера утром с телефона зашёл на Степик. Вижу такую белиберду😑

Сначала ничего не понял, подумал вообще что сплю😂 Как оказалось - нет, даже ущипнул себя, для проверки как в фильмах😁 Я вообще без понятия как описание профиля превратилось в такое и как долго это уже у меня😐

Тут вижу два варианта: либо я ооооочень удачно уснул с телефоном на лице, либо платформа решила надо мной порофлить(теорию заговора никто не отменял)😑
😁5
Заход на ООП немного затянулся🐢
Начал за здравие, а заканчиваю... как говорится.., ну вы поняли😅

Но вот уже добрался до конца очередной главы. Ожидает последнее задание — очередное «испытание», на этот раз "магическое". Ещё даже не открывал, а уже чувствую "лёгкий" трепет

Дело в том, что в этом курсе сложность задач можно измерять в минутах видеоразбора:
• Совсем простые — разбора нет 😊
• Лёгкие — видео до 6 минут 🧐
• Средние — 8-10 минут 😐
• Сложные — от 12+ 😰

Но раз на раз не приходится, могу лёгкую задачу решать пару часов, а сложная улетает меньше чем пол часа. Но чаще всего длина видео коррелирует со сложностью задачи.

И вот... Разбор ЭТОЙ задачи — 25+ минут. 🫣
Что же там такое должно быть, если преподаватель её почти полчаса разжёвывает? Моя фантазия рисует суровые картины.😐

Если я внезапно пропаду — вы знаете, где меня искать. Я ушёл в глубокий медитативный транс общения с Питоном. 🧘‍♂️💻

А у вас как дела с ООП? Давно прошли, сейчас штурмуете или путаетесь в трёх соснах?👇
🔥21
Все-таки этот "зверь" был не так страшен=)😅

Задание было написать игру крестики-нолики в ООП-шном стиле. Не знаю сколько точно времени заняло, но где-то 2 до 6 часов.⌛️ Мне кажется, если бы писал все через функции и взял например список для игрового поля, то было бы быстрее сделано. Но ничего... Надо учиться мыслить и немного в другом формате.

Первый раз сдаю, и не понимаю почему не принимает решение. Хотя в Pycharm всё работает и логика верна🤔 Оказалось банальная причина - опять немного не так прочитал. Такой косяк у меня частенько бывает😄 В общем, не заметил что is_human_win, is_computer_win, is_draw - это объекты-свойства(@property).

Изначально реализовал их как методы, и думаю чё не проходит то решение, всё вроде правильно.🤷‍♂️ Как только это исправил - всё заработало. Конечно не с 1 раза, (хотя можно было и с первого) просто достало, что assert-ы не проходило, и кидал после каждой мелкой поправки)) Хотя всё честно, логика то у программы была другая).

Вот так это выглядит:
🟦🟦🟦
🟦🟦
🟦⭕️🟦

Плюс в консоле подсказки и описания по игре🎮 В коментарий скину код. Можете попробоваться сразиться с компьютером и попытаться выиграть. Даже близко на ИИ не тянет)😂 Ну правильно, не на алгоритмы ж была задача. А продумывать код на 255 168 возможных партий, буду как-нибудь в другой раз)
🔥52
Добрался до наследования.
class Geom:
name = 'Geom'

class Line:
def draw(self):
print('Рисование линии')

g = Geom()
l = Line()


Пусть есть два класса. Очевидно что артибуты одного из другого мы никак не можем получить при такой схеме реализации. У них разное пространство имён.

Но можем установить связь между ними, например, чтобы открытые атрибуты Geom были доступны в классе Line :
class Line(Geom):
def draw(self):
print('Рисование линии')

Теперь можем получить l.name # Geom

Наследование - когда один класс определяется на основе другого. Причем, класс Geom называется родительским или базовым, а класс Line – подклассом родительского или дочерним классом.


Что это такое - понятно. Теперь для чего это нужно - разберемся на примере.

Добавим метод для установки координат в класс Line
    def set_coords(self, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2

А затем создадим еще одну фигуру, допустим прямоугольник Rect, и у него тоже будет метод установки координат. Но тогда получится дублирование кода. И метод выше мы можем вынести(определить) в базовом классе Geom.

Почему так работает? Ведь конкретно данного метода set_coords(self) нет в дочерних класса(Rect, Line). Но когда интерпретатор его там не находит, то продолжает искать у родителей. Причем 'self' из этого метода будет ссылаться не на объект базового класса, а на тот экземпляр(объект) из которого он был вызван! Это важный момент.(картинку с пояснением увидите в комментариях). Всё зависит от того откуда метод set_coords() был вызван.

Почему это важно?
Например, здесь self формирует локальные свойства/артибуты для дочерних классов Line, Rect.

Немножко вредных советов от НеУспенского:
Создали так(обратите внимание на последнюю строчку)
class Geom:
name = 'Geom'

def set_coords(self, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2

self.draw()

Запустили (ниже):
l = Line()
r = Rect()
l.set_coords(1, 1, 2 , 2)
r.set_coords(1, 1, 2 , 2)

Сработает или ошибка из-за отсутствия метода draw() в базовом классе?🧐А если set_coords() вызовем через объект класса Geom?🧐

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


Все тоже самое применимо при обращении к свойствам базового класса.

Можем там же переопределять атрибуты/методы в дочерних классах, например: 👇
class Line(Geom):
name = 'Line'

Это очень похоже на систему поиска атрибутов/методов у объекта класса vs самого класса. Сначала ищется у экземпляра, а если не находит, то ищет в классе. Самые базовые понятие, если с ними разобрались, то и наследование пойдёт тоже легко (проверю))). Тоже самое в наследовании, сначала ищет в текущем классе, а потом в родительских, поднимаясь вверх по иерархии.

Пример наследования init() в дочернем классе:
class Table:
def __init__(self, model, color):
self.model = model
self.color = color


class RoundTable(Table): # пример для реализации наследования
def __init__(self, model, color, radius, height):
# super() здесь вызывает "старый" инит и устанавливает переданные атрибуты (обычный вызов функции)
super().__init__(model, color)
self.radius = radius
self.height = height



Оставляйте фидбек по материалу 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы в комментарии!
🤔2
❄️ Всех с первым днём зимы!

Ну или у кого что там за окном😁У нас в городе пока вот так👆
Кажется, что снега в этом году могу вообще не увидеть.
А если и будет, то выпадет так быстро, что просплю единственный снегопад))

Как у вас настроение? Чувствуете уже новогодний вайб?🎄
Успеваете доделать то, что планировали на 2025 год?

И ещё вопрос из любопытства: а откуда вы?
Нас тут три с половиной сотни набралось за полгода — интересно, из каких городов и даже стран люди в этом канале!
Где-то, кажется в курсе ПП, видел интерактивную карту поселений участников, мб тоже такое сделать🙃

Рассказывайте в комментах — и про зиму за окном, и про планы, и про ваш город 😊
5
issubclass() 🔍 Наследование от встроенных типов
Продолжаем сложные, но интересные объяснения! 😈

class Geom: # <=> class Geom(object):
pass

📌 Пользовательский класс по умолчанию наследуется от базового класса object.

Зачем? 🤔 Чтобы обеспечить базовый функционал работы с классами!
g = Geom()
print(g) #👇
# <__main__.Geom object at 0x0000020B7CDF3FD0>

💡Например: вот эта "абракадабра" с адресом объекта при выводе - это результат работы методa __str__ из класса object
class Line(Geom):
pass

l = Line()
print(l.__class__)

📌 Наследование может быть ступенчатым, как видим из этого примера.
Атрибут __class__ тоже взят из того же класса object

Три способа проверки на принадлежность класса/объекта к классу:
🔧 С помощью функции issubclass(дочерний_класс, базовый_класс) можем определять,
является ли один класс подклассом другого (именно класс, а не его объекты).

🔧 А вот isinstance(объект, класс) тоже учитывает наследование! 🎯
Эта функция проверяет, является ли объект экземпляром класса или экземпляром любого из его подклассов!

🔧 Есть еще похожая функция type(объект) - она определяет конкретно какому классу принадлежит
переданный объект, без учета наследования.
print(issubclass(Line, Geom)) # True  ← Line наследуется от Geom
print(isinstance(l, Geom)) # True ← объект l принадлежит Geom или его подклассу
print(type(l) == Geom) # False ← объект l не является непосредственным экземпляром Geom

💥 Интересный факт: Все стандартные/встроенные типы данных являются классами. (int, float, list, dict, tuple, set, и т.д.)


Тогда мы можем расширить встроенные типы, например list:
class Vector(list):
def __str__(self): # self - здесь просто список
return ' '.join(map(str, self))

v = Vector([1,2, 3])
print(v) # 1 2 3 вместо [1, 2, 3]

Можно стать немножко Java-истами!😄 Складывать строки и числа.
Исходя из последних постов - вы уже знаете, как это сделать.

Как вам такие возможности наследования??? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы в комментарии!
🔥4
Наследование. Атрибуты private и protected

Напомню про режимы доступа свойств класса:
public - публичное свойство: - доступно везде
protected - (с одним подчеркиванием _attr): для обращения внутри класса и во всех его дочерних классах
private - (с двумя подчеркиваниями __attr): для обращения только внутри класса

Пример: сделаем в базовом классе все атрибуты приватными.
class Geom:
name = 'Geom'

def __init__(self, x1, y1, x2, y2):
print(f"инициализатор Geom для {self.__class__}")
self.__x1 = x1
self.__y1 = y1
self.__x2 = x2
self.__y2 = y2

class Rect(Geom):
def __init__(self, x1, y1, x2, y2, fill='red'):
super().__init__(x1, y1, x2, y2)
self.__fill = fill

r = Rect(1, 2, 3 ,4)
print(r.__dict__)

Получим вот такой вывод (хммм): создавали один класс, а там почему-то переменные из другого класса:
{'_Geom__x1': 1, '_Geom__y1': 2, '_Geom__x2': 3, '_Geom__y2': 4, '_Rect__fill': 'red'}

Мало того, если даже добавим метод для получения этих координат в классе Rect, то всё равно ничего не получится — будет ошибка:
    def get_coords(self):
return (self.__x1, self.__y1)

print(r.get_coords()) # # AttributeError: 'Rect' object has no attribute '_Rect__x1'

Однако если мы переместим этот метод в БАЗОВЫЙ класс, то всё отработает как хотелось бы.

Такое поведение потому, что privat-атрибуты доступны только внутри того класса, где объявлены. Собственно для этого они и создавались.
А если хотим получать доступ к ним в дочерних классах, то уменьшаем количество подчёркиваний на одну. Всё просто)

Protected-атрибуты фактически не запрещает обращаться вне класса. Это условности, которые напоминает, чтобы не обращались к этому атрибуту в другом месте. Не для тебя его роза цвела.😅

ываыаа

Тоже самое касается и приватных методов:
    def __verify_coord(self, coord):
return 0 <= coord <= 100

Их можно использовать только внутри текущего класса. Если попытаетесь в другом месте — ошибка отсутствия метода.

Можно еще попробовать другой вариант обхода - в дочернем классе вызывать через super() метод базового класса:
 super().__verify_coord(x1)

Но нет.. .Так тоже не получиться схитрить и ловим ошибку: ``AttributeError: 'super' object has no attribute '_Rect__verify_coord'``

💬 Вопрос к вам:
А вам вообще интересно, полезно про это читать? Может я зря стараюсь и напрягаюсь?
Практически не вижу реакций под такими постами 😔😔😔
Дайте знать в комментах — нужен фидбек!

👇 Как вам такой формат?!

🔥 — Полезно, продолжай!
🤔 — Есть вопросы /предложения или темы для следующих постов.
🔥5
Здесь можно пройти тест на "Какой ты язык программирования". У меня, как видите, совпало) А у вас что вышло?
😈4😁2
Полиморфизм и абстрактные методы

Полиморфизм – это возможность работы с совершенно разными объектами (языка Python) единым образом. (т.е. через единый интерфейс)

Разберемся на примере: пусть есть два класса: для прямоугольников и квадратов:
class Rectangle:
def __init__(self, w, h):
self.w = w
self.h = h

def get_rect_pr(self):
return 2 * (self.w + self.h)

class Square:
def __init__(self, a):
self.a = a

def get_sq_pr(self):
return 4 * self.a

r1 = Rectangle(1, 2)
r2 = Rectangle(3, 4)
sq1 = Square(10)
sq2 = Square(20)

Получить периметры очевидно можем так:
print(r1.get_rect_pr(), r2.get_rect_pr())
print(sq1.get_sq_pr(), sq2.get_sq_pr())

Но... Если захотим перебрать элементы некого списка объектов [r1, r2, sq1, sq2] и через цикл получить значение периметра, то это будет проблематичнее, ведь у них разные методы для получения данного значения. Можно, конечно, сделать проверку на принадлежность объекта какому-то классу и потом уже вызывать соответствующий метод.

Например так:
geom = [r1, r2, sq1, sq2]
for g in geom:
if isinstance(g, Rectangle):
print(g.get_rect_pr())
else:
print(g.get_sq_pr())

А если еще один или несколько классов добавиться, что тогда делать? Добавлять каждый раз ветку и плодить 100500 условий? В данном случае код получается не очень гибким.
class Triangle:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c

def get_tr_pr(self):
return self.a + self.b + self.c

Это можно исправить. Вот как раз здесь очень хорошо применим подход, который и называется полиморфизмом. Мы договоримся в каждом классе создавать методы с одинаковыми именами, например - get_pr(). Получается что к разным объектам мы обращаемся через единый интерфейс - это и есть полиморфизм.

Но можно еще немного улучшить эту методику.

Для чего? А в друг мы забудем где-то создать метод для получения периметра. Я, например, могу что-то забыть вполне спокойно😬 Тут есть два пути решения проблемы. Сначала создаем базовый класс а остальным - унаследоваться, затем:

1 способ) В нем прописать метод get_pr().
    def get_pr(self):
return -1

2 способ) Реализовать через абстрактный метод
    def get_pr(self):
raise NotImplementedError(f"В дочернем классе: {self.__class__.__name__} должен быть переопределен метод get_pr()")
)


• В 1 варианте мы не будем получать ошибок, при вызове - да. Но результат будет не тот, который ожидаем.
• Во 2 варианте будет выдаваться ошибка, которая конкретно указывает что мы забыли переопределить метод в дочернем классе.

Итоговая реализация выглядит вот так:
class Geom:
def get_pr(self):
raise NotImplementedError(f"В дочернем классе: {self.__class__.__name__} должен быть переопределен метод get_pr()")

class Rectangle(Geom):
...
def get_pr(self):
return 2 * (self.w + self.h)
...

class Square(Geom):
...

class Triangle(Geom):
...


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

Вы можете заметить, что есть еще абстрактные классы и абстрактные методы. Но об этом в другой раз😏

Комментарии и реакции приветствуются👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы в комментарии!
🔥4
🤔 ПРО МОДУЛЬ abc 🤔

В языке Python есть еще один распространенный способ объявления абстрактных методов класса через декоратор abstractmethod модуля abc:
from abc import ABC, abstractmethod

Чтобы корректно работал декоратор abstractmethod сам класс должен наследоваться от базового класса ABC. Например, так:
class Transport(ABC):
@abstractmethod
def go(self):
"""Метод для перемещения транспортного средства"""

@classmethod
@abstractmethod
def abstract_class_method(cls):
"""Абстрактный метод класса"""

Мы здесь имеем два абстрактных метода внутри класса Transport, причем, первый метод go() - это обычный метод, а второй abstract_class_method() - это абстрактный метод уровня класса. Обратите внимание на порядок использования декораторов classmethod и abstractmethod. Они должны быть записаны именно в такой последовательности.

Теперь, если объявить какой-либо дочерний класс, например:
class Bus(Transport):
def __init__(self, model, speed):
self._model = model
self._speed = speed

def go(self):
print("bus go")

@classmethod
def abstract_class_method(cls):
pass

То в нем обязательно нужно переопределить абстрактные методы go и abstract_class_method класса Transport. Иначе, объект класса Bus не будет создан (возникнет исключение TypeError).


Также с помощью модуля abc можно определять не только абстрактные методы, но и абстрактные объекты-свойства (property). Делается это следующим образом:
from abc import ABC, abstractmethod

class Transport(ABC):
@abstractmethod
def go(self):
"""Метод для перемещения транспортного средства"""

@property
@abstractmethod
def speed(self):
"""Абстрактный объект-свойство getter 'speed' """

@speed.setter
@abstractmethod
def speed(self, value):
"""Абстрактный объект-свойство setter 'speed' """

Порядок навешивания декораторов важен! Конечно, вы можете в базовом классе Transport не прописывать "property", и без этого сработает. Но так лучше все-таки делать, чтобы понимать как именно надо реализовывать этот метод в дочернем классе.

Комментарии и реакции приветствуются👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы в комментарии!
🔥5
👿Алгоритм Дейкстры или куда пропал мой вчерашний день.👿

Всём доброе утро. Курс по ООП потихоньку подходит к концу. Подошло время очередного испытания - большая задача по темам главы. Как вы возможно догадались, здесь про граф, и к сожалению не про Дракула.

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

Собственно сел решать, на 95% всё описал по ТЗ. Казалось бы почти все. Но как же я ошибался😅Это оказалось лишь верхушкой айсберга)

И тут началось самое интересное. Сначала пришлось гуглить и вспоминать как там вообще всё происходит. Последний раз проходил это в универе. Разобраться на примере на бумажке, . Ну вроде бы всё легко и понятно. Осталось дело за малым перенести алгоритм на комп.

Пару подходов с перерывами и я всё ещё практически не сдвинулся с места. Крч, недоделал до конца и забил. Конечно можно было взять готовой алгоритм. Но для чего?) Даже в статейке, где я прочитал про Дейкстру, был пример алгоритма на питоне. Чуть не проспойлерил себе решение. Предлагают импортировать доп. модуль, который предназначен для работы с кучами. После этих слов я перестал читать дальше)) Кажется можно реализовать проще, без всяких этих изыщрений.

А у вас как дела обстоят с графами?)
🎃4🔥2
👁Множественное наследование в Python👁

Это когда класс наследуется от нескольких родителей. Не самый частый, но мощный инструмент!

📚 Часть 1: Простой пример

🎯 Главный кейс — миксины (примеси) — классы, которые добавляют одну конкретную фичу другим классам.

👉 Простой пример:
class Goods: # Товары
def __init__(self, name, weight, price):
super().__init__() # 🔑 ключевой момент!
self.name = name
self.weight = weight
self.price = price

def print_info(self):
print(f"{self.name}, {self.weight}, {self.price}")

class MixinLog:
ID = 0

def __init__(self):
super().__init__() # здесь для примера
MixinLog.ID += 1
self.id = MixinLog.ID

def save_sell_log(self):
print(f"Товар с id={self.id} был продан в {datetime.now()}")

# Множественное наследование
class Notebook(Goods, MixinLog):
pass

n = Notebook('Acer', 1.5, 30000)
n.print_info() # Acer, 1.5, 30000
n.save_sell_log() # работает!

Что происходит?

1. Создаем Notebook → вызывается Goods.init
2 . super().init() в Goods передает управление MixinLog.init
3. Оба инициализатора выполняются

🧠 Как работает super()?
Раньше, уже думаю, ее видели и понимаете что она делает. Я тоже так думал, но оказалось не совсем верно понимал🫣
Оказывается super() возвращает не просто "родительский класс", а следующий класс в MRO (Method Resolution Order) — порядке обхода классов при наследовании.

👉 Цепочка вызовов в данном примере:
1. Notebook() → Goods.init()
2. super().init() в Goods вызывает MixinLog.init()
3. super().init() в MixinLog вызывает object.init()
4. Все инициализаторы выполнены!

🔍 Важно: порядок имеет значение!
Порядок наследования (Goods, MixinLog) определяет MRO (Method Resolution Order):
print(Notebook.__mro__)
# (<class '__main__.Notebook'>,
# <class '__main__.Goods'>,
# <class '__main__.MixinLog'>,
# <class 'object'>)

💎 Правила для миксинов:

1. Делайте их максимально простыми и узкоспециализированными
2. Всегда вызывайте super().init() в init
3. Ставьте миксины после основного класса в наследовании

🎯 Зачем это нужно?

• Переиспользование кода без дублирования
• Гибкое расширение функциональности
• Чистая архитектура (разделение ответственности)

📌 Итог:
Миксины — это как LEGO для классов. Добавляйте готовые блоки функциональности, не ломая основную логику!

А вы использовали множественное наследование или миксины на практике? 👇
👍4
📚 Часть 2: MRO и конфликты методов в Python

🔍 Что такое MRO на самом деле?

MRO (Method Resolution Order) — это алгоритм, который определяет порядок поиска методов и атрибутов при множественном наследовании.
class A:
def method(self):
print("A")

class B(A):
def method(self):
print("B")

class C(A):
def method(self):
print("C")

class D(B, C):
pass

print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>,
# <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

d = D()
d.method() # Вывод: "B" (первый в MRO)

🎯 Как Python вычисляет MRO?
Используется алгоритм C3 Linearization. Главные правила:

• Дочерний класс всегда перед родительским
• Порядок наследования сохраняется
• Более левые классы в наследовании имеют приоритет

⚔️ Разрешение конфликтов методов

Рассмотрим ситуацию, когда несколько родительских классов имеют методы с одинаковыми именами, и как можно управлять их вызовом:
class Printer:
def action(self):
return "Печатаю"

class Scanner:
def action(self):
return "Сканирую"

class Copywriter:
def action(self):
return "Копирую"

class MFP(Printer, Scanner, Copywriter):
pass

🔍 1-й вариант: Оставить как есть (используется первый по MRO)
mfp = MFP()
print(mfp.action()) # 👉 "Печатаю"

Почему? MRO для класса MFP: MFP → Printer → Scanner → Copywriter → object. Python находит метод action() в классе Printer первым и останавливается.

🎯 2-й вариант: явный вызов метода из конкретного класса

print(Scanner.action(mfp)) # 👉 "Сканирую"

Важно: При таком вызове нужно явно передавать объект (mfp) первым аргументом, так как мы вызываем метод класса, а не объекта.

🔧 3-й вариант: Переопределить метод в дочернем классе
class MFP(Printer, Scanner, Copywriter):
def action(self):
return Scanner.action(self)

# Своя уникальная логика
# return "Я МФУ, делаю всё!"

mfp2 = MFP()
print(mfp2.action()) # "Копирую"

📊 Когда что использовать:

1-й способ — когда порядок MRO вас устраивает и конфликтов нет
2-й способ — для разового вызова метода из конкретного родителя
3-й способ — для постоянного изменения поведения или комбинирования логики

⚠️ Замечание:
Хотя явный вызов методов через Класс.метод(объект) работает, это жёсткая привязка к конкретному классу. Если изменится иерархия наследования, такой код может сломаться. Переопределение метода в дочернем классе — более гибкий и поддерживаемый подход.

💡Напоминаю - проверить MRO можно всегда:
print(MFP.__mro__)
# (<class '__main__.MFP'>, <class '__main__.Printer'>,
# <class '__main__.Scanner'>, <class '__main__.Copywriter'>, <class 'object'>)


Вывод
: В Python есть гибкие механизмы для работы с наследованием. Выбирайте подход в зависимости от задачи — от простого использования MRO до полного контроля через переопределение методов.
🔥3
🎯 Коллекция __slots__ в Python. Что это?

__slots__ — это специальный атрибут класса, который определяет разрешённые имена локальных свойств экземпляров.

🔧 Пример использования:
class Point:
__slots__ = ('x', 'y') # Разрешаем ТОЛЬКО 'x' и 'y'

def __init__(self, x, y):
self.x = x # Можно
self.y = y # Можно

p = Point(1, 2)
p.z = 3 # Ошибка! Только 'x' и 'y'


⚠️ Важный момент:
# При slots у объектов нет dict
print(p.__dict__)  #  AttributeError! 


💪 3 главных преимущества:

1️⃣ 🔒 Ограничение свойств
• Можно создавать ТОЛЬКО атрибуты из slots
• Защита от опечаток и случайных атрибутов

2️⃣ 💾 Экономия памяти
• У объектов не создается `__dict__` (обычный словарь атрибутов)
• Атрибуты хранятся более эффективно
• Экономия 20-50% памяти на каждый объект

3️⃣ ⚡️ Ускорение работы
• Быстрее читать/писать разрешённые атрибуты
• Прямой доступ вместо поиска в словаре

🎯 Идеально для:
• Фиксированных структур данных
• Массового создания объектов
• Оптимизации памяти в сервисах
🔥4
🎯 __slots__ с property и при наследовании

Рассмотрим два важных аспекта использования __slots__ — взаимодействие с декоратором property и особенности наследования.

🔧 slots и property
• Возьмём за основу класс Point из прошлого поста.
• Добавим в slots еще одно допустимое свойство - 'length'.
• А что будет, если добавить property с таким же именем 'length'?

class Point2D:
__slots__ = ('x', 'y', '__length') # Используем защищенный атрибут

def __init__(self, x, y):
self.x = x
self.y = y
self.__length = (x**2 + y**2)**0.5

@property
def length(self):
return self.__length

@length.setter
def length(self, value):
self.__length = value


Работает отлично! Потому что:
• length в property — это атрибут класса, а не локальное свойство экземпляра.
slots не запрещает работу с такими атрибутами!

🔧 slots и наследование

Базовый класс:
class Point:
__slots__ = ('x', 'y')

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


Дочерний класс БЕЗ __slots__:
class Point3D(Point):
pass

pt3 = Point3D(10, 20)
pt3.z = 30 # Работает!
print(pt3.__dict__) # {'z': 30} - x и y не в dict!


📌 Важно: если не определить slots в дочернем классе, у объектов появится dict и можно создавать любые атрибуты!

Дочерний класс С __slots__:
class Point3D(Point):
__slots__ = ('z',) # Добавляем только новые атрибуты

pt3 = Point3D(10, 20)
pt3.z = 30 # Работает
pt3.w = 40 # Ошибка! Только x, y, z разрешены


🎯 Ключевые моменты:
1) __slots__ работает с property — используйте защищенные имена (__length) в слотах
2) Наследование требует явного указания __slots__ в дочерних классах
3) Слоты объединяются — дочерний класс получает все слоты из цепочки наследования
4) Без __slots__ в потомке — объекты получают __dict__ и теряют преимущества оптимизации

📝 Правило для наследования:
Если базовый класс имеет __slots__, определите __slots__ и в дочернем классе, даже если он пустой!
class Child(Parent):
__slots__ = () # Пустой кортеж сохраняет оптимизацию

Используйте slots осознанно, учитывая эти нюансы! 🚀
👍3
Завершил курс Балакирева по ООП. Не прошло и пол года, как говорится))

В наилучшем исходе рассчитывал пройти дней за 20, ожидаемо - 30. А прошло два месяца😅Ну это смотря как считать.🙃 Очевидно что не все 60 дней я усердно занимался. Недели две, а то и три, можно смело выкинуть на помойку. И уже получается 40 дней. В принципе адекватное время, учитывая что там как раз около 40 тем.

В день я занимался(когда не дурака валял) часов по 4-5+. Некоторые темы легче давались сложнее, некоторые легче. Другие вообще можно было пачкой сделать за один оборот земли своей оси. Но к сожалению у меня так не работает😐. И после одного цельного блока информация уже уходил на отдых. Может решил себя не грузить, чтобы всё усвоилось. А может расслаблялся слишком порой. Или же концепция ООП долго до меня доходит. Чаще получалось так: сначала надо было понять что от меня хотят, подумать как это сделать, и только потом уж писать код.

В общем, курс можно пройти за 30 дней(как и планировал), не особо сильно напрягаясь, но и не халтурить при этом). К сожалению ожидание не всегда совпадает с реальностью.🫠 На днях запишу более подробный отзыв о курсе.

Осталось еще пара-тройка скипнутых задач. На удивление не по связным спискам)) Наверно этим и займусь на ближайших выходных, но это не точно😅 Еще хотелось бы довести до ума сапера, крестики-нолки, ииии морской бой, чтобы это было играбельно всё. Тут уж от настроения зависит... Затем пойду тыкать Django.🫡

А у вас какие планы на выходные?) Все уже ёлку нарядили дома?)
🔥5🎉31🤝1
В начале нового года на Stepik планируют изменить интерфейс платформы. Пару недель назад висело объявление о прохождении тестирования.

Собственно вот ссылка - макет на figma. Это пока что прототип. Если захотите - посмотрите, потыкайте. Кнопка 'R', возвращает на начальный экран.

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

P.S. Мне интересно, найдете ли вы быстро кнопку "развернуть"?)
🔥5👏3