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

Когда к экземпляру класса применяется функция bool() - явно или неявно - она определяет, считается ли объект "истинным" или "ложным". Напомню: для пустых данных функция выдаёт False, для непустых - True.

Где это может пригодиться? Ваши варианты?
Например, в программах где нужны кастомные проверки истинности объектов

Для настройки этого поведения используют:
- __bool__ - основной способ
- __len__ - запасной вариант

По умолчанию bool() ВСЕГДА возвращает True для любых экземпляров кастомного класса. Зачем тогда это нужно? 😅

А вот зачем - можно переопределить через магические методы!
Приоритет проверки:
1. Сначала ищется __bool__
2. Если его нет - __len__()
3. Если и его нет - всегда True

Всё просто! 🎯

Перейдем к практике:
Пусть есть такой простой класс Point:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

модифицируем len()
    def __len__(self):
print('__len__')
return self.x**2 + self.y**2

изменяем bool()
    def __bool__(self):
print('__bool__')
return self.x == self.y

Важный нюанс! __bool__ ОБЯЗАН возвращать True или False ❗️

Теперь можно использовать в условных операторах: (bool() вызывается неявно)
p = Point(2, 2)
if p:
print("объект p дает True") # сработает это
else:
print("объект p дает False") # сработает это, если


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

Однако есть еще вот такие методы:
__getattribute__()
__setattr__()
__delattr__()

Визаульно похоже, но не стоит их путать. Для чего они нужны, знаете?🧐🙃
А еще есть и 4 __getattr__()🫠🫠🫠
🤔5
Поехали разбираться с магией индексации: сделаем классы "как список" 🎩

Пусть есть такой класс студент, создадим его экземпляр
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = list(marks)

st1 = Student('Евгений', [5, 5, 3, 2, 5])

Думаю понятно как обращаться к элементам списка marks: st1.marks[2]
А если хочется напрямую? st1[2]. Перехочется😂 - очевидно получим ошибку.

Это можем поправить, добавив метод __getitem__' - для получения значений.
    def __getitem__(self, item): # item - ключ/индекс
return self.marks[item]

Добавим метод __setitem__ - для изменения значений, например: по типу st1[2] = 4,
    def __setitem__(self, key, value): # key - индекс, value - # значение
self.marks[key] = value

Думаю уже догадались какой остался и для чего: __delitem__' - для удаления значений
    def __delitem__(self, key): # key - индекс
self.marks.pop(key)

Можно в эти методы дописывать необходимые вам проверки, чтобы как было задумано использовать пользовательский класс. Но и без этого будут вылезать ошибки, при неверном использовании, т.к. мы (в данном случае) будем уже работать со стандартными типами данных, в частности со списком.

Приведу пару примеров модернизации (смотреть в комменты)

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

1) ⚡️Скорость разработки: Быстрая печать = быстрое воплощение идей в код, что напрямую ускоряет реализацию проекта и поддерживает высокий темп работы.

2) 🎯 Фокус на логике: Навык позволяет перенести все ментальное внимание с механики ввода на решение сложных задач и проектирование алгоритмов.

3) 🧠 Снижение усталости: Автоматизация печати минимизирует умственное напряжение и физическое утомление глаз, позволяя дольше сохранять высокую концентрацию.

4) Повышение качества кода: Печать вслепую позволяет мгновенно обнаруживать и исправлять опечатки и синтаксические ошибки, так как взгляд фиксируется на экране.


Резюмируя: время, потраченное на освоение слепой печати (это занимает в среднем 2-4 недели регулярных занятий), — это лучшая инвестиция в себя, точка! 🚀

Я сам прошел этот путь, и могу подтвердить: результат того стоит. Этот навык не приходит сам собой, нужна практика, но эффект ошеломляющий. 💪

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

В следующем посте я поделюсь списком лучших тренажеров, которые я лично использовал, и расскажу, где лучше всего учиться. Готовьтесь к бусту продуктивности! 🔥😈

А вы как считаете, нужный это навык или все-таки нет?🙃
🔥91👍1
Хотите так же? Смотрите ниже🙃

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

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

Тренажер 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