Пытаюсь устроиться на работу #1 Тестовое задание на pyZMQ

от автора

Эта статья только первая из цикла «прохожу тестовые задания». Подобными заметками я хочу показать другим начинающим программистам с чем им придется столкнуться при собеседованиях на работу. Сам я изучаю питон(и не только) уже порядка 4 лет, но это только теория с практикой на своих пет проектах, что как оказалось с реальным программированием не имеет ничего общего. Итак хватит лирики.

Задача и попытки её решить

Сразу скажу, компанию и какие либо имена я называть не буду. Задание было довольно простое, написать связку клиент сервер на сокетах с помощью pyZMQ для транслирования видео. Проблема нарисовалась сразу, я ни разу не работал с этой библиотекой. И так я пошел смотреть уроки, читать документацию и смотреть примеры других людей на гитхабе с использованием этого фреймворка. Что ж для начала решил сделать простой стрим, который посылал бы числа на клиента, а тот их выводил на экран, всё просто. Но ZMQ так не думает, сразу возникли проблемы с кодировкой. У меня банально не работала элементарная связка send_string и recv_string. Я даже решил создать топик на stackoverflow. И о чудо мне никто не ответил. Отчаявшись, я полез на гитхаб в поисках какой либо реализации клиент серверного приложение. Там то я нашел ответы на мои вопросы. надо посылать сразу pyobject и это так же решило проблему с видео, вообщем после череды багов я сделал следующие.

Оно и правда работает
Оно и правда работает

Сервер:

import zmq import cv2  context = zmq.Context() socket = context.socket(zmq.PUSH)  socket.bind("tcp://*:8000") print('server started...')  try:     while True:         # получим объект видео         cap = cv2.VideoCapture('example.mp4')         while (cap.isOpened()):             # разбиваем по фреймам             ret, frame = cap.read()             if ret:                 # передаём по одному фрейму, один фрейм это картинка                 socket.send_pyobj(frame)             else:                 # Сместим курсор на 0 фрейм                 cap.set(cv2.CAP_PROP_POS_FRAMES, 0)                 continue finally:     socket.close()

Клиент:

import zmq import cv2  context = zmq.Context() socket = context.socket(zmq.PULL)  socket.connect("tcp://localhost:8000") print("client started")  # Настройки для отображения видео down_width = 400 down_height = 400 down_points = (down_width, down_height) print('Чтобы закрыть видео нажмите q') try:     while True:         image = socket.recv_pyobj()         image = cv2.resize(image, down_points, interpolation=cv2.INTER_LINEAR)         # показываем по одной картинке, что в итоге сложится в видео         cv2.imshow('frame', image)          if cv2.waitKey(1) & 0xFF == ord('q'):             break  finally:     cv2.destroyAllWindows()     socket.close()

По сути мы разбиваем видео на фреймы и передаём их поочередно клиенту, он их по одному воспроизводит и получается вроде как стрим. А ну и когда мы доходим до конца видео, то переносим курсор cap.set(cv2.CAP_PROP_POS_FRAMES, 0) на начальный фрейм и всё по новой. Вроде и видос транслируется, и даже поток есть, тз сделано.

Тут то и начинаются проблемки, по сути тз мне дали из одного предложения, но с одной устной поправкой, есть пространство к творчеству. Многие из вас скажут, тестовое задание и нужно для того чтобы выпендриваться и показать вообще все свои навыки в улучшение кода. И я скажу, что вы правы, но к сожалению, за 4 года в университете меня научили только решать простенькие алгоритмы и не более.

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

Что ж тут не так?

Миша, всё х**ня, давай по новой. Ладно всё не так плохо, вот прямая цитата

«По поводу проекта тестового, если коротко, в целом задание выполнено, но в очень узком формате, хотелось бы что бы ты учел ошибки возникающие например при обрыве соединения, или при запуске нескольких серверов, также неплохо было бы добавить логирование, чтобы понимать что вообще происходит, и документирование кода тоже хотелось бы видеть»

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

Я стал лучшим программистом, чем был вчера. Наверное…

С логированием всё понятно, его нужно добавить и вообще практика хорошая, лучше чем пользоваться принтами. В качестве инструмента, я не стал брать стандартную библиотеку logging, так как все говорят, что она хорошая, но не достаточно. Loguru мой выбор. Спасибо гайдерам на ютубе и хабру за пояснение теории логирования.

Окей, логи написаны, что там дальше? Обрывы соединения и несколько серверов. Не понятно зачем дублировать это, если библиотека zmq уже об этом позаботилась за нас, ну ладно, добавим обработку подобных ситуаций, чтобы программа не падала, а только оповещала нас об событии. Обернем инициализацию сокета в try except и добавим логи к этому.
Готово:

    try:         context = zmq.Context()         socket = context.socket(zmq.PUSH)         logger.debug('Создание сокета')         socket.bind("tcp://*:8000")         logger.debug('Сервер запущен...')     except Exception as e:         logger.error(f'{e}')         return

Окей теперь у нас нельзя включить 2 сервера, до этого тоже нельзя было, так что не понятно что я вообще сделал, ну хотя бы логируется. С разрывами соединения вообще не понятно, по сути связка Push pull работает по принципу сервер отправляет и кто угодно может присоединиться и принимать, если принимать некому то ничего и не отправляют. Короче, надеюсь в комментариях мне подскажут, что я должен был сделать в рамках данной библиотеки.

Последние это документирование кода, тут сложно придумать что-то новое, так как еще при когда я сдавал задание у меня уже были комментарии в коде, а README в гитхаб выглядел так:

Вроде достаточно чтобы запустить и оно работало
Вроде достаточно чтобы запустить и оно работало

Окей, добавлю еще комментариев в код, чтобы даже человеку никогда не работавшему с библиотекой было плюс минус ясно, что я делаю, хотя считаю это избыточным и загрязняющим код.

Результат

Сервер:

import zmq import cv2 from loguru import logger  # настройки логгера, а именно папка сохранения логов, в каком виде вести логоирование, через какое время удалять логи logger.add('logs/server.log', format='{time} {level} {message}', level='INFO', rotation='1 week')   def main():     try:         # Создание сокета         context = zmq.Context()         socket = context.socket(zmq.PUSH)         # Привязка к сокету определенного хоста         logger.debug('Создание сокета')         socket.bind("tcp://*:8000")         logger.debug('Сервер запущен...')     except Exception as e:         logger.error(f'{e}')         return      try:         logger.info(f'Начало трансляции')         while True:             # получим объект видео             cap = cv2.VideoCapture('example.mp4')             while (cap.isOpened()):                 # разбиваем по фреймам                 ret, frame = cap.read()                 if ret:                     # передаём по одному фрейму, один фрейм это картинка                     socket.send_pyobj(frame)                 else:                     # Сместим курсор на 0 фрейм                     logger.debug('Видео началось заново')                     cap.set(cv2.CAP_PROP_POS_FRAMES, 0)                     continue     except Exception as e:         logger.error(f'{e}')     finally:         # закрываем сокет         socket.close()   if __name__ == '__main__':     main()

Клиент:

import zmq import cv2 from loguru import logger  # настройки логгера, а именно папка сохранения логов, в каком виде вести логоирование, через какое время удалять логи logger.add('logs/client.log', format='{time} {level} {message}', level='INFO', rotation='1 week')   def main():     # Соединение с сервером     context = zmq.Context()     socket = context.socket(zmq.PULL)      socket.connect("tcp://localhost:8000")     logger.debug("Присоеденились")      # Настройки для отображения видео     down_width = 400     down_height = 400     down_points = (down_width, down_height)     print('Чтобы закрыть видео нажмите q')     try:         while True:             # Принимаем по картинке             image = socket.recv_pyobj()             image = cv2.resize(image, down_points, interpolation=cv2.INTER_LINEAR)             # показываем по одной картинке, что в итоге сложится в видео             cv2.imshow('frame', image)              if cv2.waitKey(1) & 0xFF == ord('q'):                 logger.info('Пользователь отключился')                 break     except Exception as e:         logger.error(f'{Exception}')      finally:         # Закроем все окна и все сокеты         cv2.destroyAllWindows()         socket.close()   if __name__ == '__main__':     main() 

Заключение

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

А я пойду и дальше стучаться к компаниям, пытаясь устроиться на работу, выполнив тестовое задание, попутно конспектирую сиё действо сюда.


ссылка на оригинал статьи https://habr.com/ru/post/682752/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *