☎️📞🤙 Call me maybe ☎️📞🤙
Классы в Python можно вызывать как функции. Да, те самые обычные две скобки () при создании экземпляра.
В этот момент вызывается "магический метод" `__call__` 🧙♂️. По умолчанию он вызывает ошибку TypeError, если применяется к экземпляру класса без реализации.
Классы, которые так себя ведут, называют "функторы" 🧠. Странное название, ну да ладно 😁.
Но зачем это надо? 🤔 Давайте разберемся на примерах.
1) Простой счетчик
2) Счетчик с параметром:
Можем немножко изменить код, чтобы туда можно было передавать аргументы, Что выведется в последней строчке тогда, как думаете?
Еще пару вариантов:
3) Замена замыканий функций:
+ в чат если помните что это такое 🙃. Пример для обработки строки через strip(). Но можно придумать что угодно другое.
4) Реализация декораторов, с помощью классов.
Математика вошла в чат 📊. Здесь вычисляется значение производной функции в конкретной точке х.
Итог: Функторы — это мощный инструмент, когда нужно сохранять состояние между вызовами или создавать умные декораторы! 🚀
А вы используете функторы в своих проектах? Делитесь в комментариях! 👇
P.S. Кто угадал результат во втором примере? 😄
Классы в Python можно вызывать как функции. Да, те самые обычные две скобки () при создании экземпляра.
В этот момент вызывается "магический метод" `__call__` 🧙♂️. По умолчанию он вызывает ошибку TypeError, если применяется к экземпляру класса без реализации.
Классы, которые так себя ведут, называют "функторы" 🧠. Странное название, ну да ладно 😁.
Но зачем это надо? 🤔 Давайте разберемся на примерах.
1) Простой счетчик
class Counter:
def __init__(self):
self.__counter = 0
def __call__(self, *args, **kwargs):
print('__call__')
self.__counter += 1
return self.__counter
c = Counter()
c()
c()
res = c()
print(res) # 3 🎯
2) Счетчик с параметром:
Можем немножко изменить код, чтобы туда можно было передавать аргументы, Что выведется в последней строчке тогда, как думаете?
class Counter:
def __init__(self):
self.__counter = 0
def __call__(self, step=1, *args, **kwargs):
print('__call__')
self.__counter += step
return self.__counter
c = Counter()
c2 = Counter()
c()
c(10)
res = c(3)
res2 = c2(-5)
print(res, res2) # что выведет? 🧠
Еще пару вариантов:
3) Замена замыканий функций:
+ в чат если помните что это такое 🙃. Пример для обработки строки через strip(). Но можно придумать что угодно другое.
class StripChars:
def __init__(self, chars):
# символы для удаления в конце и начале строки
self.__chars = chars
def __call__(self, *args, **kwargs):
if not isinstance(args[0], str):
raise ValueError("Аргумент должен быть строкой")
return args[0].strip(self.__chars)
s1 = StripChars("?:!.; ")
res = s1("; Hello World! ")
print(res) # "Hello World" ✨
4) Реализация декораторов, с помощью классов.
Математика вошла в чат 📊. Здесь вычисляется значение производной функции в конкретной точке х.
import math
class Derivate:
def __init__(self, func):
self.__fn = func
def __call__(self, x, dx=0.0001, *args, **kwargs):
return (self.__fn(x + dx) - self.__fn(x)) / dx
@Derivate
def df_sin(x):
return math.sin(x)
# df_sin = Derivate(df_sin) # в "лоб"
print(df_sin(math.pi/3)) # производная синуса 🧮
Итог: Функторы — это мощный инструмент, когда нужно сохранять состояние между вызовами или создавать умные декораторы! 🚀
А вы используете функторы в своих проектах? Делитесь в комментариях! 👇
P.S. Кто угадал результат во втором примере? 😄
🤯2🔥1
Всем доброе утро!☺️
А я пойду наверно скоро спать🤣 Восстановить режим пока не оч. получается.
Встал в 2 ночи и всё...не могу заснуть. Ну думаю что делать? пойду дорешаю несколько задачек по простой теме(позже пост). А среди них..опять прокрался злосчастный двусвязный список и нагородили новых методов еще.😬
Коротко о задаче:
Наверно 2.5+ часа решал.🫠🫠🫠
Часа полтора логика где-то + написание. Пошел ее проверил по своим тестам. Всё ок ✅
И еще где-то час ушло на то, что я решил "сэкономить" время, пропивав data-decriptor вместо трёх одинаковых property . Ну а че? Думал так красивее, быстрее и правильнее. Ага...🤡
Какого же было моё удивление, когда assert-ы в итоговой проверке не проходят. Хотя казалось бы...все случаи предусмотрел.
И в чем проблема была, как думаете???😢
Т.е....
В первом способе вручную создавалась имя атрибута, которое начиналось с двух подчеркиваний, например, __next.
➡️ Имя атрибута в словаре объекта: __next ⛔️
➡️ Что ожидали ассерты: _ObjList__next ✅
Почему не работало: Ассерты искали атрибут с именем
И вот из-за такой, казалось бы мелочи, тратится много времени и нервов на решение задач. Про уставшие глаза, желание забросить всё и сейчас я вообще молчу🧠Ну ничего движемся дальше.
Это мне напомнило момент про "генераторы списков". Раньше их тоже не любил, скорее даже не навидел. А все просто, потому что не понимал как ими пользоваться и тратил кучу времени на решение через такой способ. Проще было написать трёхэтажный цикл с условиями, чем один генератор. Но сейчас всё изменилось и пихаю это много где, и даже когда не надо😂
Вопрос к вам:
А какие у вас сейчас темы сложно даются? Интересно узнать, делитесь, не стеснятесь😼
А я пойду наверно скоро спать🤣 Восстановить режим пока не оч. получается.
Встал в 2 ночи и всё...не могу заснуть. Ну думаю что делать? пойду дорешаю несколько задачек по простой теме(позже пост). А среди них..опять прокрался злосчастный двусвязный список и нагородили новых методов еще.😬
Коротко о задаче:
Верно решили 3 114 учащихся
Из всех попыток 22% верных
Наверно 2.5+ часа решал.🫠🫠🫠
Часа полтора логика где-то + написание. Пошел ее проверил по своим тестам. Всё ок ✅
И еще где-то час ушло на то, что я решил "сэкономить" время, пропивав data-decriptor вместо трёх одинаковых property . Ну а че? Думал так красивее, быстрее и правильнее. Ага...🤡
Какого же было моё удивление, когда assert-ы в итоговой проверке не проходят. Хотя казалось бы...все случаи предусмотрел.
И в чем проблема была, как думаете???😢
def __set_name__(self, owner, name): # а это что еще за зверь?!
self.name = '__' + name # 1 способ - не подошел (хотя так показывали в теории)
self.name = f'_{owner.__name__}__{name}' # 2 cпособ(верный в моем случае)
Т.е....
В первом способе вручную создавалась имя атрибута, которое начиналось с двух подчеркиваний, например, __next.
➡️ Имя атрибута в словаре объекта: __next ⛔️
➡️ Что ожидали ассерты: _ObjList__next ✅
Почему не работало: Ассерты искали атрибут с именем
_ObjList__next, но дескриптор создал атрибут с именем __next. Python считает эти два имени абсолютно разными. Выходит обращался не по тому "адресу".И вот из-за такой, казалось бы мелочи, тратится много времени и нервов на решение задач. Про уставшие глаза, желание забросить всё и сейчас я вообще молчу🧠Ну ничего движемся дальше.
Это мне напомнило момент про "генераторы списков". Раньше их тоже не любил, скорее даже не навидел. А все просто, потому что не понимал как ими пользоваться и тратил кучу времени на решение через такой способ. Проще было написать трёхэтажный цикл с условиями, чем один генератор. Но сейчас всё изменилось и пихаю это много где, и даже когда не надо😂
Вопрос к вам:
А какие у вас сейчас темы сложно даются? Интересно узнать, делитесь, не стеснятесь😼
😱2
Прощаемся с <object at 0x...> раз и навсегда! 👋
Надоело видеть в консоли нечитабельную ерунду вроде <__main__.Cat object at 0x000002359011C160>? 🤔
Давайте сделаем вывод объектов Python понятным и информативным для человека!
В этом нам помогут магические методы
1️⃣
2️⃣
Пара важных нюансов: если не определен первый, то вызывается второй, иначе просто будет не совсем понятная лабуда в выводе.
Пример с котиками😼
И еще два довольно простых метода
По умолчанию они не определены, и их при их вызове получим ошибку. Но мы можем их легко переделать по себя, например вот так:
Надеюсь, этот мини-урок поможет вашему коду стать чуточку понятнее и красивее! ✨ Если было полезно, буду рад вашим реакциям — это лучшая мотивация для новых полезных постов.
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
🐱 — Котики — топ!
Надоело видеть в консоли нечитабельную ерунду вроде <__main__.Cat object at 0x000002359011C160>? 🤔
Давайте сделаем вывод объектов Python понятным и информативным для человека!
В этом нам помогут магические методы
__str__ и __repr__. Они "отрабатывают" в момент отображения объекта и превращают технический мусор в красивую строку. В чем разница между ними?1️⃣
__str__ — для пользователей (когда вы делаете print() или str()).2️⃣
__repr__ — для разрабов (в режиме отладки или консоли).Пара важных нюансов: если не определен первый, то вызывается второй, иначе просто будет не совсем понятная лабуда в выводе.
Пример с котиками😼
class Cat:
def __init__(self, name):
self.name = name
def __repr__(self):
return f'{self.__class__}: {self.name}'
def __str__(self):
return f'Имя кошки: {self.name}'
cat = Cat('Василий')
cat # в консоле выведет: <class '__main__.Cat'>: Василий
print(cat) # Имя кошки: Василий
str(cat) # Имя кошки: Василий
И еще два довольно простых метода
__len__, __abs__. По умолчанию они не определены, и их при их вызове получим ошибку. Но мы можем их легко переделать по себя, например вот так:
class Point:
def __init__(self, *args):
self.__coords = args
def __len__(self):
return len(self.__coords)
def __abs__(self):
return list(map(abs, self.__coords))
p = Point(1, -2)
print(len(p)) # 2
print(abs(p)) # [1, 2]
Надеюсь, этот мини-урок поможет вашему коду стать чуточку понятнее и красивее! ✨ Если было полезно, буду рад вашим реакциям — это лучшая мотивация для новых полезных постов.
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
🐱 — Котики — топ!
🔥5❤2
Симуляция работы программиста 👨💻
Если есть тут те, кто реально работает, скажите, насколько я попал? 🤔
В общем, вчера проснулся в 4 утра. Дальше — все как в тумане, неожиданно наступило 8 часов 😶 Время ехать в офис. Но так как я «работаю» удаленно, до 9:30 еще валялся и досыпал. Кайф!
Дальше открываю Jira Stepik. Смотрю задачи, беру ту, что «горит». Пару дней назад кое-что отложил, вот пришлось срочно доделывать. Разобрался с этим и приступаю.
В итоге просидел до 17:30, не считая перерывов на кофе и что-то сделать покушать. Получился целый рабочий день. Не думал, что может так затянуть! Раньше вообще размышлял, как можно столько часов подряд сидеть и кодить? А нет, оказывается, можно))
Пошел четко по плану. К моему удивлению, задача, которая на первый взгляд казалась самой легкой, отняла больше всего времени (целых 2.5 часа!). А самая непонятная (по описанию) была не такой уж и трудной.
В общей сложности сделал 7 задач. Забудьте про те 25 в день, про которые писал раньше 😂 Важно, сколько времени ты уделяешь работе, а не количество. Просто прошлым способом проще заставить себя начать.
Как только прошло 8 рабочих часов, ни минуты не задерживаясь, закрыл всё к чертям! 😎 Теоретически мог бы заниматься дальше, но чувствую, продуктивность была бы уже около-нулевая.
А у вас как проходят рабочие будни? Делитесь в комментах! 👇🙃
Узнали себя? - Ставьте лайк😈
Если есть тут те, кто реально работает, скажите, насколько я попал? 🤔
В общем, вчера проснулся в 4 утра. Дальше — все как в тумане, неожиданно наступило 8 часов 😶 Время ехать в офис. Но так как я «работаю» удаленно, до 9:30 еще валялся и досыпал. Кайф!
Дальше открываю
В итоге просидел до 17:30, не считая перерывов на кофе и что-то сделать покушать. Получился целый рабочий день. Не думал, что может так затянуть! Раньше вообще размышлял, как можно столько часов подряд сидеть и кодить? А нет, оказывается, можно))
Пошел четко по плану. К моему удивлению, задача, которая на первый взгляд казалась самой легкой, отняла больше всего времени (целых 2.5 часа!). А самая непонятная (по описанию) была не такой уж и трудной.
В общей сложности сделал 7 задач. Забудьте про те 25 в день, про которые писал раньше 😂 Важно, сколько времени ты уделяешь работе, а не количество. Просто прошлым способом проще заставить себя начать.
Как только прошло 8 рабочих часов, ни минуты не задерживаясь, закрыл всё к чертям! 😎 Теоретически мог бы заниматься дальше, но чувствую, продуктивность была бы уже около-нулевая.
А у вас как проходят рабочие будни? Делитесь в комментах! 👇🙃
Узнали себя? - Ставьте лайк😈
🔥5
Магические методы для арифметических операторов в Python 🔢
Знакомьтесь с основными магическими методами:
• add() – сложение
• sub() – вычитание
• mul() – умножение
• truediv() – обычное деление
• floordiv() – целочисленное деление
• mod() – остаток от деления
Лучше один раз увидеть, чем 100500 раз прочитать! 👀Внимание - очень много букв😁😁😁
Возьмем класс для работы со временем. Время храним в секундах (макс. 86400 - секунд в сутках):
Добавляем сложение ➕
Сначала научим складывать с числами:
Теперь работает:
Усложняем - складываем два объекта Clock:
Теперь:
Закончили? Неа! ❌
Добавляем обратное сложение 🔄
Теперь 100 + c1 работает. Почему кидает в add? Честно - я не до конца понял 😅
Но метод выше можно еще реализовывать например таким способом, тут всraddее:
Ну теперь уж точно всё, подумали вы. Нет😂😂 все еще не работает такой стиль c1 += c2🙈
Точнее работает, но не так. как хотелось бы. Мы пока что возвращаем(создаем) новый объект, а здесь новый эзкемпляр создавать не надо. Надо просто изменять на месте, для этого напишем так.
Изменяем объект на месте (не iadd новый)
Теперь c1 += c2 изменяет сам c1 🎯
Важный нюанс:
🟡 + создает новый объект
🟡 += изменяет существующий
Для изменяемых классов (как списки) можно всегда возвращать старый объект.
Другие методы делаются абсолютно аналогично.
Итог:
Теперь вы знаете, как работают операции в int, float, str! Все ошибки, которые вы видели - это как раз проверки в этих dunder методах.
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
🐍 — Здесь притаился питон.raddто объясниaddму
Знакомьтесь с основными магическими методами:
• 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 из прошлого поста сравнивать другими операциями, то вообще вылезут ошибки.
Но их можно легко исправить.
Пример:
Другие методы можете сами догадаться как прописывать, аналогично этому
Занимательный факт: можно дополнительно не реализовывать метод для сравнения на неравенство - (ne) , если уже реализован (eq). Как думаете почему? А если реализовали метод "<", сработает ли ">"?🧐🧐🧐
Подсказка: 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). Как думаете почему? А если реализовали метод "<", сработает ли ">"?🧐🧐🧐
Подсказка
🔥3
"Вайбкодер" vs Программист: Новая эра или просто хайп?
Смотрю сейчас вакансии и натыкаюсь вот на такую. Название "Вайбкодер" - интересно))
И сразу мысль: может я не тем занимаюсь и зря всё это учу? Чисто взял, научился создавать промпты для нейронки — и ты уже в IT? Кажется, что это легкий вход, особенно когда видишь "опыт не требуется" и "удаленка".
Но за этим хайповым названием скрываются вполне реальные технические требования: знание Python, работа с API, автоматизация, n8n, VS Code, OpenAI и Claude. Это не просто "поболтать с чат-ботом".
Всем, кому интересно полное описание - вот ссылка на вакансию.
Мое мнение - нет, это не замена классическому кодингу(хотя я задумался😅). Просто новая ниша. Учиться все равно нужно много. А вы как считаете?
Смотрю сейчас вакансии и натыкаюсь вот на такую. Название "Вайбкодер" - интересно))
И сразу мысль: может я не тем занимаюсь и зря всё это учу? Чисто взял, научился создавать промпты для нейронки — и ты уже в IT? Кажется, что это легкий вход, особенно когда видишь "опыт не требуется" и "удаленка".
Но за этим хайповым названием скрываются вполне реальные технические требования: знание Python, работа с API, автоматизация, n8n, VS Code, OpenAI и Claude. Это не просто "поболтать с чат-ботом".
Всем, кому интересно полное описание - вот ссылка на вакансию.
Мое мнение - нет, это не замена классическому кодингу(хотя я задумался😅). Просто новая ниша. Учиться все равно нужно много. А вы как считаете?
🤔5
Вычисление хеша для объектов класса в Python
Функция hash() в Python вычисляет хеш для неизменяемых объектов (immutable types):
Если вы попытаетесь взять хеш от изменяемых объектов (например, списка list), то получите ошибку TypeError: unhashable type: 'list'. 😱
Ошибка возникнет даже в том случае, если изменяемый элемент находится внутри неизменяемого объекта. Пример: элементом кортежа является список list. Тогда тоже будет ошибка "unhashable".
Некоторые свойства хешей:
1) Для одинаковых объектов хеши всегда одинаковы.
2) Обратное утверждение в общем случае не верно (разные объекты могут иметь одинаковый хеш — это называется коллизией).
3) Если хеши не равны, то и сами объекты точно не равны.
Зачем это вообще нужно? 🤔
Мы поняли, что такое хеш, теперь давайте разберемся, зачем он нам пригодился! Например, словари используют хеши для быстрого доступа к ключам для быстрого поиска. Ведь помните, словари в ключах требуют только неизменяемые типы данных? Словарь хранит ключи примерно в таком виде: (хеш_ключа, значение_ключа).
А это зачем нужно? Первоначально запись в словаре ищется по хешу, что занимает минимум времени. Далее, если вдруг хеши разных ключей совпали (коллизия), то тогда уже Python дополнительно проверяет объекты на равенство (==).
Хеширование на примере классов
Теперь рассмотрим, как это работает с пользовательскими классами:
Как думаете? какие будут хеши у этих экземпляров класса? Одинаковые, разные, или вообще ошибка?Разные!
Если p1 == p2 возвращает True, то хеши одинаковые. А если False, то разные(если нет коллизии). Почему так? Коллизия? Нет, все проще. По умолчанию Python вычисляет хеш на основе id объекта (его адреса в памяти) 🧠
Переопределение `__eq__` и `__hash__`
А что если мы переопределим метод
Тогда p1 == p2 станет True. Значит и хеши по идее равны. Но не все так просто) Вылезет ошибка. Хе-хе🙃. Когда мы переопределилиИными словами: после переопределения __eq__: Python автоматически делает класс unhashable (`__hash__ = None`), если мы не определили свой `__hash__`. Это защита от нарушения контракта между равенством и хешированием.
Чтобы это исправить, нужно обязательно переопределить метод
Что теперь скажете насчет хешей объектов p1 и p2?Равны! 🎉 Мы теперь вычисляем хеш не от самих объектов (их ID в памяти), а от их координат.
Пример использования в словаре
Например, если по такой логике создавать словарь👇, то получим только одну пару ключ: значение. А если мы не переопределяли эти методы, то получили бы две.
Таким образом, мы можем переопределять методы eq и hash под требуемую логику нашей программы. В данном случае, объекты класса Point с одинаковыми координатами воспринимаются как одинаковые объекты. 😏
А вы когда-нибудь сталкивались с ошибкой "unhashable type" в своих проектах? 😁
Функция 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() перестает работать. Т.к. перестает работать стандартный алгоритм вычисления хеша. Чтобы это исправить, нужно обязательно переопределить метод
__hash__ в нашем классе. Он и вызывается (логично, да?), когда мы пишем hash(p1).def __hash__(self):
return hash((self.x, self.y))
Что теперь скажете насчет хешей объектов p1 и p2?
Пример использования в словаре
Например, если по такой логике создавать словарь👇, то получим только одну пару ключ: значение. А если мы не переопределяли эти методы, то получили бы две.
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() ВСЕГДА возвращает True для любых экземпляров кастомного класса. Зачем тогда это нужно? 😅
А вот зачем - можно переопределить через магические методы!
Приоритет проверки:
1. Сначала ищется
2. Если его нет -
3. Если и его нет - всегда True
Всё просто! 🎯
Перейдем к практике:
Пусть есть такой простой класс Point:
модифицируем len()
изменяем bool()
Важный нюанс!
Теперь можно использовать в условных операторах: (bool() вызывается неявно)
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы в комментарии!
Когда к экземпляру класса применяется функция 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
Поехали разбираться с магией индексации: сделаем классы "как список" 🎩✨
Пусть есть такой класс студент, создадим его экземпляр
Думаю понятно как обращаться к элементам списка marks: st1.marks[2]
А если хочется напрямую? st1[2]. Перехочется😂 - очевидно получим ошибку.❌
Это можем поправить, добавив метод
Добавим метод
Думаю уже догадались какой остался и для чего:
Можно в эти методы дописывать необходимые вам проверки, чтобы как было задумано использовать пользовательский класс. Но и без этого будут вылезать ошибки, при неверном использовании, т.к. мы (в данном случае) будем уже работать со стандартными типами данных, в частности со списком.
Приведу пару примеров модернизации (смотреть в комменты)
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы в комментарии!
Пусть есть такой класс студент, создадим его экземпляр
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 месяца вы будете печатать вслепую уверенно. Вы сэкономите сотни, а скорее тысячи рабочих часов за свою карьеру! ⏳
В следующем посте я поделюсь списком лучших тренажеров, которые я лично использовал, и расскажу, где лучше всего учиться. Готовьтесь к бусту продуктивности! 🔥😈
А вы как считаете, нужный это навык или все-таки нет?🙃
1) ⚡️Скорость разработки: Быстрая печать = быстрое воплощение идей в код, что напрямую ускоряет реализацию проекта и поддерживает высокий темп работы.
2) 🎯 Фокус на логике: Навык позволяет перенести все ментальное внимание с механики ввода на решение сложных задач и проектирование алгоритмов.
3) 🧠 Снижение усталости: Автоматизация печати минимизирует умственное напряжение и физическое утомление глаз, позволяя дольше сохранять высокую концентрацию.
4) ✅ Повышение качества кода: Печать вслепую позволяет мгновенно обнаруживать и исправлять опечатки и синтаксические ошибки, так как взгляд фиксируется на экране.
Резюмируя: время, потраченное на освоение слепой печати (это занимает в среднем 2-4 недели регулярных занятий), — это лучшая инвестиция в себя, точка! 🚀
Я сам прошел этот путь, и могу подтвердить: результат того стоит. Этот навык не приходит сам собой, нужна практика, но эффект ошеломляющий. 💪
Начните практиковаться уже сейчас, и через 3 месяца вы будете печатать вслепую уверенно. Вы сэкономите сотни, а скорее тысячи рабочих часов за свою карьеру! ⏳
В следующем посте я поделюсь списком лучших тренажеров, которые я лично использовал, и расскажу, где лучше всего учиться. Готовьтесь к бусту продуктивности! 🔥😈
А вы как считаете, нужный это навык или все-таки нет?🙃
🔥9❤1👍1
Хотите так же? Смотрите ниже🙃
Печатать вслепую я начал давно, пришлось этому обучиться в школе, чтобы не отставать от других😬В подробности углубляться не буду, вот что мне помогло:
Для изучения техники печати:
• Тренажер Stamina: Проходил еще на sega с клавиатурой). Это изначально поставило мне верно пальцы и дало первый буст в печати. Кстати, если читать текст, там очень забавные фразы предлагают😁
• Соло на клавиатуре: После полного прохождения вы будете ненавидеть или любить фамилию Шахиджанян, но уж точно запомните :D). А если серьёзно, то считаю, что это основа основ для слепой 10-ти пальцевой печати!
• Codewar: Нет, это не про решение задач😄Относительно новый сайт. Завлекло то, что дает персональные рекомендации по улучшению и гемфикация процесса (но на любителя).
Для практики точности и скорости:
• Клавогонки: Куча разных текстов
• Monkeytype: Аналогично гонкам, но на англ.
• TypingClub: Очень много упражнений для eng. lang.
• Еще один сайт: Здесь ссылка именно на тренажер печати на Python.
Печатать вслепую я начал давно, пришлось этому обучиться в школе, чтобы не отставать от других😬В подробности углубляться не буду, вот что мне помогло:
Для изучения техники печати:
• Тренажер Stamina: Проходил еще на sega с клавиатурой). Это изначально поставило мне верно пальцы и дало первый буст в печати. Кстати, если читать текст, там очень забавные фразы предлагают😁
• Соло на клавиатуре: После полного прохождения вы будете ненавидеть или любить фамилию Шахиджанян, но уж точно запомните :D). А если серьёзно, то считаю, что это основа основ для слепой 10-ти пальцевой печати!
• Codewar: Нет, это не про решение задач😄Относительно новый сайт. Завлекло то, что дает персональные рекомендации по улучшению и гемфикация процесса (но на любителя).
Для практики точности и скорости:
• Клавогонки: Куча разных текстов
• Monkeytype: Аналогично гонкам, но на англ.
• TypingClub: Очень много упражнений для eng. lang.
• Еще один сайт: Здесь ссылка именно на тренажер печати на Python.
🔥11❤🔥2⚡1🤝1
🔄 Итераторы в Python: "магия"
Давайте разберемся, как работают итераторы в классах! Начнем с основы:
Для любого итерируемого объекта (например, список) мы можем создать итератор → с помощью next() читаем элементы последовательно → когда доходим до конца, получаем StopIteration
Пример с range():
Работает аналогично! 🔄 Вспомнили?
Тогда переходим к классам! 🚀
Честно: сам не очень люблю такие темы (наверно, потому что забываю что-то 😅), но знать НАДО!
Напомнило мне про боль в односвязных и двусвязных списках — уже решил штук 5-6 задач по ним за последнюю главу. Теперь выглядят не так страшно! 💪
Поехали разбираться! Создаем свой итератор — класс FRange (аналог range() для вещественных чисел):
Добавляем метод для получения значений:
Используем так (записи эквивалентны):
Важный момент! 🤔
Так происходит потому что функция next() неявно вызывает магический метод `__next.__` Соответственно, получаем то значение, которое возвращает метод, а объект fr выступает тут в роли итератора (объект, у которого есть магический метод next(), но и не только он).
Пробуем через for:
Получаем: TypeError: 'FRange' object is not iterable ❌
Почему? Не можем создать итератор для fr (не можем вызвать iter()).
Исправляем — добавляем
Нюанс: инициализацию self.value можно перенести в
Итог: двумя методами (
В комментах выложу еще один пример - продложение этого.
Как вам такие "магические" методы? 👇
🔥 — Огонь, приму на вооружение!
🤔 — Знал, но освежил память.
💬 — Есть вопросы - в комментарии!
P.S Если что-то не так написал по теории - поправьте)
Задача:, вам необходимо разработать итератор, который бы перебирал указанные столбцы двумерного списка. Список представляет собой двумерную таблицу из данных:
Для этого в программе необходимо объявить класс с именем IterColumn, объекты которого создаются командой:
где lst - ссылка на двумерный список; column - индекс перебираемого столбца (отсчитывается от 0).
Затем, с объектами класса IterColumn должны быть доступны следующие операции:
Решение:
def __iter__(self):
for row in self.__tables:
yield ( elem.data for elem in row)
__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
for row in self.__tables:
yield (
🔥2🤔2
Вчера утром с телефона зашёл на Степик. Вижу такую белиберду😑
Сначала ничего не понял, подумал вообще что сплю😂 Как оказалось - нет, даже ущипнул себя, для проверки как в фильмах😁 Я вообще без понятия как описание профиля превратилось в такое и как долго это уже у меня😐
Тут вижу два варианта: либо я ооооочень удачно уснул с телефоном на лице, либо платформа решила надо мной порофлить(теорию заговора никто не отменял)😑
Сначала ничего не понял, подумал вообще что сплю😂 Как оказалось - нет, даже ущипнул себя, для проверки как в фильмах😁 Я вообще без понятия как описание профиля превратилось в такое и как долго это уже у меня😐
Тут вижу два варианта: либо я ооооочень удачно уснул с телефоном на лице, либо платформа решила надо мной порофлить(теорию заговора никто не отменял)😑
😁5
Заход на ООП немного затянулся🐢
Начал за здравие, а заканчиваю... как говорится.., ну вы поняли😅
Но вот уже добрался до конца очередной главы. Ожидает последнее задание — очередное «испытание», на этот раз "магическое". Ещё даже не открывал, а уже чувствую "лёгкий" трепет ✨
Дело в том, что в этом курсе сложность задач можно измерять в минутах видеоразбора:
• Совсем простые — разбора нет 😊
• Лёгкие — видео до 6 минут 🧐
• Средние — 8-10 минут 😐
• Сложные — от 12+ 😰
Но раз на раз не приходится, могу лёгкую задачу решать пару часов, а сложная улетает меньше чем пол часа. Но чаще всего длина видео коррелирует со сложностью задачи.
И вот... Разбор ЭТОЙ задачи — 25+ минут. 🫣
Что же там такое должно быть, если преподаватель её почти полчаса разжёвывает? Моя фантазия рисует суровые картины.😐
Если я внезапно пропаду — вы знаете, где меня искать. Я ушёл в глубокий медитативный транс общения с Питоном. 🧘♂️💻
А у вас как дела с ООП? Давно прошли, сейчас штурмуете или путаетесь в трёх соснах?👇
Начал за здравие, а заканчиваю... как говорится.., ну вы поняли😅
Но вот уже добрался до конца очередной главы. Ожидает последнее задание — очередное «испытание», на этот раз "магическое". Ещё даже не открывал, а уже чувствую "лёгкий" трепет ✨
Дело в том, что в этом курсе сложность задач можно измерять в минутах видеоразбора:
• Совсем простые — разбора нет 😊
• Лёгкие — видео до 6 минут 🧐
• Средние — 8-10 минут 😐
• Сложные — от 12+ 😰
Но раз на раз не приходится, могу лёгкую задачу решать пару часов, а сложная улетает меньше чем пол часа. Но чаще всего длина видео коррелирует со сложностью задачи.
И вот... Разбор ЭТОЙ задачи — 25+ минут. 🫣
Что же там такое должно быть, если преподаватель её почти полчаса разжёвывает? Моя фантазия рисует суровые картины.😐
Если я внезапно пропаду — вы знаете, где меня искать. Я ушёл в глубокий медитативный транс общения с Питоном. 🧘♂️💻
А у вас как дела с ООП? Давно прошли, сейчас штурмуете или путаетесь в трёх соснах?👇
🔥2❤1
Все-таки этот "зверь" был не так страшен=)😅
Задание было написать игру крестики-нолики в ООП-шном стиле. Не знаю сколько точно времени заняло, но где-то 2 до 6 часов.⌛️ Мне кажется, если бы писал все через функции и взял например список для игрового поля, то было бы быстрее сделано. Но ничего... Надо учиться мыслить и немного в другом формате.
Первый раз сдаю, и не понимаю почему не принимает решение. Хотя в Pycharm всё работает и логика верна🤔 Оказалось банальная причина - опять немного не так прочитал. Такой косяк у меня частенько бывает😄 В общем, не заметил что is_human_win, is_computer_win, is_draw - это объекты-свойства(@property).
Изначально реализовал их как методы, и думаю чё не проходит то решение, всё вроде правильно.🤷♂️ Как только это исправил - всё заработало. Конечно не с 1 раза, (хотя можно было и с первого) просто достало, что assert-ы не проходило, и кидал после каждой мелкой поправки)) Хотя всё честно, логика то у программы была другая).
Вот так это выглядит:
🟦🟦🟦
🟦❌🟦
🟦⭕️🟦
Плюс в консоле подсказки и описания по игре🎮 В коментарий скину код. Можете попробоваться сразиться с компьютером и попытаться выиграть. Даже близко на ИИ не тянет)😂 Ну правильно, не на алгоритмы ж была задача. А продумывать код на 255 168 возможных партий, буду как-нибудь в другой раз)
Задание было написать игру крестики-нолики в ООП-шном стиле. Не знаю сколько точно времени заняло, но где-то 2 до 6 часов.⌛️ Мне кажется, если бы писал все через функции и взял например список для игрового поля, то было бы быстрее сделано. Но ничего... Надо учиться мыслить и немного в другом формате.
Первый раз сдаю, и не понимаю почему не принимает решение. Хотя в Pycharm всё работает и логика верна🤔 Оказалось банальная причина - опять немного не так прочитал. Такой косяк у меня частенько бывает😄 В общем, не заметил что is_human_win, is_computer_win, is_draw - это объекты-свойства(@property).
Изначально реализовал их как методы, и думаю чё не проходит то решение, всё вроде правильно.🤷♂️ Как только это исправил - всё заработало. Конечно не с 1 раза, (хотя можно было и с первого) просто достало, что assert-ы не проходило, и кидал после каждой мелкой поправки)) Хотя всё честно, логика то у программы была другая).
Вот так это выглядит:
🟦🟦🟦
🟦❌🟦
🟦⭕️🟦
Плюс в консоле подсказки и описания по игре🎮 В коментарий скину код. Можете попробоваться сразиться с компьютером и попытаться выиграть. Даже близко на ИИ не тянет)😂 Ну правильно, не на алгоритмы ж была задача. А продумывать код на 255 168 возможных партий, буду как-нибудь в другой раз)
🔥5❤2