Instead of defining your functions in this manner:

def generate_usage_report(users, start_date, end_date, limit=3):
    stats = [extract_usage_stats(user) for user in users]
    return Report.limit(limit).
    from
    (start_date).to(end_date)

… and get confused as you use them like this:

# What do these parameters even mean?

reports = generate_usage_report(
    [john, bob, jane], 
    today() - month(), 
    today(), 
    10,
)

You can define them like this:

def generate_usage_report(
    *,
    users: List[User],
    start_date: datetime,
    end_date: datetime,
    limit: int = 3,
) -> Report:
    stats = [extract_usage_stats(user) for user in users]
    return Report(stats).limit(limit)._from(start_date).to(end_date)

… to be used like this:

reports = generate_usage_report(
    users=[john, bob, jane],
    start_date=today() - month(),
    end_date=today(),
    limit=10,
)

Pros:

  1. Every function call is intuitive at a glance.
  2. Zero ambiguity, you don’t accidentally mix the start and end date since you don’t have to pass them in order.
  3. mypy support for typehints.

Cons:

  1. More keystrokes.