Kalt29

Это решение без исключений (try/except, raise), потому что пользователь ещё не знаком с этими концепциями. К сожалению, многие курсы на Stepik и Youtube упорно игнорируют исключения в своих учебных материалах, хотя это одно из фундаментальных понятий.

Исходный код:

Пустые строки и общая структура текста

Начнём с того, что приведём код в более-менее читаемый вид, расставив пустые строки в нужных местах. По PEP8 функции отделяются друг от друга 2 пустыми строками.

Также стоит отделить друг от друга пустыми строками логические блоки внутри функций. Логический блок - это набор инструкций, связанных друг с другом по смыслу.

def check_departure(departure):
    if len(departure) != 5 or departure[2] != ':':
        return False

    if not departure[0:2].isnumeric() or not departure[3:].isnumeric():
        return False
    if int(departure[0:2]) not in [i for i in range(0, 24)]:
        return False
    if int(departure[3:]) not in [i for i in range(0, 60)]:
        return False

    return [int(i) for i in departure.split(':')]


def check_travel_time(travel_time):
    if travel_time[-3] != ':':
        return False

    if not travel_time[0:-3].isnumeric() or not travel_time[-2:].isnumeric():
        return False
    for i in travel_time[0:-3]:
        if int(i) not in [k for k in range(0, 10)]:
            return False
    if int(travel_time[-2:]) not in [i for i in range(0, 60)]:
        return False

    return [int(i) for i in travel_time.split(':')]


def calc_arrival(departure, travel_time):
    verify_departure = check_departure(departure)
    verify_travel_time = check_travel_time(travel_time)
    if not verify_departure or not verify_travel_time:
        return None

    hours = str((verify_departure[0] + verify_travel_time[0] + (verify_departure[1] + verify_travel_time[1])//60)%24)
    if len(hours) == 1:
        hours = ''.join(['0', hours])

    minutes = str((verify_departure[1] + verify_travel_time[1])%60)
    if len(minutes) == 1:
        minutes = ''.join(['0', minutes])

    return ':'.join([hours, minutes])

Дублирование

Взгляните на функции check_departure() и check_travel_time(). В них выполняются практически идентичные действия, хоть и записанные по-разному. А значит, их можно свести к одной функции check_time().

def check_time(time, within_day=False):
    if len(time) != 5 or time[2] != ':':
        return False

    if not time[0:2].isnumeric() or not time[3:].isnumeric():
        return False
    if within_day and int(time[0:2]) >= 24:
        return False
    if int(time[3:]) not in [i for i in range(0, 60)]:
        return False

    return [int(i) for i in time.split(':')]


def calc_arrival(departure, travel_time):
    verify_departure = check_time(departure, within_day=True)
    verify_travel_time = check_time(travel_time)
    if not verify_departure or not verify_travel_time:
        return None

    hours = str((verify_departure[0] + verify_travel_time[0] + (verify_departure[1] + verify_travel_time[1])//60)%24)
    if len(hours) == 1:
        hours = ''.join(['0', hours])

    minutes = str((verify_departure[1] + verify_travel_time[1])%60)
    if len(minutes) == 1:
        minutes = ''.join(['0', minutes])

    return ':'.join([hours, minutes])

При этом внутри самой функции check_time() также есть дублирование - одни и те же инструкции выполняются по несколько раз. Избавляемся от проблемы с помощью переменных.

def check_time(time, within_day=False):
    if len(time) != 5 or time[2] != ':':
        return False

    hours_str, minutes_str = time.split(':')
    if not hours_str.isnumeric() or not minutes_str.isnumeric():
        return False

    hours = int(hours_str)
    minutes = int(minutes_str)
    if within_day and hours >= 24:
        return False
    if minutes not in [i for i in range(0, 60)]:
        return False

    return [hours, minutes]

Избыточная сложность

Проверка на то, что количество минут находится в диапазоне от 0 до 59, сейчас выполняется так:

if minutes not in [i for i in range(0, 60)]:

Можно сделать гораздо проще, ведь при проверке isnumeric() мы уже убедились в том, что знака - в строке точно нет.

if minutes >= 60:

Возвращаемые значения и исключения

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

Так что стоит знать, что такое исключения и как с ними работать.

def check_time(time, within_day=False):
    if len(time) != 5 or time[2] != ':':
        raise ValueError(f'Время должно быть в формате "HH:MM": {time}')

    hours_str, minutes_str = time.split(':')
    if not hours_str.isnumeric() or not minutes_str.isnumeric():
        raise ValueError(
            f'Часы и минуты должны состоять только из цифр: {time}'
        )

    hours = int(hours_str)
    minutes = int(minutes_str)
    if within_day and hours >= 24:
        raise ValueError(f'Часы должны быть <= 23: {hours}')
    if minutes >= 60:
        raise ValueError(f'Минуты должны быть <= 59: {minutes}')

    return [hours, minutes]

Теперь функция либо возвращает список из 2 целых чисел, либо выбрасывает исключение.

Лучше просить прощения, чем разрешения

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

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

В исходном варианте кода мы просим разрешение на каждое действие. Проверяем, что в исходной строке есть двоеточие на нужном месте, потом проверяем, что слева и справа от двоеточия стоит по 2 цифры. Но на самом деле эти проверки можно убрать, потому что чуть ниже мы и так разбиваем строку по разделителю :, а затем проверяем, что полученные числа находятся в диапазоне от 0 до 23 или от 0 до 59.

def check_time(time, within_day=False):
    hours, minutes = map(int, time.split(':'))
    if hours < 0: 
        raise ValueError(f'Часы должны быть >= 0: {hours}')
    if within_day and hours > 23:
        raise ValueError(f'Часы должны быть <= 23: {hours}')
    if not 0 <= minutes <= 59:
        raise ValueError(f'Минуты должны быть от 0 до 59: {minutes}') 
    return [hours, minutes]

Название функции

С учётом всех изменений check_time() становится не слишком подходящим названием для функции. При разборе кода от Amplicon использовали имя parse_time(), так что и здесь можно использовать его же.

calc_arrival()

Из-за того, что бывшая функция check_time() теперь выбрасывает исключения при получении некорректных данных, больше нет необходимости в этой проверке:

if not verify_departure or not verify_travel_time:
    return None

А с помощью вспомогательных переменных и f-строк можно превратить вот это:

hours = str((verify_departure[0] + verify_travel_time[0] + (verify_departure[1] + verify_travel_time[1])//60)%24)
if len(hours) == 1:
    hours = ''.join(['0', hours])
minutes = str((verify_departure[1] + verify_travel_time[1])%60)
if len(minutes) == 1:
    minutes = ''.join(['0', minutes])
return ':'.join([hours, minutes])

Вот в это:

departure_h, departure_m = parse_time(departure, within_day=True)
travel_h, travel_m = parse_time(travel_time)

arrival_h = (departure_h + travel_h + (departure_m + travel_m) // 60) % 24)
arrival_m = (departure_m + travel_m) % 60

return f'{arrival_h:02d}:{arrival_m:02d}'

Итоговый код