Итерация по диапазону дат в Python
Я написал следующий код для обработки диапазона дат, но как его можно улучшить? На данный момент, я считаю, что это лучше, чем вложенные циклы, но он начинает выглядеть чересчур запутанно, особенно когда в списковом выражении используется генератор.
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 ответ(ов)
Вопрос о том, почему в вашем коде используются два вложенных итератора, актуален. На самом деле, в приведенном вами коде нет необходимости в двух уровнях итерации, поскольку он использует генератор, который работает только с одним уровнем. Давайте рассмотрим ваш код:
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()
.
Таким образом, ваш первоначальный вопрос про два вложенных итератора можно считать риторическим: итерация по датам действительно может быть выполнена одним уровнем, а генераторная функция может сделать этот процесс более удобным и понятным.
Это может быть более понятно:
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
. Таким образом, результатом выполнения этого кода будет список всех дат в указанном диапазоне.
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)
Вот наиболее читаемое с точки зрения человека решение, которое я могу предложить.
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.
Функция 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
, заключается в том, что ваш код становится проще для понимания и сопровождения.
Преобразование даты в datetime в Python
Как получить день недели по заданной дате?
Добавление дней к дате в Python
Как рассчитать количество дней между двумя заданными датами
Как вывести дату в стандартном формате?