5

Итерация по диапазону дат в Python

29

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

day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
    print(strftime("%Y-%m-%d", single_date.timetuple()))

Примечания

  • На самом деле, я не использую этот код для вывода. Это просто для демонстрации.
  • Переменные start_date и end_date являются объектами datetime.date, так как мне не нужны метки времени. (Они будут использоваться для генерации отчета).

Пример вывода

Для начала даты 2009-05-30 и конца даты 2009-06-09:

2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09

Как можно сделать этот код более читабельным и эффективным?

5 ответ(ов)

8

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

for single_date in (start_date + timedelta(n) for n in range(day_count)):
    print ...

Этот код создает генератор, который будет генерировать даты, начиная с start_date и добавляя каждый раз timedelta(n) для n из диапазона day_count. Соответственно, он не создает никаких списков и использует один генератор для итерации.

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

Ваше обновление с использованием функции-генератора выглядит изящно и эффективно:

from datetime import date, timedelta

def daterange(start_date: date, end_date: date):
    days = int((end_date - start_date).days)
    for n in range(days):
        yield start_date + timedelta(n)

start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
    print(single_date.strftime("%Y-%m-%d"))

Эта функция daterange абстрагирует итерацию над диапазоном дат и делает ваш код более читаемым и удобным для дальнейшего использования. Обратите внимание, что, аналогично встроенной функции range(), ваша итерация закончится до достижения end_date. Для включительной итерации стоит использовать следующий день, как и в случае с range().

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

3

Это может быть более понятно:

from datetime import date, timedelta

start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
    print(start_date.strftime("%Y-%m-%d"))
    start_date += delta

В этом коде мы используем модуль datetime, чтобы перебрать все даты от start_date до end_date. Мы начинаем с 1 января 2019 года и продолжаем до 1 января 2020 года, увеличивая дату на один день в каждой итерации цикла с помощью timedelta. Для форматирования даты в виде строки мы используем метод strftime. Таким образом, результатом выполнения этого кода будет список всех дат в указанном диапазоне.

1

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

Вот пример, как создать диапазон дат:

import pandas as pd
daterange = pd.date_range(start_date, end_date)

После этого вы можете пройтись по диапазону дат и вывести каждую дату:

for single_date in daterange:
    print(single_date.strftime("%Y-%m-%d"))

Кроме того, в Pandas есть множество опций, которые упрощают работу. Например, если вам нужны только будние дни, вы можете использовать bdate_range. Более подробную информацию можно найти по ссылке: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps.

Сила Pandas заключается в его DataFrame, который поддерживает векторизованные операции (похожим образом на numpy), что делает работу с большими объемами данных очень быстрой и простой.

EDIT: Вы также можете полностью пропустить цикл и вывести диапазон дат напрямую, что будет проще и эффективнее:

print(daterange)
0

Вот наиболее читаемое с точки зрения человека решение, которое я могу предложить.

import datetime

def daterange(start, end, step=datetime.timedelta(1)):
    curr = start
    while curr < end:
        yield curr
        curr += step

Эта функция daterange генерирует последовательность дат от start до end, с указанным интервалом step (по умолчанию равным одному дню). Вы можете использовать ее следующим образом:

for single_date in daterange(datetime.date(2023, 1, 1), datetime.date(2023, 1, 5)):
    print(single_date)

В результате вы получите даты с 1 по 4 января 2023 года. Эта функция удобна для работы с временными диапазонами в Python.

0

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

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

Вот пример использования вашей функции:

import datetime

start_date = datetime.date(2023, 1, 1)
end_date = datetime.date(2023, 1, 10)

for date in daterange(start_date, end_date, inclusive=True):
    print(date.strftime("%Y-%m-%d"))

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь