Комментарии 25
Посмотрел исходный код, либо здесь я ничего не понял. Все равно не понял. Непонятные названия переменных, лишние абстракции, овер дофига уровни вложенности. не соблюдение pep8(понимаю, автор пишет всего лишь 2 недели, но пусть хотя бы с snake_case можно же писать код), странные тесты.
Спасибо за отзыв.
С кейсом переменных понятно. В разных средах требования и стандарты разные. Я привык по другому и, на мой взгляд роли особой это в данном случае не играет.
А по остальным пунктам, если можно, подробнее.
Интересует: что за лишние абстракции и уровни вложенности. О чем речь?
Если можно, то с примером как оно "должно быть".
Интересно.
не соблюдение pep8(понимаю, автор пишет всего лишь 2 недели, но пусть хотя бы с snake_case можно же писать код)
Время знакомства здесь не сильно влияет. Все популярные языки которые дожили до нынешних дней уже обросли инструментами автоматизации. Можно соблюдать большинство стандартов, даже не зная их, просто следую предупреждением инструментов.
> Все же этот код я писал как знакомство я языком и буду рад любым отзывам о его качестве.
В статье великий мастер божественного языка снизошёл для того, чтобы посмотреть языки простых смертных.
Я уж было думал, что угадал, этот божественный язык по именованию классов, но там префикс I используется для интерфейсов, а не для абстрактных классов.
До Питона я не добрался, сломался на общих вещах.
Много лишней сложности. Класс (ISettings) принимает в конструктор экземпляр своего типа и проксирует его методы. Это можно решить с помощью наследования.
Класс Settings в котором почти в каждом методе стоит if по типу, это странное использование ООП.
main.py 500 строк кода, где логика размазана по разным местам.
Цикломатическая сложность зашкаливает.
Я иногда помогаю знакомым с Питоном, поэтому дочитал статью до конца. Не думаю, что она будет полезна новичкам.
Если использовать форматирование для С подобных языков, то оказывается, что пробелов там не сильно меньше и еще куча фигуруных скобок. Ваши претензии к пробелам мне не понятны. Привычка приходить в новый язык со своим самоваром мешает его изучению.
В статье великий мастер божественного языка снизошёл для того, чтобы посмотреть языки простых смертных.
Откуда такой сарказм?
Впрочем, я наверное догадываюсь, Вы восприняли выражение "птичий" как оскорбление?
Дико извиняюсь, автопилот.
Просто это выражение мне стало привычным очень много лет назад. Оно не несет никакой негативной нагрузки и является своеобразным "техническим термином", обозначающим не компилируемый язык, как правило узко-нишевый. В 80х, когда начали активно развиваться скриптовые языки, в том числе мы писали их сами, часто звучало: "Как ты это сделал?" - "Написал свой птичий язык". Это просто было кратчайшее пояснение.
В общем, как-то так.
Еще раз извиняюсь, если оскорбил.
По остальному тексту многое непонятно.
Что такое "цикломатическая сложность" я прочитал, но не уверен что понял к какой именно части относится претензия. Вы не могли бы ткнуть пальцем?
Класc ISettings я использовал не как прокси (хотя он именно это), а в попытке хоть как-то разделить реализацию и описание т.к. я так и не понял какими методами это делается. Описаний в питоне нет (собственно я знаю только паскаль и С с ними), поэтому мне приходилось либо тащить весь код в 1 файл, либо это не собиралось из-за циклических зависимостей.
Претензию про main.py я тоже не понял.
Этот файл - единый сборник примеров использования не особо связанных друг с другом.
Можно было бы написать с десяток разных файлов с раздельными примерами, в этом проблема?
Мне показалось, что будет проще запустить одного бота и сразу посмотреть все (+-) что может либа, чем запускать кучу тестов.
Про количество кода это была шутка.
Жаль что опять оказалась, видимо, оскорблением.
Хотя все претя писать объект класса - это раздражало. Я еще не использовал языков, в которых были бы объекты, но в их методах нужно было бы всегда явно его писать.
Откуда такой сарказм?
Да и сама статья написана в свободном стиле.
Чтобы говорить о сложности её нужно измерить. https://pypi.org/project/radon/
Уровень С и D уже считается избыточно сложным.
В данном коде вы очень злоупотребляете вложенными условиями, за счет этого его сложно читать.
PS J:\projects\LinearBotLib> radon cc . -s -n C
bot.py
M 468:4 BotChat.waitProcess - D (21)
M 573:4 BotChat.logicStart - C (11)
bot_imessage.py
M 135:4 BotIMessage._display - C (15)
main.py
F 26:0 logic_MENU_Form - C (12)
F 149:0 logic_MENU_Animation - C (12)
Класc ISettings я использовал не как прокси (хотя он именно это), а в попытке хоть как-то разделить реализацию и описание т.к. я так и не понял какими методами это делается.
Не очень понимаю, что есть описание и зачем оно нужно.
Если хочется сделать, что то типа интерфейса, то листайте результаты поиска гугла на эту тему. Рано или поздно наткнетесь на абстрактные классы.
Хотя в данном случае можно и без интерфейсов обойтись. Одна из фишек Питона, это можно сделать потом, не меняя код в других файлах.
либо это не собиралось из-за циклических зависимостей
В файле три сущности. Вроде нет там циклических зависимостей.
И ситуация когда класс наследует интерфейс и его же экземпляр получает в конструктор мне кажется странной.
И ситуация когда класс наследует интерфейс и его же экземпляр получает в конструктор мне кажется странной.
Это класс, который может быть как полноценным независимым хранилищем, так и ссылкой на ветку в уже существующем.
# независимое хранилище
s = Settings()
# Ссылка на ветку my.internal.object в другом хранилище
sub = s.sub_cfg('my.internal.object')
В первом случае объект сам манипулирует данными, во втором делегирует все действия по цепочке владельцев оригинальному хранилищу.
Это сделано для того, чтобы в разных объектах ветви могли пересекаться.
К примеру некий "UsersArray" работает с веткой "users", каждый отдельный "User" с ветками внутри нее, а сессия все это хранит целиком.
Любые изменения вносятся в единое место.
Скорее всего это уже 100 раз реализовано и, возможно, более "правильно".
Я это писал в первый день, разбираясь с rtti питона т.к. было интересно.
Ясно.
В этих примерах функции с вложенными функциями, что я использовал как своего рода private.
Кроме BotChat.waitProcess, в которой просто вложенность для отладочного логирования в виде:
D:\MYTESTS\LinearBotLib\bot.py[930]::ask( ask ) {
D:\MYTESTS\LinearBotLib\bot.py[895]::popup( popup ) {
new msg 3642 text Which version do you want to test?
SM: add KBD waiter <bot.Waiter object at 0x000000F1E8F923E0>
CH: add waiter[0, m: False] : <bot.Waiter object at 0x000000F1E8F923E0>
CH: waiter added[1]
D:\MYTESTS\LinearBotLib\bot.py[303]::_OnPopupMessage( msg, 3642 ) {
M: del waiter
CH: del waiter 1 m: False w: <bot.Waiter object at 0x000000F1E8F923E0>
CH: waiter deleted 0
PM: add waiter
CH: add waiter[0, m: True] : <bot.ModalWaiter object at 0x000000F1E8F923E0>
CH: waiter added[1]
waiting modal True
D:\MYTESTS\LinearBotLib\bot.py[472]::waitProcess( logic, True ) {
WP waiters 1
PM: msg OK: <bot_types.BotKeyboardResult object at 0x000000F1E8F91780>
thisMsg
WP 0 modal True rc True
notify_complete
WP ret 0
}
PM: lrc: <bot_types.BotKeyboardResult object at 0x000000F1E8F91780>
}
D:\MYTESTS\LinearBotLib\bot.py[768]::delete( msg, <bot.BotMessage object at 0x000000F1E8F92050> ) {
del= 3642
}
}
}
Если выкинуть вложенность, то радон становится почти счастлив:
D:\MYTESTS\LinearBotLib>radon cc . -s -n C
bot.py
M 468:4 BotChat.waitProcess - C (12)
M 573:4 BotChat.logicStart - C (11)
bot_imessage.py
M 135:4 BotIMessage._display - C (15)
main.py
F 26:0 logic_MENU_Form - C (12)
F 149:0 logic_MENU_Animation - C (12)
settings.py
F 540:0 pSINGLE - C (13)
Вообще, разумеется, разодрать ее на куски для удовлетворения радона, конечно, можно.
Просто я обычно в коде исхожу из следующего, раздирать функции надо если: функция сильно не влезает на экран, имеет длинную сложную логику, длинный куски кода в ветках логики.
В данном случае, функция у меня помещается на экран, а если "сфолдить" ее 3 независимые части, то будет вообще 4 строчки (как это видно в IDEA).
async def waitProcess(self, message: Message_t = None, data: types.CallbackQuery = None):
"""Process received data or message thru waiters queue"""
if not message and not data: return False
with PROC('logic', self.logicWorking):
# start/restart bot logic
if message and message.text[0] == '/': ...
# bot logic is down
if not self.logicWorking: ...
# have no bot logic, nothing to do
if not self.logicWorking: return
# dispatch events
LOG('WP', 'waiters', len(self.waiters))
if len(self.waiters): ...
На мой вкус не заслуживает упрощения.
Это я не ради спора, просто поясняю.
Вообще, разумеется, разодрать ее на куски для удовлетворения радона, конечно, можно. Просто я обычно в коде исхожу из следующего, раздирать функции надо если: функция сильно не влезает на экран, имеет длинную сложную логику, длинный куски кода в ветках логики.
Радон более портируемая метрика чем размер экрана и шрифта.
Я разделяю по функциям по тому же признаку и еще когда хочу отделить разные уровни логики, какие-нибудь манипуляция с индексами или библиотеками и бизнес логику. Например вынести `message and message.text[0] == '/'` в is_chat_message(message)
Отличие в том, что у меня более жёсткие критерии.
Для меня ваш код выглядит слишком сложным, для его чтения требуется больше усилий чем можно было бы.
Я не знаю, что вы понимаете под длинными путями в ветках логики. Вот самая глубокая вложенность. Человеческая кратковременная память может держать в себе 7 +- 2 элементов.
Здесь уже 7 уровней вложенности.
with ... if ... with ... while ... try ... if ... if
В этих примерах функции с вложенными функциями
Это называется контекстный менеджер. Посмотрите в документации для него есть простой синтаксис, без класса.
Претензию про main.py я тоже не понял. Этот файл - единый сборник примеров использования не особо связанных друг с другом.
Не сразу понял задумку с несвязанных объектах.
Размазывание в том каждая функция, например logic_MENU состоит из двух частей, которые должны быть согласованны между собой.
Зачем заставлять пользователя писать эти `while True` блоки, и давать ему возможность делать несогласованные части? Это всё от пользователя можно спрятать внутри библиотеки.
Весь main может состоять из создание экземпляров Menu и вызова строчки run_bot(menu).
class Item:
def __init__(self, user_text, handler): ...
class Menu:
def __init__(self, title, items: list[Item]): ...
---- main.py ---
from ... import Menu, Item
from ... import run_bot
menu = Menu(.....)
run_bot(menu)
Зачем заставлять пользователя писать эти
while True
блоки, и давать ему возможность делать несогласованные части? Это всё от пользователя можно спрятать внутри библиотеки.
Вы вообще смысла моего кода не поняли.
Смысл как раз в том, что пользователь общается с функционалом бота полностью в линейном режиме, как если бы он использовал консоль для ввода input-ом, и выводом print-ом.
Никакие дополнительные классы не нужны.
await chat.menu()
- это уже "класс" меню. Законченный. В этой строчке он отрисует свой интерфейс в телеграме, дождется реакции пользователя и вернет то, что он там выбрал. Сюда же.
Пользователя никто циклы писать не заставляет - он может реализовывать свою логику как ему угодно, хоть создавая классы, хоть делая циклы.
Весь main.py - это сплошной код "пользователя".
О телеграме, его протоколе, способах взаимодействия с ним и особенностях пользователь не обязан знать практически ничего.
Для реализации всего что ему надо, по сути, достаточно BotChat.say
, BotChat.menu
, BotChat.ask
и BotChat.delete
. Для чуть более сложных "интерфейсов" еще и BotIMessage. По сути все.
Вы вообще смысла моего кода не поняли.
Звучит двусмысленно :)
Пользователя никто циклы писать не заставляет - он может реализовывать свою логику как ему угодно, хоть создавая классы, хоть делая циклы.
Вложенное меню так часто используется, и всегда с циклами.
Мне паттерн `while true; rc = await chat.menu(...); if rc.data == ...` не нравится, слишком много кода. Это можно упрятать в библиотеку. Да, я ожидаю чтобы вложенные меню были внутри, библиотеки, а не собирались по кусочкам снаружи.
Хотя все претя писать объект класса - это раздражало. Я еще не использовал языков, в которых были бы объекты, но в их методах нужно было бы всегда явно его писать
Этот подход соответствует философии языка. Explicit is better than implicit.
Привыкните перестанет раздражать.
Вопрос
1) я хочу сделать все тоже самое , но не от бота , а от своего акка, со свои логином паролем. можно ли такое сделать? на сколько мне надо всё переделывать? Ваш "захваленный АПИ телеграмма", он тот же если работать от своего акка?
2) вы много работали с media , если у телеги CDN? можно ли как то взять url медия? Должен же быть, как то же веб версия телеги работает с этим.
Телеграм не имеет такого апи для работы от имени пользователя.
Для этого, есть специальная библиотека (бинарная) от телеграма, которая скромно предлагает некоторые вещи. Пример из этой статьи вообще не подойдёт. И переделывать придётся абсолютно всё.
Не говоря уже о том, что пример из статьи сомнительный.
Первый абзац - это неправда.
С вторым спорить не могу.
Это правда. Телеграмм не представляет прямого апи, как с ботом, для работы с пользователем. Это факт. Попробуйте изучить вопрос получше.
Для работы от имени пользователя, телеграмм представляет бинарную библиотеку-клиента. Где сам реализовывает всё, что считает нужным. И только эту библиотеку можно использовать для работы от имени пользователя
Я бы даже и ничего не писал, но вдруг кто поверит.
Поизучай на досуге прям первый абзац вот отсюда: https://core.telegram.org
Я знакомому уже даж болванку клиента на этой "бинарной" либе написал....
*рука лицо*.
Попробуйте чуть глубже почитать, а не только первый абзац. Посмотрите примеры использования этой библиотеки. И наконец, дойдите до момента, когда эта библиотека подключается к вашему языку.
Или напишите, свое классное приложение на питоне, а потом запустите этот скрипт на чужом компе, где рядом не будет tdjson.dll библиотеки (если речь про win).
Или, хотя бы, вот это откройте https://github.com/alexander-akhmetov/python-telegram/blob/master/telegram/tdjson.py
я хочу сделать все тоже самое
Учитывая остальное сообщение: это как?
Это либа для реализации какой-то логики.
Как это применимо "от своего аккаунта"? Что именно вы хотите сделать?
но не от бота , а от своего акка, со свои логином паролем. можно ли такое сделать?
Для работы с телеграмом есть 2 возможности: создать бота, который делает какие-то действия автоматически и создать клиента телеграма (т.е. аналог того клиента, которым Вы пользуетесь).
Во втором случае, если делать что-то узко-специализированное свое, то да, можно сделать софт, который будет реализовывать клиента с автоматическим подключением именно Вашего телефона, и выполнять какие-то действия от Вашего имени.
Если не секрет: зачем?
Я не понимаю, чем вам не угодила система пулинга, от которой, к слову, вы ни куда и не ушли.
Дополню ваши полученные знания:
В пуллинге есть бесконечный цикл, но запросы там не шлются один за другим несколько раз в секунду. Пулинг использует "долгие запросы". Это значит, что сервер, при получении запроса не отвечает длительное время (тянет запрос), пока не появятся данные. Это может продолжаться до 30 секунд. Т.е. если сервер не имеет данных, запрос будет выполняться 30 секунд, если данные есть на момент запроса, он выполнится сразу. Так что нечего бояться пулинга.
Пулинг, по сути это та же система событий, стой лишь разницей, что она не разделяет события по разным сущностям (пользователь, канал). Разделение должен реализовать разработчик. И это делается достаточно просто.
Кажется, что вы изобрели велосипед, но с октагональными колесами.
Вы вообще не поняли ни о чем статья, ни для чего весь написанный код.
Скорее всего, увидев первое же слово, которое Вы посчитали оскорблением, остальное не читали вообще, либо сквозь кровавую пелену агрессии.
Пролистайте чуть выше, я уже в комментарии пояснил, что цели оскорбить кого-то у меня не было.
Если по делу, то у меня не было цели "уйти от пулинга". Это не реализуемо в принципе.
Моя цель была избавить пользователя даже от мысли что он есть.
Код пользователя в моем примере - это ТОЛЬКО файл main.py до строчки:
botSession = BotSession(dp, Logic)
Какое оскорбление? О чем вы? И какая агрессия, простите?
Зачем избавлять разработчика от пуллинга? Ещё и просто более его запутывая. С пуллингом он все четко понимает. Вот мы забираем событие, вот обрабатываем. Всё линейно и просто. В вашем же коде что-то происходит где попало. Что и почему не понятно. Вот смысл моего комментария.
Создаем библиотеку для бота telegram