Полезное для разработчика

24
Сен
2020

🐍 4 ошибки в коде на Python, которые выдают в вас новичка

Подробный разбор типичных ошибок новичков в Python. Почему не стоит полагаться на работу функций по умолчанию и стараться перехитрить систему?

Привет! Меня зовут Маша, я уже шесть лет занимаюсь коммерческой разработкой на Python, а ещё пишу задачи и объясняю теорию для студентов курса «Мидл Python-разработчик» от Яндекс.Практикума. По опыту знаю, что начинающий разработчик чаще всего хорошо знает синтаксис языка, но не до конца разбирается с тем, что у Python «под капотом».

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

1. Полагаетесь на изменяемые типы в значениях по умолчанию

У Python есть прекрасная особенность, а именно возможность задавать значения по умолчанию. Вы можете написать так:

        def pow(number, mod=2):
    pass

    

Или так:

        class Cat:
    legs = 4

    

И вам не придётся каждый раз указывать степень, в которую вы хотите возвести число (пока эта степень – 2), или уточнять количество ног у вашего кота.

В чём подвох? Значения по умолчанию работают правильно только с неизменяемыми объектами – строками, числами, frozen-объектами и boolean-типами. Если же вы укажете в качестве значения по умолчанию изменяемый объект, например, list, set или dict , то Python не будет ругаться, но преподнесёт вам неприятный сюрприз. Вот один из примеров: кота заводили дома, а он поселился ещё и в офисе:

        class House:
    cats = []

my_house = House()
office = House()

my_house.cats.append('Tom')

print(my_house.cats)  # ["Tom"]
print(office.cats)  # ["Tom"]

    

Чем объясняется проблема? Инструкции, объявляющие класс, выполнятся один раз. У всех экземпляров класса House будет ссылка на один и тот же массив – cats.

Такое поведение бывает сложно поймать: если вы создали всего один экземпляр объекта, то, скорее всего, не заметите проблему. Но столкнётесь с ней позже.

Как решить проблему? Привыкайте вместо значений по умолчанию указывать None:

        class House:
    cats: list = None
    
    def __init__(self):
        self.cats = []

my_house = House()
office = House()

my_house.cats.append('Tom')

print(my_house.cats)  # ["Tom"]
print(office.cats)  # []

    

Тогда код будет работать корректно, и все коты останутся на своих местах!

2. Вызываете функцию в значении по умолчанию

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

        from datetime import datetime

def create_log_entry(user, action, time=datetime.now()):
    return f'{time}: {user} {action}'

    

После этого вы, спокойные и довольные собой, ушли на работу.

В чём подвох? Вернувшись домой, вы решили проверить, как записалось каждое событие, посмотреть актуальные даты и описания. Ожидание:

        create_log_entry('Алла', 'вышла из дома')
'2020-09-14 15:20:03.333333: Алла вышла из дома'
create_log_entry('Том', 'поймал мышь')
'2020-09-14 15:25:12.795328: Том поймал мышь'
create_log_entry('Адорианец', 'заварил кофе')
'2020-09-14 15:40:33.173500: Адорианец заварил кофе'
create_log_entry('Агент Кей', 'применил нейралайзер')
'2020-09-14 15:41:48.922357: Агент Кей применил нейралайзер'

    

Реальность – все события как будто произошли в одно и то же время:

        create_log_entry('Алла', 'вышла из дома')
'2020-09-14 15:20:00.333333: Алла вышла из дома'
create_log_entry('Том', 'поймал мышь')
'2020-09-14 15:20:00.333333: Том поймал мышь'
create_log_entry('Адорианец', 'заварил кофе')
'2020-09-14 15:20:00.333333: Адорианец заварил кофе'
create_log_entry('Агент Кей', 'применил нейралайзер')
'2020-09-14 15:20:00.333333: Агент Кей применил нейралайзер'

    

Чем объясняется проблема? Это произошло из-за того, что datetime.now сработал всего один раз – в тот момент, когда интерпретатор встретил объявление функции конструкцией def create_log_entry. Python запомнил, какая дата и время были на момент запуска программы, и постоянно использовал это значение.

Как её решить? Чтобы время вычислялось каждый раз при вызове вашей функции, нужно перенести вычисления в тело функции:

        from datetime import datetime

def create_log_entry(user, action, time=None):
    time = datetime.now() if time is None else time
    return f'{time}: {user} {action}'

    

Так вы всё-таки узнаете, во сколько Том и Адорианец пили кофе и когда агент Кей ворвался к вам домой со своим нейралайзером.

3. Используете одновременно int и bool как ключи dict

Предположим, вы решили написать простой переводчик с компьютерного языка на человеческий для своего умного дома. Вам нужно, чтобы True отображалось как «Правда», False – как «Ложь», а 1 и 0 переводились как «Есть» и «Нет». Зафиксируем все переводы в словаре:

        vocabulary = {
    True: "Правда", 
    False: "Ложь",
    1: "Есть",
    0: "Нет"
}

    

В чём подвох? В этом словаре используется четыре разных ключа. Проверим, действительно ли всё работает корректно:

        print(vocabulary[True])     # 'Есть'
print(vocabulary[False])    # 'Нет'
print(vocabulary[1])        # 'Есть'
print(vocabulary[0])        # 'Нет'

    

Кажется, что-то пошло не так. Давайте заглянем в сам словарь:

        print(vocabulary)           # {True: 'Есть', False: 'Нет'}

    

Из него пропали два варианта перевода, а те, что остались – неверные.

Чем объясняется проблема? Чтобы разобраться в произошедшем, нужно понимать две вещи: что такое класс bool и как работает словарь.

  1. Класс bool, добавленный в Python 2.3, реализован как наследник класса int. То есть глобальные объекты True и False – всего лишь два экземпляра класса bool, представляющие собой 1 и 0. В этом классе переопределены методы __repr__ и __str__, которые отвечают за отображение экземпляра, но «под капотом» они остаются простыми цифрами. Это можно проверить, сравнив True и число. Зная это, вы можете использовать boolean-переменные в математических выражениях. Но я так поступать не рекомендую: как сказано в дзене Python (вы можете прочитать его, введя в интерпретатор import this), «читаемость имеет значение». Подробнее о реализации boolean можно прочитать в PEP-0285.
  2. Также внутри словаря находится hash-таблица: то есть все новые ключи, которые добавляются в словарь, проходят через hash-функцию, и именно она определяет, где расположить элемент в памяти. Таким образом, поиск и вставка данных становятся намного быстрее, чем в обычном массиве. Если хочется узнать больше подробностей о работе словарей в Python, рекомендую заглянуть на stackowerflow.

Как решить проблему? Для корректной реализации переводчика следует привести все ключи к одному типу данных – str.

        vocabulary = {
    "True": "Правда", 
    "False": "Ложь",
    "1": "Есть",
    "0": "Нет"
}

    

Hash-функции ключей перестанут совпадать, и ответ словаря будет таким, как мы хотели, – общий язык с умным домом всё-таки будет найден:

        vocabulary[str(True)]   # "Правда"

    

4. Используете set для ускорения вычислений

Среди разработчиков бытует распространённое мнение, что поиск элемента в set работает быстрее, чем в list. Поэтому нередко можно встретить следующий вариант кода:

        animals = ['cat', 'dog', 'bird', 'mouse', 'rat', 'elephant']

# <какой-то код, дополняющий или модифицирующий список>
if 'dog' in set(animals):
    # <дальнейшие вычисления>

    

В чём подвох? Рассмотрим конструкцию с точки зрения интерпретатора:

        animals = ['cat', 'dog', 'bird', 'mouse', 'rat', 'elephant']

animals_set = set(animals)  
# Нужно пройтись по всем элементам list и добавить каждый из них в set (cложность: O(n))
if 'dog' in animals_set:  # Нужно найти элемент во множестве O(1)
    # <дальнейшие вычисления>

    

Без оптимизации интерпретатор остановил бы поиск на втором элементе, но код заставил его сначала пройтись по всему списку, а потом выполнить дополнительное действие с set. В итоге вместо двух шагов получилось семь – никакого ускорения, только дополнительные расходы на память!

Чем объясняется проблема? Прежде всего – разной природой list и set. При объявлении типа list резервируется участок памяти, в котором будут храниться ссылки на другие данные в памяти. Список может хранить ссылки на любые объекты: строки, числа, другие массивы и даже на самого себя. Все объекты в списке хранятся последовательно.

Чтобы найти нужный элемент, интерпретатор последовательно идёт по ссылкам, начиная с первой, и сравнивает объект с искомым: найдя нужные данные, он останавливает поиск. Чем длиннее список, тем больше времени занимает процесс. В O-нотации это записывается как O(n).

Примечание
Подробнее об O-нотации читайте в Анализе алгоритмов для начинающих.

set, так же, как и list, хранит элементы, но работает принципиально иначе. Во-первых, он содержит в себе только уникальные элементы, во-вторых, в нём нельзя хранить изменяемые структуры, и, наконец, в-третьих, данные будут размещены не в заданном вами порядке, а в наиболее удобном для Python.

Так как расположение в множестве определяется содержимым элемента, поиск по set и правда работает гораздо быстрее. Выполняя команду x in set_y, интерпретатору нужно взять hash-функцию от x и посмотреть, есть ли в set_y данные по полученному адресу. Никакого последовательного просмотра элементов и нудного сравнения!

O-нотация называет такую сложность O(1): вне зависимости от размеров множества поиск будет происходить за одинаковое количество времени.

Как решить проблему? Звучит банально, но правильнее было бы не мудрить и воспользоваться обычным поиском.

        animals = ['cat', 'dog', 'bird', 'mouse', 'rat', 'elephant']

if 'dog' in animals: 
    # <дальнейшие вычисления>

    

Как говорится в дзене Python, «простое лучше сложного».

Советы для новичков в Python

Пожалуй, самый главный совет, который стоит дать специалистам-джуниорам, только начинающим свою карьеру в Python, – это не только зубрить основы, но и заглядывать внутрь инструмента, которым вы пользуетесь.

Чтобы не оказаться тем самым новичком, у которого ничего не работает, я советую:

  1. Прочувствовать на себе дзен Python. Мало прочитать, что «простое лучше, чем сложное»: важно применять этот принцип на практике и не создавать себе дополнительных трудностей.
  2. Зрить в корень. Про типы, классы, структуры данных и операции с ними рассказывают на первых уроках по программированию. Ваша задача – выяснить не только «для чего они используются» и «что могут», но и «как они работают».
  3. Не соблазняться фрилансом. В начале пути вам точно стоит поработать в компаниях с высокой инженерной культурой. Так вы сможете перенимать опыт от людей, которые умеют и любят писать хороший код, а не набивать шишки самостоятельно.
24
Сен
2020

Автоматизированный рендеринг видео

Задача следующая: есть готовый шаблон для создания видео из фото (с переходами, эфетами и т.д. (Affter Effect, sony vegas…), есть папка с фотографиями которые нужно вставить в шаблон. Как сделать рендеринг в автоматическом режиме? Примем…

24
Сен
2020

Не получается обновить компонент

Пытаюсь сделать авторизацию с помощью jwt токена с помощью jwt react kit но я так понимаю при редиректе происходит ошибка с рендером компонента

Warning: Cannot update a component (h) while rendering a different component (Login). To loca…

23
Сен
2020

Слишком большой размер изображений после загрузки на сайт

Всем привет, на днях заметил такую проблему – изображения после загрузки на сайт становятся в 4-5 раз больше чем изначально, к примеру, если картинка была 200кб, то если ее после загрузки на сайт скачать, она становится уже почти 1мб. По э…

23
Сен
2020

Не работает код игры "Угадай число" для бота в ТГ Python pytelegrambotapi

Никак не могу написать код для игры в угадай число для бота, код пишу в отдельном файле(не главном), чтобы потом вызвать его функцией, вот код игры:
import random
import time

def random_guesses(message,bot):
# число попыток угадать
g…

23
Сен
2020

🗣️ «Иногда решение не в изменении системы, а в изменении отношения заказчика». Интервью с системным аналитиком

Библиотека программиста продолжает серию интервью с представителями IT-индустрии. В этот раз мы побеседовали с системным аналитиком Ярославом Атроховым об особенностях работы с заказчиками, многозадачности, сложностях профессии и важности команды.

Ранее мы уже писали о том, как стать системным аналитиком, а также о различиях системного и бизнес-аналитика. Продолжаем разбираться в профессии вместе с экспертом из отрасли, Ярославом Атроховым (LinkedIn). Интервью подготовлено при поддержке факультета Системной и бизнес-аналитики онлайн-университета GeekBrains.

***

Библиотека программиста: Добрый день, Ярослав! Расскажите, пожалуйста, для начала, как вы пришли в профессию? Где проходили обучение системному анализу?

Ярослав Атрохов: Общий опыт моей работы 10 лет, в IT уже 5 лет. Некоторое время я работал руководителем проектов. Эти проекты были на 40-100% связаны с IT. Со временем захотелось глубже уйти в технические аспекты информационных систем.

Я получил дополнительное образование в Высшей Школе Экономики по направлению бизнес-информатики. Проходил профильные курсы для подготовки к сертификации IIBA и PMI.

Б.П.: Как вы определите термин «системный аналитик»‎?

Я.А.: Такой специалист отвечает за постановку и приёмку результатов труда разработчиков. Нужно собрать требования, согласовать технические решения, преобразовать их в задачи для разработчиков и обеспечить связь между разработчиками и заказчиком. Системный аналитик принимает готовое решение перед передачей заказчику, проверяет, что всё соответствует договорённостям. То есть системный аналитик выполняет роль интегратора между заинтересованными лицами.

Б.П.: В чём разница между бизнес-аналитиком и системным аналитиком?

Я.А.: Однажды так «прорвало», что написал об этом статью. Если коротко, то бизнес-аналитик – это больше про понимание конкретного бизнеса, особенностей индустрии и её бизнес-правил. А системный аналитик – это в большей мере технический специалист.

Б.П.: В чём суть работы системного аналитика, какие задачи он решает?

Я.А.: Если говорить упрощённо, то системный аналитик это своего рода переводчик с языка бизнеса на язык, понятный разработчикам. Однако обязанности системных аналитиков сильно разнятся.

Б.П.: Что входит в ваши обязанности?

Я.А.: На основе документации и обсуждений с заинтересованными лицами я формирую требования заказчика, очищаю их от лишней информации, проектирую решение, согласовываю с командой, консультирую членов команды во время разработки.

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

Б.П.: Можно ли утверждать, что системный аналитик – это человек, который берёт на себя обязанности недостающей роли в команде или компании?

Я.А.: В команде любая недостающая роль распределяется между всеми. Системный аналитик, наверное, в этом наиболее универсален.

Конкретно у нас в компании с архитекторами решаются в основном серьёзные изменения в системе и вопросы интеграции. Есть правила разработки – какие средства используются, какие архитектурные паттерны. Руководитель проекта у нас не ставит задачи непосредственно разработчикам, он помещает их в бэклог и сообщает срок, когда они должны быть готовы. Бизнес-аналитик у нас – это эксперт, с ним можно валидировать, например, имеем ли мы право по закону предоставлять пользователю планируемую функциональность.

Б.П.: Можете объяснить, что значит «спроектировать решение» со стороны системного аналитика?

Я.А.: Чаще всего я имею дело не с разработкой с нуля, а с доработкой систем, поэтому под «спроектировать решение» имею в виду изменения в текущем состоянии приложения. Это изменения в базе данных и интерфейсах, причём больше с пользовательской стороны. Изменения, которые будут произведены в системе – прерогатива архитектора системы, руководителя команды разработчиков, им я предлагаю своё решение, а не диктую его. Впоследствии разработчики уточняют конкретную реализацию исходя из принятых в системе правил разработки.

Б.П.: Правильно ли утверждать, что системный аналитик существенно влияет на цену ошибки, которую могут допустить разработчики из-за недопонимания?

Я.А.: Цена ошибки всегда велика, если сравнивать её с масштабом проекта. Мы редко меняем «цвет кнопки» просто так – у всего есть своя цель и стоимость. Аналитик обычно ближе всех к источнику требований – заказчику, он получает больше информации, и лучше других должен понимать ожидания заказчика, а значит, повлиять на разработчика, чтобы эти ожидания были исполнены наилучшим образом.

Б.П.: Какие инструменты используете в работе?

Я.А.: Некоторые из программ я использую раз в квартал, некоторые каждый день. Могу выделить Jira и Confluence, незаменимые MS Word, Excel и Notepad++, программы для прототипирования интерфейсов, как Figma, Axure RP, Pencil и работа с SQL-запросами.

Так как я работаю над веб-приложением, инструментами можно считать и несколько рабочих браузеров. Ещё пользуюсь Skype, сервисами для рисования диаграмм, например, Cawemo, diagrams.net, программами для тестирования веб-сервисов, как SoapUI, Postman.

Б.П.: К чему нужно быть готовым в профессии?

Я.А.: Быстро разбираться и изучать новое, потреблять множество сложной информации, решать нетривиальные задачи. Системному аналитику может поступить практически любой вопрос или запрос на решение проблемы, он должен обработать его и предоставить решение. Но иногда решение не в изменении системы, а в изменении отношения заказчика к проблеме.

Б.П.: Расскажите о самой интересной задаче, которую приходилось решать за время работы?

Я.А.: Боюсь, что здесь я скован соглашениями с работодателем и мне не следует распространяться на эти темы. Мои любимые задачи – когда нужно «на вчера» и круто. Люблю экстремальные ситуации, но постоянно в них бывать не хотелось бы.

Б.П.: Возможно, расскажете о сложностях, которые возникали по мере внедрения ваших проектов?

Я.А.: Самые частые сложности – непродуманные решения или изменение ситуации, нежелание заказчика разобраться и довериться. Порой нужно отказаться от большого количества выполненной работы, чтобы верно выполнить задачу. Иногда требуется признавать и исправлять свои ошибки, цена которых очень велика. В таких случаях помогает команда. Команда – это всё.

Б.П.: Расскажите об особенностях взаимодействия с заказчиком и исполнителями. Какие методы используете?

Я.А.: Работа с людьми – это навык коммуникации, и в работе системного аналитика он нужен постоянно. Общение один на один, общение в рабочих группах, во время карантина – онлайн-встречи, переписка в почте и мессенджерах, Service desk (программа для техподдержки, куда обращаются пользователи), таск-трекеры и так далее.

Б.П.: Каким бэкграундом нужно обладать, чтобы стать системным аналитиком?

Я.А.: Желательно – техническим, хотя масса системных аналитиков имеют и другой багаж знаний.

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

Б.П.: Какие есть плюсы и минусы в профессии системного аналитика?

Я.А.: Я очень люблю свою работу. Минусы чаще всего люди сами создают в своей голове. Плюсы тоже очень субъективны, но из них я бы выделил: высокий спрос на специалистов, высокий уровень окружения – можно развиваться.

Из минусов: постоянные изменения, зависимость от других членов команды.

Б.П.: Что должен знать и уметь хороший аналитик?

Я.А.: Это сильно зависит от команды. Аналитик должен дополнять её и помогать команде быть эффективной.

Б.П.: Как прокачиваете свои навыки?

Я.А.: Учусь и учу. Приходится много читать, огромная польза от обучающих видео на YouTube, но большую часть информации, конечно, лучше узнавать на практике.

Из профильной литературы могу посоветовать SWEBOK, для системных аналитиков это единственная профильная книга. Есть также BABOK (для бизнес-аналитиков), но в ней не выделяется роль именно системного аналитика. Советую читать профильную литературу о тех технологиях, которые используются в конкретном проекте.

Б.П.: Что посоветуете новичкам в профессии?

Я.А.: Учиться и не бояться признавать своё незнание, быть дружелюбными и хорошими собеседниками – тогда информация и знания будут сами к вам идти.

***

Обучиться описанным в интервью навыкам можно на факультете Системной и бизнес-аналитики в GeekBrains. Благодаря объёмной и содержательной программе обучения вы сможете получить все необходимые для работы знания. Изучить организацию процесса разработки и узнать, как правильно написать техническое задание, понять принципы описания архитектур систем, оптимизации и интеграции в них новых решений.

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

23
Сен
2020

Уменьшение числа до нуля

Мне нужно чтоб число уменьшалось на заданное постоянно пока число не будет меньше нуля
number = 999
minus = 3
while number > 0:
number =- minus
print number

проблема в том что когда я включаю этот код сразу выводится число -3 к…

23
Сен
2020

Как сделать текстовую игру с двумя игроками при помощи функции?

Как взывать эту функцию для второго игрока? С начало берёт первый, потом второй и т.д.. Долго сижу и думаю над решением, но так и не пойму как это сделать(
koloda = [6, 7, 8, 9, 10, 2, 3, 4, 11] * 4
import random

random.shuffle(koloda)

p…

23
Сен
2020

Элемент wordpress

Сайт на wordpress, элементу "кнопка" присваивается стиль не через php, а через скрипт. Как отследить этот скрипт, чтобы его отключить? И может ли подгружать стиль не с директории сайта и бд, а с другого ресурса? Я говорю про шриф…

23
Сен
2020

Элемент wordpress

Сайт на wordpress, элементу "кнопка" присваивается стиль не через php, а через скрипт. Как отследить этот скрипт, чтобы его отключить? И может ли подгружать стиль не с директории сайта и бд, а с другого ресурса? Я говорю про шриф…

23
Сен
2020

Проблема вызова рекурсивной функции

Задача определить является ли A предком W
5
A
Y : X A
V : Z Y
W : V
Z : K L
1
A W

Обход должен быть таким:
W -> V -> Z (K,L) -> Y
в Y находится A и возвращается назад по цепочке True
Но фактически программа выполняется
W -> V …

23
Сен
2020

Получить информацию о нагрузке железа

можно ли как-нибудь получить информацию о том сколько сейчас использовано оперативной памяти (не скриптом а вообще всей системой), и загруженность диска в процентах пример вывода:
Ram Used: 2.11 GB
Ram total: 6 GB
DISK: 10%

23
Сен
2020

Python задача Команда мэра

Может кто-нибудь подскажет алгоритм для решения задачи или код? Ниже прикрепил свой неудачный код.
Команда мэра
Для обеспечения победы на выборах, мэр решил создать команду из своих знакомых, в которой каждый есть другом каждого из остальн…

23
Сен
2020

Ввод текста в canvas

Без градиента текст выводится, но с градиентом его нет, не могу понять почему. И ещё, как можно сделать так, чтобы пользователь сам выбирал цвет текста?

<canvas id=”canvas” width=’600px’></canvas>
<input type=”text” id=”…

23
Сен
2020

Ввод текста в canvas

Без градиента текст выводится, но с градиентом его нет, не могу понять почему. И ещё, как можно сделать так, чтобы пользователь сам выбирал цвет текста?

<canvas id=”canvas” width=’600px’></canvas>
<input type=”text” id=”…

23
Сен
2020

Перегрузка метода с дополнением кода

Есть ли какой-то способ (без прямого копирования кода родительского метода), вызова родительской функции, причем так, чтобы можно было изменять ее состояние, грубо говоря есть такой код:
class father():
def method(self):
a = 5

23
Сен
2020

Рефакторинг API (JS)

Написал такой код, который взаимодействует со Star Wars API (SWAPI)- принимает от пользователя значение инпута и ищет совпадения на сайте. Селектом можно выбрать конкретный тип поиска-корабль, персонаж или планету, но получился длинный, ст…

23
Сен
2020

Google Drive API (Python) – Файл скачивается без формата расширения (.mp4)

Проблема в том, что когда я загружаю видео на Диск и получаю ссылку на скачивание:
https://drive.google.com/uc?id=*******************&export=download

Файл скачивается без формата .mp4.
Просто файл и если дописать .mp4 все работает, но…

23
Сен
2020

Вопрос про обучение нейросети

Создаю собственную нейросеть на питоне, столкнулся с проблемой обучения.
Имеется входной массив изображений (1000 картинок, 20 на 20 пикселей, по 100 картинок на цифру, название каждой картинки начинается с той цифры, которая на ней изобра…