From 7cd101bb8f6cb80548b6a1b20a0d6d751bf45988 Mon Sep 17 00:00:00 2001 From: Alina Date: Wed, 17 Oct 2018 12:34:44 +0300 Subject: [PATCH 01/11] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D0=BA=D1=83?= =?UTF-8?q?=20=D1=81=D0=BE=20=D1=81=D1=85=D0=B5=D0=BC=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d2ec80c6..011006db 100644 --- a/README.md +++ b/README.md @@ -108,3 +108,4 @@ days_before_start ? 1)от источника данных; 2)от формата вывода в файл. +![alt text](https://devman.org/assets/images/7_40__data_flow.png) \ No newline at end of file From 237698ff34a8319af0bfe987eda94a62e8cda8e6 Mon Sep 17 00:00:00 2001 From: dvmn-tasks <44063469+dvmn-tasks@users.noreply.github.com> Date: Tue, 7 Jul 2020 15:10:43 +0300 Subject: [PATCH 02/11] Update image link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 011006db..60388a41 100644 --- a/README.md +++ b/README.md @@ -108,4 +108,4 @@ days_before_start ? 1)от источника данных; 2)от формата вывода в файл. -![alt text](https://devman.org/assets/images/7_40__data_flow.png) \ No newline at end of file +![image](https://dvmn.org/filer/canonical/1594117412/678/) From 14a20819d056461f3e5db5a93864d7200db3e538 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 17 Oct 2018 12:33:04 +0300 Subject: [PATCH 03/11] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=BE=D1=8F=D1=81=D0=BD=D1=8F=D1=8E=D1=89=D0=B8?= =?UTF-8?q?=D0=B9=20=D1=82=D0=B5=D0=BA=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 60388a41..1c75b800 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,12 @@ безгранично гибкий код — это как сферический конь в вакууме. Теоретически возможен, но практической ценности не несет. +Если мы заранее будем знать как изменятся требования к коду через год, два или +десять лет, то уже сейчас можно заложить необходимые механизмы расширения +функциональности. К сожалению, такие сведения сложно получить, а зачастую +просто невозможно. Приходится опираться на свой опыт, опыт товарищей, +прорабатывать разные сценарии развития проекта, подстраховываться. + Один из часто встречающихся и оправданных приемов — это отделение обработки данных от процесса ввода/вывода. Рассмотрим несколько примеров. @@ -108,4 +114,14 @@ days_before_start ? 1)от источника данных; 2)от формата вывода в файл. + ![image](https://dvmn.org/filer/canonical/1594117412/678/) + +Кроме того, часть кода удалось превратить в чистые функции, что облегчит +тестирование и повторное использование. + +Стратегия по отделению операций ввода/вывода от обработки данных встречается +повсеместно, в самых разных программах: от небольших скриптов до серьезных и +крупных проектов. Это один из базовых приемов, нужно уверенно им владеть. + + From dcb83ea23d1ccc883d38dd8b11236857acccd3ad Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 17 Oct 2018 12:33:38 +0300 Subject: [PATCH 04/11] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1c75b800..8763cc18 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,8 @@ days_before_start ? ![image](https://dvmn.org/filer/canonical/1594117412/678/) Кроме того, часть кода удалось превратить в чистые функции, что облегчит + +Кроме того, часть кода удалось превратить в [чистые функции](https://devman.org/encyclopedia/decomposition/decomposition_pure_functions/), что облегчит тестирование и повторное использование. Стратегия по отделению операций ввода/вывода от обработки данных встречается From acf4937b5ec9f978a79da73ac4d0cf7e229097bf Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Wed, 6 Aug 2025 10:41:27 +0300 Subject: [PATCH 05/11] =?UTF-8?q?=D0=9E=D1=84=D0=BE=D1=80=D0=BC=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D1=82=D1=8C=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8763cc18..76cdc5ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Отделяйте ввод/вывод от обработки +# Отделяйте ввод/вывод от обработки Ключевой критерий качества кода — это стоимость внесения в него изменений. @@ -16,7 +16,7 @@ Один из часто встречающихся и оправданных приемов — это отделение обработки данных от процесса ввода/вывода. Рассмотрим несколько примеров. -Пример. Подбор онлайн-курса +## Пример. Подбор онлайн-курса По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из @@ -106,8 +106,7 @@ days_before_start ? и повторной отладки всей программы от начала до конца, ведь изменения локальны и изолированы. -Вместо заключения - +## Вместо заключения В результате мы пришли к ситуации, когда логика обработки данных слабо зависит: From c5308b1df87ae103b8020d838404e9be9e51f51a Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Wed, 6 Aug 2025 10:57:10 +0300 Subject: [PATCH 06/11] =?UTF-8?q?=D0=BE=D1=84=D0=BE=D1=80=D0=BC=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B8=20=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 76cdc5ce..6c20d2c4 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,18 @@ ## Пример. Подбор онлайн-курса - По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода: -def get_courses_list(courses_url): +```def get_courses_list(courses_url): html = fetch_html(courses_url) if html: # .... parsing logic return courses_list else: print("can't load list of courses") - exit() + exit()``` + Теперь примерим на себя роль провидца и подумаем какой функционал потребуется через месяц: @@ -58,7 +58,7 @@ HTML разметкой вместо courses_url. Вуаля, мы решили Пойдем дальше. Код другой функции: -def get_course_info(html): +```def get_course_info(html): # ... parsing logic rating = soup.find_all('div', attrs={'class': 'ratings-text'}) @@ -70,7 +70,7 @@ def get_course_info(html): # .... parsing logic - return course_data + return course_data``` Что может произойти с кодом дальше? Если рейтинга нет — надо искать его на другом сайте. @@ -83,7 +83,7 @@ rating yet" можно переместить туда где данные по Та же функция, часть вторая, последняя: -def get_course_info(html): +```def get_course_info(html): # ... more parsing logic is here # number prefix is usefull for simple sorting data before output to xlsx @@ -93,7 +93,7 @@ def get_course_info(html): '3_language': language, '4_weeks': duration, "5_rating": rating - } + }``` Сразу возникают вопросы. А если нужна еще одна выгрузка в формате csv, с другим порядком столбцов, как это сделать? Как заменить столбец 2_date на days_before_start ? @@ -109,9 +109,8 @@ days_before_start ? ## Вместо заключения В результате мы пришли к ситуации, когда логика обработки данных слабо зависит: - -1)от источника данных; -2)от формата вывода в файл. +* 1)от источника данных; +* 2)от формата вывода в файл. ![image](https://dvmn.org/filer/canonical/1594117412/678/) From 1fdda5a55fa7d0639cc5292864746c29f08a2068 Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Mon, 11 Aug 2025 10:10:57 +0300 Subject: [PATCH 07/11] =?UTF-8?q?Revert=20"=D0=BE=D1=84=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c5308b1df87ae103b8020d838404e9be9e51f51a. --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6c20d2c4..76cdc5ce 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,18 @@ ## Пример. Подбор онлайн-курса + По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода: -```def get_courses_list(courses_url): +def get_courses_list(courses_url): html = fetch_html(courses_url) if html: # .... parsing logic return courses_list else: print("can't load list of courses") - exit()``` - + exit() Теперь примерим на себя роль провидца и подумаем какой функционал потребуется через месяц: @@ -58,7 +58,7 @@ HTML разметкой вместо courses_url. Вуаля, мы решили Пойдем дальше. Код другой функции: -```def get_course_info(html): +def get_course_info(html): # ... parsing logic rating = soup.find_all('div', attrs={'class': 'ratings-text'}) @@ -70,7 +70,7 @@ HTML разметкой вместо courses_url. Вуаля, мы решили # .... parsing logic - return course_data``` + return course_data Что может произойти с кодом дальше? Если рейтинга нет — надо искать его на другом сайте. @@ -83,7 +83,7 @@ rating yet" можно переместить туда где данные по Та же функция, часть вторая, последняя: -```def get_course_info(html): +def get_course_info(html): # ... more parsing logic is here # number prefix is usefull for simple sorting data before output to xlsx @@ -93,7 +93,7 @@ rating yet" можно переместить туда где данные по '3_language': language, '4_weeks': duration, "5_rating": rating - }``` + } Сразу возникают вопросы. А если нужна еще одна выгрузка в формате csv, с другим порядком столбцов, как это сделать? Как заменить столбец 2_date на days_before_start ? @@ -109,8 +109,9 @@ days_before_start ? ## Вместо заключения В результате мы пришли к ситуации, когда логика обработки данных слабо зависит: -* 1)от источника данных; -* 2)от формата вывода в файл. + +1)от источника данных; +2)от формата вывода в файл. ![image](https://dvmn.org/filer/canonical/1594117412/678/) From ea529517bf77ec5a188b73e8ec063f94a77d9789 Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Mon, 11 Aug 2025 10:25:17 +0300 Subject: [PATCH 08/11] =?UTF-8?q?=D0=BE=D1=84=D0=BE=D1=80=D0=BC=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 76cdc5ce..f7e5cb7f 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,10 @@ def get_courses_list(courses_url): exit() Теперь примерим на себя роль провидца и подумаем какой функционал потребуется через месяц: + 1. В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем подождать еще 30 секунд и так далее. + 2. В случае если адрес недоступен - постучаться по другому url в зеркало сайта. + 3. В случае ошибки сделать запись в лог и взять данные из ранее подготовленного кеша. -В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем -подождать еще 30 секунд и так далее. -В случае если адрес недоступен - постучаться по другому url в зеркало сайта. -В случае ошибки сделать запись в лог и взять данные из ранее подготовленного -кеша. Как все это сделать когда def get_courses_list сама завершает программу ?! От вызова exit() надо отказаться. Можно выбросить исключение и таким образом сообщить о проблеме внешнему коду, пускай там разбираются. @@ -47,10 +45,10 @@ def get_courses_list(courses_url): многократных вызовов def get_courses_list. Что еще может потребоваться в скором будущем? - -Отладить и покрыть тестами парсер HTML страницы. -Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком + 1. Отладить и покрыть тестами парсер HTML страницы. + 2. Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком диске. + Ага, значит вызывать fetch_html() внутри def get_courses_list не такая уж хорошая идея. Жить будет легче если передать в def get_courses_list строку с HTML разметкой вместо courses_url. Вуаля, мы решили проблемы еще до их @@ -72,11 +70,11 @@ def get_course_info(html): return course_data Что может произойти с кодом дальше? - -Если рейтинга нет — надо искать его на другом сайте. -В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. -Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы + 1. Если рейтинга нет — надо искать его на другом сайте. + 2. В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. + 3. Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы удобнее было руками проверять. + Для всего этого нужно уметь отличать от прочих ситуацию "рейтинг неизвестен". В Python для этих целей предусмотрено значение rating = None. А строку "No rating yet" можно переместить туда где данные подготавливаются к выводу в xlsx. @@ -113,7 +111,6 @@ days_before_start ? 1)от источника данных; 2)от формата вывода в файл. - ![image](https://dvmn.org/filer/canonical/1594117412/678/) Кроме того, часть кода удалось превратить в чистые функции, что облегчит From e883920209939b1e4b9f785f6211bd268a79de46 Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Mon, 11 Aug 2025 10:28:08 +0300 Subject: [PATCH 09/11] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f7e5cb7f..610486b0 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,10 @@ def get_courses_list(courses_url): exit() Теперь примерим на себя роль провидца и подумаем какой функционал потребуется через месяц: - 1. В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем подождать еще 30 секунд и так далее. - 2. В случае если адрес недоступен - постучаться по другому url в зеркало сайта. - 3. В случае ошибки сделать запись в лог и взять данные из ранее подготовленного кеша. + +1. В случае сетевой ошибки взять паузу в 10 секунд и повторить попытку, затем подождать еще 30 секунд и так далее. +2. В случае если адрес недоступен - постучаться по другому url в зеркало сайта. +3. В случае ошибки сделать запись в лог и взять данные из ранее подготовленного кеша. Как все это сделать когда def get_courses_list сама завершает программу ?! От вызова exit() надо отказаться. Можно выбросить исключение и таким образом @@ -45,8 +46,8 @@ def get_courses_list(courses_url): многократных вызовов def get_courses_list. Что еще может потребоваться в скором будущем? - 1. Отладить и покрыть тестами парсер HTML страницы. - 2. Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком +1. Отладить и покрыть тестами парсер HTML страницы. +2. Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком диске. Ага, значит вызывать fetch_html() внутри def get_courses_list не такая уж @@ -70,9 +71,10 @@ def get_course_info(html): return course_data Что может произойти с кодом дальше? - 1. Если рейтинга нет — надо искать его на другом сайте. - 2. В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. - 3. Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы + +1. Если рейтинга нет — надо искать его на другом сайте. +2. В xlsx указывать не просто отсутствие рейтинга, а еще на каких сайтах искал. +3. Отчет о курсах без рейтинга выгружать в дополнительную вкладку xlsx, чтобы удобнее было руками проверять. Для всего этого нужно уметь отличать от прочих ситуацию "рейтинг неизвестен". @@ -108,8 +110,8 @@ days_before_start ? В результате мы пришли к ситуации, когда логика обработки данных слабо зависит: -1)от источника данных; -2)от формата вывода в файл. +1. от источника данных; +2. от формата вывода в файл. ![image](https://dvmn.org/filer/canonical/1594117412/678/) From ef22f5d323f3074490586b5759ec1cf2f73aad67 Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Mon, 11 Aug 2025 10:40:28 +0300 Subject: [PATCH 10/11] =?UTF-8?q?=D0=BE=D1=84=D0=BE=D1=80=D0=BC=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 610486b0..ba2e9da1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ По условию задачи нужно скачать из сети данные об онлайн-курсах, выбрать из них лучшие и сохранить результат в xlsx файл. Вот фрагмент кода: +```python def get_courses_list(courses_url): html = fetch_html(courses_url) if html: @@ -30,6 +31,8 @@ def get_courses_list(courses_url): else: print("can't load list of courses") exit() +``` + Теперь примерим на себя роль провидца и подумаем какой функционал потребуется через месяц: @@ -57,6 +60,7 @@ HTML разметкой вместо courses_url. Вуаля, мы решили Пойдем дальше. Код другой функции: +```python def get_course_info(html): # ... parsing logic @@ -70,6 +74,8 @@ def get_course_info(html): # .... parsing logic return course_data +``` + Что может произойти с кодом дальше? 1. Если рейтинга нет — надо искать его на другом сайте. @@ -83,6 +89,7 @@ rating yet" можно переместить туда где данные по Та же функция, часть вторая, последняя: +```python def get_course_info(html): # ... more parsing logic is here @@ -94,6 +101,8 @@ def get_course_info(html): '4_weeks': duration, "5_rating": rating } +``` + Сразу возникают вопросы. А если нужна еще одна выгрузка в формате csv, с другим порядком столбцов, как это сделать? Как заменить столбец 2_date на days_before_start ? From 5ec7ce02f89b0461593d58c3434909938359a314 Mon Sep 17 00:00:00 2001 From: antonssss441 Date: Mon, 11 Aug 2025 10:48:40 +0300 Subject: [PATCH 11/11] =?UTF-8?q?=D0=BE=D1=84=D0=BE=D1=80=D0=BC=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B8=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ba2e9da1..3f09c612 100644 --- a/README.md +++ b/README.md @@ -40,22 +40,22 @@ def get_courses_list(courses_url): 2. В случае если адрес недоступен - постучаться по другому url в зеркало сайта. 3. В случае ошибки сделать запись в лог и взять данные из ранее подготовленного кеша. -Как все это сделать когда def get_courses_list сама завершает программу ?! От -вызова exit() надо отказаться. Можно выбросить исключение и таким образом +Как все это сделать когда `def get_courses_list` сама завершает программу ?! От +вызова `exit()` надо отказаться. Можно выбросить исключение и таким образом сообщить о проблеме внешнему коду, пускай там разбираются. Вызов print тоже стоит вынести из тела функции наружу. В рассмотренных сценариях вывод в консоль зависит от общей логики загрузки данных и -многократных вызовов def get_courses_list. +многократных вызовов `def get_courses_list`. Что еще может потребоваться в скором будущем? 1. Отладить и покрыть тестами парсер HTML страницы. 2. Ускорить работу скрипта, хранить ранее скачанные страницы в кеше на жестком диске. -Ага, значит вызывать fetch_html() внутри def get_courses_list не такая уж -хорошая идея. Жить будет легче если передать в def get_courses_list строку с -HTML разметкой вместо courses_url. Вуаля, мы решили проблемы еще до их +Ага, значит вызывать `fetch_html()` внутри `def get_courses_list` не такая уж +хорошая идея. Жить будет легче если передать в `def get_courses_list` строку с +HTML разметкой вместо `courses_url`. Вуаля, мы решили проблемы еще до их появления на горизонте! Пойдем дальше. Код другой функции: @@ -84,7 +84,7 @@ def get_course_info(html): удобнее было руками проверять. Для всего этого нужно уметь отличать от прочих ситуацию "рейтинг неизвестен". -В Python для этих целей предусмотрено значение rating = None. А строку "No +В Python для этих целей предусмотрено значение `rating = None`. А строку "No rating yet" можно переместить туда где данные подготавливаются к выводу в xlsx. Та же функция, часть вторая, последняя: @@ -104,14 +104,14 @@ def get_course_info(html): ``` Сразу возникают вопросы. А если нужна еще одна выгрузка в формате csv, с -другим порядком столбцов, как это сделать? Как заменить столбец 2_date на -days_before_start ? +другим порядком столбцов, как это сделать? Как заменить столбец `2_date` на +`days_before_start`? Кроме того, наперед известно, что пользовательский интерфейс — будь то вывод в консоль или запись в файл — меняется очень часто. Было бы удобно собрать все, что относится к форматированию вывода в одном месте. Например, всю логику -выгрузки в xlsx поместить в def fill_xlsx(workbook, courses):, а вывод в -консоль собрать внутри if __name__=='__main__':. Удастся избежать вычитывания +выгрузки в xlsx поместить в `def fill_xlsx(workbook, courses):`, а вывод в +консоль собрать внутри `if __name__=='__main__':`. Удастся избежать вычитывания и повторной отладки всей программы от начала до конца, ведь изменения локальны и изолированы.