В чем разница между time.time() и time.monotonic() в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Краткое резюме
time.time() возвращает количество секунд с Unix-эпохи (1 января 1970) и может прыгать вперёд-назад при синхронизации системных часов. time.monotonic() возвращает количество секунд с произвольной точки и всегда движется только вперёд. Они решают разные задачи: time.time() для получения абсолютного времени (логи, базы данных), time.monotonic() для измерения прошедшего времени (таймеры, производительность).
time.time()
Возвращает текущее время в секундах с Unix-эпохи (1970-01-01 00:00:00 UTC) как число с плавающей точкой.
Характеристики
import time
# Текущее время
ts = time.time()
print(ts) # 1711101234.5678
print(type(ts)) # <class 'float'>
# Преобразование в дату
from datetime import datetime
dt = datetime.fromtimestamp(ts)
print(dt) # 2024-03-22 12:34:56.789
# UTC дата
dt_utc = datetime.utcfromtimestamp(ts)
print(dt_utc) # 2024-03-22 10:34:56.789
Проблемы с time.time()
1. Прыгающие часы (Clock Skew)
Если администратор изменит системные часы или произойдёт синхронизация NTP, time.time() может "прыгнуть":
import time
start = time.time()
print(f"Начало: {start}")
# Пользователь вручную установил часы на 1 час вперёд
time.sleep(1) # Спим 1 секунду
end = time.time()
print(f"Конец: {end}")
print(f"Прошло: {end - start}") # Может быть 3601.001 вместо 1.001!
2. Отрицательные разницы
start = time.time()
time.sleep(0.1)
end = time.time()
# Если часы отступили, end может быть меньше start!
if end < start:
print("Часы отступили назад!")
3. Precision зависит от ОС
На Windows точность может быть ~15ms, на Linux лучше (~1мкс).
Когда использовать time.time()
- Логирование событий с реальным временем
- Сохранение timestamp в БД
- Проверка истечения истории (когда нужна абсолютная дата)
- Синхронизация между разными машинами
import logging
from datetime import datetime
# Логирование с реальным временем
logging.basicConfig(
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger()
logger.info(f"Вход пользователя в {datetime.fromtimestamp(time.time())}")
# Сохранение в БД
def save_event_to_db(event: str):
timestamp = time.time() # Абсолютное время
db.insert("events", {
"event": event,
"timestamp": timestamp
})
time.monotonic()
Возвращает количество секунд с произвольной отправной точки. Часы монотонны: они никогда не идут назад, даже если пользователь изменит системные часы.
Характеристики
import time
# Монотонное время
ts = time.monotonic()
print(ts) # 12345.6789
print(type(ts)) # <class 'float'>
# Всегда растёт
for i in range(5):
print(time.monotonic())
time.sleep(0.1)
# 12345.678
# 12345.779
# 12345.879
# 12345.980
# 12346.081
Преимущества time.monotonic()
1. Невозможно сделать отрицательную разницу
def measure_elapsed_time():
start = time.monotonic()
# ... некоторый код ...
end = time.monotonic()
elapsed = end - start
assert elapsed >= 0 # Всегда истинно!
return elapsed
# Даже если пользователь изменит часы
elapsed = measure_elapsed_time()
print(f"Прошло: {elapsed} сек") # Всегда правильно
2. Защита от синхронизации NTP
import time
import asyncio
async def wait_with_timeout(seconds: float):
start = time.monotonic()
while True:
# Даже если NTP синхронизирует часы, монотонное время правильно
elapsed = time.monotonic() - start
if elapsed >= seconds:
break
await asyncio.sleep(0.1)
return elapsed
# Работает правильно, несмотря на синхронизацию часов
3. Надёжная точность
time.monotonic() использует наиболее точные системные часы (на Linux: CLOCK_MONOTONIC, Windows: QueryPerformanceCounter()).
Когда использовать time.monotonic()
- Измерение прошедшего времени
- Таймеры и таймауты
- Производительность (profiling)
- Синхронизация потоков
import time
from typing import Callable, TypeVar
T = TypeVar('T')
def measure_performance(func: Callable[..., T]) -> Callable[..., T]:
"""Декоратор для измерения времени выполнения функции"""
def wrapper(*args, **kwargs) -> T:
start = time.monotonic() # Начало
result = func(*args, **kwargs)
end = time.monotonic() # Конец
elapsed = end - start
print(f"{func.__name__} выполнилась за {elapsed:.4f} сек")
return result
return wrapper
@measure_performance
def slow_function():
time.sleep(1)
return 42
slow_function()
# Вывод: slow_function выполнилась за 1.0012 сек
Практический пример: Реализация timeout
import time
from typing import Optional, Any
class TimeoutError(Exception):
pass
def with_timeout(func: callable, timeout: float, *args, **kwargs) -> Any:
"""Выполнить функцию с таймаутом"""
start = time.monotonic()
# Простой пример (для asyncio используй asyncio.wait_for)
result = func(*args, **kwargs)
elapsed = time.monotonic() - start
if elapsed > timeout:
raise TimeoutError(
f"Функция выполнялась {elapsed:.2f}с, лимит {timeout}с"
)
return result
def slow_operation():
"""Операция, которая может долго выполняться"""
time.sleep(2)
return "Результат"
# Использование
try:
result = with_timeout(slow_operation, timeout=1)
except TimeoutError as e:
print(f"Ошибка: {e}")
Сравнительная таблица
| Аспект | time.time() | time.monotonic() |
|---|---|---|
| Значение | Секунды с Unix-эпохи (1970) | Секунды с произвольной точки |
| Реальное время | Да (абсолютное) | Нет (относительное) |
| Может прыгать | Да (NTP, вручную) | Нет (монотонно растёт) |
| Может быть отрицательным | Нет (всегда > 1970) | Не важно (берём разность) |
| Точность | Зависит от ОС (15ms-1мкс) | Высокая (наилучшие часы ОС) |
| Использование | Абсолютное время (логи, БД) | Измерение интервалов (таймеры) |
| Нулевая точка | Известна (1970) | Неизвестна |
| Потокобезопасность | Да | Да |
Реальный пример: Кэш с TTL
import time
from typing import Optional, Generic, TypeVar, Dict
T = TypeVar('T')
class CacheEntry(Generic[T]):
def __init__(self, value: T, ttl_seconds: float):
self.value = value
self.created_at = time.monotonic() # Когда создана запись
self.ttl_seconds = ttl_seconds
def is_expired(self) -> bool:
"""Проверить, истекла ли запись"""
elapsed = time.monotonic() - self.created_at
return elapsed > self.ttl_seconds
class SimpleCache(Generic[T]):
def __init__(self):
self._cache: Dict[str, CacheEntry[T]] = {}
def set(self, key: str, value: T, ttl_seconds: float = 3600):
"""Сохранить значение в кэш на ttl_seconds секунд"""
self._cache[key] = CacheEntry(value, ttl_seconds)
def get(self, key: str) -> Optional[T]:
"""Получить значение из кэша"""
if key not in self._cache:
return None
entry = self._cache[key]
if entry.is_expired():
del self._cache[key] # Удалить просроченную запись
return None
return entry.value
# Использование
cache = SimpleCache()
cache.set("user:123", {"name": "John"}, ttl_seconds=60)
print(cache.get("user:123")) # {"name": "John"}
time.sleep(61)
print(cache.get("user:123")) # None (просрочено)
Заключение
- time.time() — для получения реального времени (когда нужна дата/время)
- time.monotonic() — для измерения прошедшего времени (таймеры, производительность)
- Golden Rule: всегда используй time.monotonic() для измерения интервалов в коде
- time.time() используй только для логирования и сохранения в БД
- Никогда не полагайся на дельту time.time() для критичных таймаутов