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

Если есть тут те, кто реально работает, скажите, насколько я попал? 🤔

В общем, вчера проснулся в 4 утра. Дальше — все как в тумане, неожиданно наступило 8 часов 😶 Время ехать в офис. Но так как я «работаю» удаленно, до 9:30 еще валялся и досыпал. Кайф!

Дальше открываю Jira Stepik. Смотрю задачи, беру ту, что «горит». Пару дней назад кое-что отложил, вот пришлось срочно доделывать. Разобрался с этим и приступаю.

В итоге просидел до 17:30, не считая перерывов на кофе и что-то сделать покушать. Получился целый рабочий день. Не думал, что может так затянуть! Раньше вообще размышлял, как можно столько часов подряд сидеть и кодить? А нет, оказывается, можно))

Пошел четко по плану. К моему удивлению, задача, которая на первый взгляд казалась самой легкой, отняла больше всего времени (целых 2.5 часа!). А самая непонятная (по описанию) была не такой уж и трудной.

В общей сложности сделал 7 задач. Забудьте про те 25 в день, про которые писал раньше 😂 Важно, сколько времени ты уделяешь работе, а не количество. Просто прошлым способом проще заставить себя начать.

Как только прошло 8 рабочих часов, ни минуты не задерживаясь, закрыл всё к чертям! 😎 Теоретически мог бы заниматься дальше, но чувствую, продуктивность была бы уже около-нулевая.

А у вас как проходят рабочие будни? Делитесь в комментах! 👇🙃
Узнали себя? - Ставьте лайк😈
🔥5
Магические методы для арифметических операторов в Python 🔢

Знакомьтесь с основными магическими методами:
add() – сложение
sub() – вычитание
mul() – умножение
truediv() – обычное деление
floordiv() – целочисленное деление
mod() – остаток от деления

Лучше один раз увидеть, чем 100500 раз прочитать! 👀 Внимание - очень много букв😁😁😁

Возьмем класс для работы со временем. Время храним в секундах (макс. 86400 - секунд в сутках):
class Clock:
__DAY = 86400 # секунд в сутках

def __init__(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self.__DAY

def get_time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{h:02}:{m:02}:{s:02}"

Добавляем сложение

Сначала научим складывать с числами:
    def __add__(self, other): # 
if not isinstance(other, int):
raise ArithmeticError("Правый операнд должен быть типом int")
return Clock(self.seconds + other)

Теперь работает:
с1 = Clock(1000) 
с1 = с1 + 100
print(с1.get_time()) # 00:18:20

Усложняем - складываем два объекта Clock:
    def __add__(self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Правый операнд должен быть типом int или объектом Clock")

sc = other if isinstance(other, int) else other.seconds
return Clock(self.seconds + sc)

Теперь:
c1 = Clock(1000)
c2 = Clock(100)
c3 = c1 + c2
print(c3.get_time()) # # 00:18:20

Закончили? Неа!
Не работает: c1 = 100 + c1  # Ошибка!

Добавляем обратное сложение 🔄
    def __radd__(self, other):
return self + other # перекидывает в __add__(self, other) .

Теперь 100 + c1 работает. Почему кидает в add? Честно - я не до конца понял 😅
c1 = Clock(1000) 
c1 = 100 + c1
print(c1.get_time()) # 00:18:20

Но метод выше можно еще реализовывать например таким способом, тут всraddее:
    def __radd__(self, other):
return self.__add__(other)

Ну теперь уж точно всё, подумали вы. Нет😂😂 все еще не работает такой стиль c1 += c2🙈
Точнее работает, но не так. как хотелось бы. Мы пока что возвращаем(создаем) новый объект, а здесь новый эзкемпляр создавать не надо. Надо просто изменять на месте, для этого напишем так.

Изменяем объект на месте (не iadd новый)
    def __iadd__(self, other):
print("__iadd__")
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Правый операнд должен быть типом int или объектом Clock")

sc = other if isinstance(other, int) else other.seconds
self.seconds += sc

return self # возвращаем тот же объект!

Теперь c1 += c2 изменяет сам c1 🎯

Важный нюанс:
🟡 + создает новый объект
🟡 += изменяет существующий

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

Итог:
Теперь вы знаете, как работают операции в int, float, str! Все ошибки, которые вы видели - это как раз проверки в этих dunder методах.

Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
🐍 — Здесь притаился питон.raddто объясниaddму __radd__ вызывает __add__ в первом варианте реализации этого метода? Жду в комментах! 👇
🤔3🔥1
Сравнения в Python: как научить классы понимать ==, !=, >, <, >=, <= 🔍

Продолжаем разбирать магические методы! После арифметики из прошлого поста переходим к сравнениям:

eq
()
– для равенства ==
ne() – для неравенства !=
lt() – для оператора меньше <
le() – для оператора меньше или равно <=
gt() – для оператора больше >
ge() – для оператора больше или равно >=

По умолчанию первый и второй метод сравнивают 🆔 объектов. Поэтому технически норм, при вызове ошибок не будет. Но реализация конечно будет не такой как вы ожидали увидеть). А если для того же класса Clock из прошлого поста сравнивать другими операциями, то вообще вылезут ошибки.

Но их можно легко исправить.
    def __eq__(self, other):
if not isinstance(other, (int, Clock)):
raise TypeError('Оперант справа должен иметь тип int или Clock')

other_seconds = other if isinstance(other, int) else other.seconds
return self.seconds == other_seconds

Пример:
c1 = Clock(1000)
c2 = Clock(1000)
print(c1 == c2) # True


Другие методы можете сами догадаться как прописывать, аналогично этому __eq__

Занимательный факт: можно дополнительно не реализовывать метод для сравнения на неравенство - (ne) , если уже реализован (eq). Как думаете почему? А если реализовали метод "<", сработает ли ">"?🧐🧐🧐

Подсказка: Python пытается быть умным и использовать имеющиеся методы для обратных операций! 🔄
🔥3
"Вайбкодер" vs Программист: Новая эра или просто хайп?

Смотрю сейчас вакансии и натыкаюсь вот на такую. Название "Вайбкодер" - интересно))

И сразу мысль: может я не тем занимаюсь и зря всё это учу? Чисто взял, научился создавать промпты для нейронки — и ты уже в IT? Кажется, что это легкий вход, особенно когда видишь "опыт не требуется" и "удаленка".

Но за этим хайповым названием скрываются вполне реальные технические требования: знание Python, работа с API, автоматизация, n8n, VS Code, OpenAI и Claude. Это не просто "поболтать с чат-ботом".

Всем, кому интересно полное описание - вот ссылка на вакансию.

Мое мнение - нет, это не замена классическому кодингу(хотя я задумался😅). Просто новая ниша. Учиться все равно нужно много. А вы как считаете?
🤔5
Вычисление хеша для объектов класса в Python

Функция hash() в Python вычисляет хеш для неизменяемых объектов (immutable types):
a = hash(123) # 123
b = hash("Python") # -8344082464644304368
c = hash((1, 2, 3)) # 529344067295497451

Если вы попытаетесь взять хеш от изменяемых объектов (например, списка list), то получите ошибку TypeError: unhashable type: 'list'. 😱

Ошибка возникнет даже в том случае, если изменяемый элемент находится внутри неизменяемого объекта. Пример: элементом кортежа является список list. Тогда тоже будет ошибка "unhashable".

Некоторые свойства хешей:
1) Для одинаковых объектов хеши всегда одинаковы.
2) Обратное утверждение в общем случае не верно (разные объекты могут иметь одинаковый хеш — это называется коллизией).
3) Если хеши не равны, то и сами объекты точно не равны.

Зачем это вообще нужно? 🤔

Мы поняли, что такое хеш, теперь давайте разберемся, зачем он нам пригодился! Например, словари используют хеши для быстрого доступа к ключам для быстрого поиска. Ведь помните, словари в ключах требуют только неизменяемые типы данных? Словарь хранит ключи примерно в таком виде: (хеш_ключа, значение_ключа).

А это зачем нужно? Первоначально запись в словаре ищется по хешу, что занимает минимум времени. Далее, если вдруг хеши разных ключей совпали (коллизия), то тогда уже Python дополнительно проверяет объекты на равенство (==).

Хеширование на примере классов

Теперь рассмотрим, как это работает с пользовательскими классами:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

p1 = Point(1, 2)
p2 = Point(1, 2)

print(hash(p1))
print(hash(p2))


Как думаете? какие будут хеши у этих экземпляров класса? Одинаковые, разные, или вообще ошибка? Разные!
Если p1 == p2 возвращает True, то хеши одинаковые. А если False, то разные(если нет коллизии). Почему так? Коллизия? Нет, все проще. По умолчанию Python вычисляет хеш на основе id объекта (его адреса в памяти) 🧠

Переопределение `__eq__` и `__hash__`

А что если мы переопределим метод __eq__ в классе Point следующим образом?
    def __eq__(self, other):
return self.x == other.x and self.y == other.y

Тогда p1 == p2 станет True. Значит и хеши по идее равны. Но не все так просто) Вылезет ошибка. Хе-хе🙃. Когда мы переопределили __eq__, то функция hash() перестает работать. Т.к. перестает работать стандартный алгоритм вычисления хеша. Иными словами: после переопределения __eq__: Python автоматически делает класс unhashable (`__hash__ = None`), если мы не определили свой `__hash__`. Это защита от нарушения контракта между равенством и хешированием.

Чтобы это исправить, нужно обязательно переопределить метод __hash__ в нашем классе. Он и вызывается (логично, да?), когда мы пишем hash(p1).
    def __hash__(self):
return hash((self.x, self.y))

Что теперь скажете насчет хешей объектов p1 и p2? Равны! 🎉 Мы теперь вычисляем хеш не от самих объектов (их ID в памяти), а от их координат.

Пример использования в словаре

Например, если по такой логике создавать словарь👇, то получим только одну пару ключ: значение. А если мы не переопределяли эти методы, то получили бы две.
d = {}
d[p1] = 1
d[p2] = 2
print(d) # {<__main__.Point object at 0x0000024D012C8F40>: 2}

Таким образом, мы можем переопределять методы eq и hash под требуемую логику нашей программы. В данном случае, объекты класса Point с одинаковыми координатами воспринимаются как одинаковые объекты. 😏

А вы когда-нибудь сталкивались с ошибкой "unhashable type" в своих проектах? 😁
🔥3
Сегодня поговорим о способах настройки "правдивости" объектов класса 🤔

Когда к экземпляру класса применяется функция 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