← Назад к вопросам

В чем разница между time.time() и time.monotonic() в Python?

2.2 Middle🔥 91 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Краткое резюме

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() для критичных таймаутов