In English writing, you can use the ellipsis to indicate that you’re leaving something out.
Essentially, you use three dots (...
) to replace the content.
But the ellipsis doesn’t only exist in prose—you may have seen three dots in Python source code, too.
The ellipsis literal (...
) evaluates to Python’s Ellipsis
.
Because Ellipsis
is a built-in constant,
you can use Ellipsis
or ...
without importing it:
>>> ...
Ellipsis
>>> Ellipsis
Ellipsis
>>> ... is Ellipsis
True
Although three dots may look odd as Python syntax, there are situations where using ...
can come in handy.
But when should you use Ellipsis
in Python?
Source Code: Click here to download the free source code that you’ll use to master the ellipsis literal.
In Short: Use the Ellipsis as a Placeholder in Python
While you can use ...
and Ellipsis
interchangeably, you’ll commonly opt for ...
in your code.
Similar to using three dots in English to omit content, you can use the ellipsis in Python as a placeholder for unwritten code:
# ellipsis_example.py
def do_nothing():
...
do_nothing()
When you run ellipsis_example.py
and execute do_nothing()
, then Python runs without complaining:
$ python ellipsis_example.py
$
There’s no error when you execute a function in Python that contains only ...
in the function body.
That means you can use an ellipsis as a placeholder similar to the pass
keyword.
Using three dots creates minimal visual clutter. So, it can be convenient to replace irrelevant code when you’re sharing parts of your code online.
A common time when you omit code is when you work with stubs. You can think of stubs as stand-ins for real functions. Stubs can come in handy when you only need a function signature but don’t want to execute the code in the function body. For example, you’d probably want to prevent external requests when you’re developing an application.
Say you have a Flask project where you’ve created your own visitor counter in custom_stats.count_visitor()
.
The count_visitor()
function is connected to the database where you track the number of visitors.
To not count yourself when you test your application in debug mode, you can create a stub of count_visitor()
:
1# app.py
2
3from flask import Flask
4
5from custom_stats import count_visitor
6
7app = Flask(__name__)
8
9if app.debug:
10 def count_visitor(): ...
11
12@app.route("/")
13def home():
14 count_visitor()
15 return "Hello, world!"
Because the content of count_visitor()
doesn’t matter in this case, it’s a good idea to use the ellipsis in the function body.
Python calls count_visitor()
without error or unwanted side effects when you run your Flask application in debug mode:
$ flask --app app --debug run
* Serving Flask app 'app'
* Debug mode: on
If you run the Flask app in debug mode, then count_visitor()
in line 14 refers to your stub in line 10.
Using ...
in the function body of count_visitor()
helps you to test your app without side effects.
The example above shows how to use stubs on a smaller scale. For bigger projects, stubs are often used in unit tests because they help to test parts of your code in an isolated manner.
Also, if you’re familiar with type checking in Python, this talk of the ellipsis in combination with stubs may ring a bell.
The most common tool for doing type checking is mypy. To determine the types of the standard library and third-party library definitions, mypy uses stub files:
A stub file is a file containing a skeleton of the public interface of that Python module, including classes, variables, functions – and most importantly, their types. (Source)
You can visit Python’s typeshed repository and explore the usage of ...
in the stub files that this repository contains.
When you dive deeper into the topic of static typing, you may find another use case of the Ellipsis
constant.
In the next section, you’ll learn when to use ...
in type hints.
What Does the Ellipsis Mean in Type Hints?
In the last section, you learned that you can use the Ellipsis
as a placeholder for your stub files, including when type checking.
But you can also use ...
in type hinting. In this section, you’ll learn how to use ...
to:
- Specify a variable-length tuple of homogeneous type
- Substitute for a list of arguments to a callable
Type hinting is a great way to be explicit about the data types that you expect in your code. But sometimes, you want to use type hints without fully restricting how your users can use the objects. For example, perhaps you want to specify a tuple that should contain only integers, but the number of integers can be arbitrary. That’s when the ellipsis can come in handy:
1# tuple_example.py
2
3numbers: tuple[int, ...]
4
5# Allowed:
6numbers = ()
7numbers = (1,)
8numbers = (4, 5, 6, 99)
9
10# Not allowed:
11numbers = (1, "a")
12numbers = [1, 3]
In line 3, you define a variable, numbers
, of the type tuple.
The numbers
variable must be a tuple that contains only integers.
The total quantity is unimportant.
The variable definitions in lines 6, 7, and 8 are valid because they comply with the type hinting.
The other definitions of numbers
aren’t allowed:
- Line 11 doesn’t contain homogeneous items.
- Line 12 isn’t a tuple, but a list.
If you have mypy installed, then you can run the code with mypy to list both errors:
$ mypy tuple_example.py
tuple_example.py:11: error: Incompatible types in assignment
(expression has type "Tuple[int, str]", variable has type "Tuple[int, ...]")
tuple_example.py:12: error: Incompatible types in assignment
(expression has type "List[int]", variable has type "Tuple[int, ...]")
Using ...
within a tuple type hint means that you expect all items to be of the same type in the tuple.
On the other hand, when you use the ellipsis literal for callable types, you’re essentially lifting some restriction on how the callable can be called, perhaps in terms of number or type of arguments:
1from typing import Callable
2
3def add_one(i: int) -> int:
4 return i + 1
5
6def multiply_with(x: int, y: int) -> int:
7 return x * y
8
9def as_pixels(i: int) -> str:
10 return f"{i}px"
11
12def calculate(i: int, action: Callable[..., int], *args: int) -> int:
13 return action(i, *args)
14
15# Works:
16calculate(1, add_one)
17calculate(1, multiply_with, 3)
18
19# Doesn't work:
20calculate(1, 3)
21calculate(1, as_pixels)
In line 12, you define a callable parameter, action
.
This callable may take any number and type of arguments but must return an integer.
With *args: int
, you also allow a variable number of optional arguments, as long as they’re integers.
In the function body of calculate()
in line 13, you call the action
callable with the integer i
and any other arguments that are passed in.
When you define a callable type, you have to let Python know what types you’ll allow as input and what type you expect the callable to return.
By using Callable[..., int]
, you say that you don’t mind how many and which types of arguments the callable accepts.
Yet, you’ve specified that it must return an integer.
The functions that you pass in as arguments for calculate()
in lines 16 and 17 comply with the rule you set.
Both add_one()
and multiply_with()
are callables that return an integer.
The code in line 20 is invalid because 3
isn’t callable.
A callable must be something that you can call, hence the name.
Although as_pixels()
is callable, its usage in line 21 is also not valid.
In line 10, you’re creating an f-string.
You get a string as the return value, which isn’t the integer type that you expected.
In the examples above, you’ve investigated how to use the ellipsis literal in type hinting for tuples and callables:
Type | Ellipsis Usage |
---|---|
tuple |
Defines an unknown-length tuple of data with a uniform type |
Callable |
Stands in for a list of arguments to a callable, removing restrictions |
Next, you’ll learn how to use Ellipsis
with NumPy.
How Can You Use the Ellipsis for Slicing in NumPy?
If you’ve worked with NumPy before, then you may have come across another use of Ellipsis
.
In NumPy, you can slice multidimensional arrays with the ellipsis literal.
Start with an example that doesn’t take advantage of Ellipsis
in NumPy:
>>> import numpy as np
>>> dimensions = 3
>>> items_per_dimension = 2
>>> max_items = items_per_dimension**dimensions
>>> axes = np.repeat(items_per_dimension, dimensions)
>>> arr = np.arange(max_items).reshape(axes)
>>> arr
array([[[0, 1],
[2, 3]],
[[4, 5],
[6, 7]]])
In this example, you’re creating a three-dimensional array by combining .arange()
and .reshape()
in NumPy.
If you want to specify the first items of the last dimension, then you can slice the NumPy array with the help of the colon character (:
):
>>> arr[:, :, 0]
array([[0, 2],
[4, 6]])
Because arr
has three dimensions, you need to specify three slices.
But imagine how annoying the syntax would get if you added more dimensions!
It’s even worse when you can’t tell how many dimensions an array has:
>>> import numpy as np
>>> dimensions = np.random.randint(1,10)
>>> items_per_dimension = 2
>>> max_items = items_per_dimension**dimensions
>>> axes = np.repeat(items_per_dimension, dimensions)
>>> arr = np.arange(max_items).reshape(axes)
In this example, you’re creating an array that can have up to ten dimensions.
You could use NumPy’s .ndim()
to find out how many dimensions arr
has.
But in a case like this, using ...
is a better way:
>>> arr[..., 0]
array([[[[ 0, 2],
[ 4, 6]],
[[ 8, 10],
[12, 14]]],
[[[16, 18],
[20, 22]],
[[24, 26],
[28, 30]]]])
Here, arr
has five dimensions.
Because the number of dimensions is random, your output may look different.
Still, specifying your multidimensional array with ...
will work.
NumPy offers even more options to use Ellipsis
to specify an element or a range of arrays.
Check out NumPy: Ellipsis
(...
) for ndarray
to discover more use cases for these three little dots.
Are Three Dots in Python Always the Ellipsis?
Once you’ve learned about Python’s Ellipsis
, you may pay closer attention to the appearance of every ellipsis in the Python world.
However, you might see three dots in Python that don’t represent the Ellipsis
constant.
In the Python interactive shell, three dots indicate the secondary prompt:
>>> def hello_world():
... print("Hello, world!")
...
For example, when you define a function in the Python interpreter, or when you create for
loops, the prompt continues over multiple lines.
In the example above, the three dots aren’t an ellipsis literal but the secondary prompt for your function body.
Are there other places where you’ve spotted three dots in Python? Share your findings with the Real Python community in the comments below!
Conclusion
The ellipsis literal (...
) evaluates to the Ellipsis
constant.
Most commonly, you may use ...
as a placeholder,
for example when you create stubs of functions.
In type hinting, the three dots can come in handy when you want to be flexible. You can specify a variable-length tuple of homogeneous type and substitute a list of arguments for callable types with the ellipsis literal.
If you work with NumPy, then you can use ...
to abbreviate your slicing syntax by replacing variable-length dimensions with the Ellipsis
object.
With the clutter-free syntax that the three dots provide, you can make your code more readable.
As a rule of thumb, you can remember that you generally use Python’s Ellipsis
to omit code.
Some might even say that the ellipsis can make incomplete code look cute …
Source Code: Click here to download the free source code that you’ll use to master the ellipsis literal.