The three dots ... (officially the Ellipsis literal) is one of Python’s most
versatile yet underappreciated features. It is a built‑in singleton constant, just
like None, True, and False. You can type it directly in source code and it
evaluates to the object Ellipsis.
print(...) # Ellipsis
print(type(...)) # <class 'ellipsis'>
print(... is Ellipsis) # TrueThis guide walks through every professional‑grade use case, with fully runnable examples.
The most common beginner use is as a TODO marker. Unlike pass, the ellipsis visually signals “intentionally empty, not an oversight.”
def future_function():
... # TODO: implement later
class FutureClass:
...
if 2 + 2 == 4:
... # handle this case laterThis code runs without errors, and many linters treat ... as an acceptable placeholder.
Type hints use ... to indicate arbitrary parameters or variable‑length homogeneous tuples.
When a callback can accept any arguments but returns a known type:
from typing import Callable
def register_callback(cb: Callable[..., str]) -> None:
# cb might be called with anything, but we expect it to return a string
result = cb("example", key=42)
print(result)
# Usage
def my_callback(*args, **kwargs) -> str:
return f"Called with {len(args)} args and {len(kwargs)} kwargs"
register_callback(my_callback)A tuple of arbitrary length where every element is of the same type:
from typing import Tuple
def sum_all(numbers: Tuple[int, ...]) -> int:
return sum(numbers)
print(sum_all((1, 2, 3))) # OK
print(sum_all((10, 20, 30, 40))) # OKProtocols define an interface that a class satisfies simply by having the required methods – no explicit inheritance needed. The ellipsis is the canonical body for protocol methods.
from typing import Protocol
class Greeter(Protocol):
def greet(self) -> str:
...
class Person:
def __init__(self, name: str):
self.name = name
def greet(self) -> str:
return f"Hello, I am {self.name}"
class Robot:
def greet(self) -> str:
return "Beep boop"
class Dog:
def bark(self) -> str:
return "Woof"
def greet(self) -> str:
return "Woof! I'm a dog, nice to meet you!"
def welcome(g: Greeter) -> None:
print(g.greet())
# All three classes satisfy the Greeter protocol
p = Person("Alice")
r = Robot()
d = Dog()
welcome(p) # "Hello, I am Alice"
welcome(r) # "Beep boop"
welcome(d) # "Woof! I'm a dog, nice to meet you!"The ellipsis in the protocol tells static type checkers: “This method is part of the interface; the real body is supplied by the implementing class.”
Similar to protocols, abstract methods in ABCs often use ... to mark “I have no concrete implementation here.”
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def connect(self) -> None:
...
class SQLiteDB(Database):
def connect(self) -> None:
print("Connecting to SQLite database...")
# real connection logic here
# db = Database() # Error – can't instantiate abstract class
db = SQLiteDB()
db.connect()In NumPy, ... means “as many : as needed to select all remaining dimensions.”
import numpy as np
arr = np.zeros((2, 3, 4, 5))
# Select first element in the first dimension, keep all others
sliced = arr[0, ...] # shape (3, 4, 5)
print(sliced.shape)A custom class can also handle ... inside __getitem__:
class MyTensor:
def __init__(self, data):
self.data = data
def __getitem__(self, key):
if key is ...:
return self # return the whole tensor
return self.data[key]
t = MyTensor([1, 2, 3])
print(t[...]) # <__main__.MyTensor object ...>
print(t[1]) # 2In Pydantic models, ... makes a field required (no default value).
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = ... # required, no default
# Works only if both fields are provided
item = Item(name="Laptop", description="A powerful machine")
print(item)
# Item(name='Laptop', description='A powerful machine')In FastAPI, ... can mark a query parameter as required:
# This is a snippet; FastAPI needs a running server
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str = Query(..., min_length=3)):
# q is a required query parameter
return {"query": q}Note:
...does not work insideTypedDictto mark a key as required. Usetotal=Falseortyping.Requiredinstead.
... inside Depends() tells FastAPI that a dependency is required and will be injected automatically.
from fastapi import Depends, FastAPI
app = FastAPI()
def get_api_key():
return "secret"
@app.get("/data")
async def get_data(key: str = Depends(...)):
# key is injected by FastAPI, not passed by the client
return {"key": key}When wrapping a function that adds or removes parameters, ... in the final Callable signature represents the captured parameter specification.
from typing import Callable, Concatenate, ParamSpec
P = ParamSpec("P")
def with_prefix(prefix: str) -> Callable[[Callable[Concatenate[str, P], None]], Callable[P, None]]:
def decorator(func: Callable[Concatenate[str, P], None]) -> Callable[P, None]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
return func(prefix, *args, **kwargs)
return wrapper
return decorator
# Usage
@with_prefix("LOG:")
def log(prefix: str, message: str) -> None:
print(f"{prefix} {message}")
log("Hello")
# Prints: LOG: HelloIn Python 3.13+, you can give a TypeVarTuple a default using ... so that it can be omitted when the generic class is instantiated.
import sys
if sys.version_info >= (3, 13):
from typing import Generic, TypeVarTuple, Unpack
Ts = TypeVarTuple("Ts", default=...)
class Stack(Generic[Unpack[Ts]]):
def __init__(self, *items: Unpack[Ts]):
self.items = items
# Without default, Stack() would be an error
s = Stack(1, 2, 3) # OK, Ts inferred as (int, int, int)
print(s.items)
else:
print("TypeVarTuple default requires Python 3.13+")Because ... is a unique object, it makes an excellent sentinel to separate “the user passed None” from “the user passed nothing at all.”
from typing import Optional
def update_profile(username: Optional[str] = ...):
if username is ...:
print("No change requested")
elif username is None:
print("Username explicitly deleted")
else:
print(f"Username changed to {username}")
update_profile() # No change requested
update_profile(None) # Username explicitly deleted
update_profile("new_user") # Username changed to new_userType stub files (.pyi) contain type annotations for existing code. In a stub, the function body is almost always ... because only the signature matters for static analysis.
utils.py – the actual implementation:
def cube(n):
if not isinstance(n, (int, float)):
raise TypeError("Expected int or float")
return n * n * nutils.pyi – the type stub:
def cube(n: int | float) -> int | float:
"""Returns the cube of a number."""
...Now type checkers can understand the exact signature, while the ellipsis acts as a syntactically correct no‑op body.
A small test script that uses the function:
from utils import cube
n = cube(3)
print(f"The cube of 3 is {n}.")
n2 = cube(4.4)
print(f"The cube of 4.4 is {n2}.")Output:
The cube of 3 is 27.
The cube of 4.4 is 85.18400000000001.
The .pyi file is never executed; it’s a contract for type checkers like mypy.
The ellipsis is the standard placeholder for a stub function body.
| Context | Meaning of ... |
|---|---|
| Function / class body | “Intentionally empty, to be implemented later” |
| Protocols & ABCs | “This is an interface; the body is elsewhere” |
Type hints: Callable[..., R] |
“Any number of parameters” |
Type hints: Tuple[int, ...] |
“Homogeneous tuple of arbitrary length” |
| NumPy slicing | “Expand all remaining dimensions” |
| Pydantic / FastAPI fields | “This field is required (no default)” |
FastAPI Depends(...) |
“Dependency is required and will be injected” |
Custom __getitem__ |
A special key for “select everything” |
TypeVarTuple default |
“Default to an empty type tuple” |
| Sentinel value | “Argument was not provided” |
Stub files (.pyi) |
“Signature only; implementation elsewhere” |
The ellipsis literal is far more than a lazy placeholder. It is a first‑class Python object that serves as:
- A clean stub in incomplete code
- A fundamental building block of type hints and structural interfaces
- A domain‑specific operator in scientific Python (NumPy)
- A required‑ness marker in modern web frameworks (FastAPI)
- A powerful sentinel for advanced function design
- The standard body in type stub files
Understanding where and why to use ... makes your Python code more expressive, more precise, and — dare we say — more Pythonic.