It's Pointless! Or Isn't It? Python's `Ellipsis` Has Three ...
Is the Ellipsis, most commonly used as ..., the most pointless thing there is in Python? Not quite! And one of the stops in this article is NumPy…
What's the point of those three points in Python? You'll normally see them written as three dots, ...
, but ...
is a Python object—isn't everything an object in Python? The object's name is Ellipsis
.
What should you expect from this article?
Lots of puns about points
Not much else. How much is there to say about
...
?
But there may be a bit more than you think. This is a short article, but let's get straight to the point anyway.
The Ellipsis
Object
You probably heard this statement before: "Everything is an object in Python." Well, not everything, but let's not worry about detail!
The Ellipsis
is also an object. And ...
is the literal you can use to represent that object:
In case you're wondering, the type of the object Ellipsis
is EllipsisType
:
The Ellipsis
object is a singleton. There's only one instance of it:
When is all this information useful? Answer: in a Python-themed pub quiz!
However, there are cases where the Ellipsis
has a purpose. Or should I have said, "has a point?"
The Placeholder ...
One of the places I use the Ellipsis
most is as a placeholder when writing code. Let's say I'm writing a game and, as part of the planning stage, I decide I will have functions to turn the player left and right and to shoot. I could write the following in my code:
I defined the names of the functions, but I still need to write the code to go into the function definitions. I may want to come back to these later as I focus on another part of the code first.
I can't write:
This raises an IndentationError
. Many programmers may choose to use pass
in these cases instead of the Ellipsis
, and that's a perfectly fine alternative. I prefer to follow the pattern of using pass
when I genuinely want nothing to happen in a specific block of code and ...
when I intend to come back to this part of the code later to replace the ...
with code.
Useful, but not exciting. Let's look at a second use for the Ellipsis
, which is useful.
The briefest of interruptions. Two quick announcements:
• The 50% off on The Python Coding Place membership ends 29 February
• I’ve announced dates and topic for the hybrid cohort course in April: Beyond the Basics
The Slicing ...
You may already be familiar with slicing for built-in sequences such as lists and strings. Slicing is also a topic that seems simple enough until you dive deeper—you can read A Slicing Story if you missed it when I published it last year.
But let's look at multi-dimensional arrays. Libraries such as NumPy are designed to deal with such arrays. Indeed, NumPy's data type for arrays is called ndarray
, which stands for n-dimensional array.
Although NumPy is central to many applications in Python, it's not part of the standard library. Therefore, you'll need to install it using pip install numpy
in the terminal (or whichever way you install packages).
Let's create a three-dimensional array:
This array has three dimensions. Since we can't display a 3D structure on the screen, NumPy shows two separate 3 x 4
blocks. But these blocks are part of the same array. You can imagine one of these blocks in front of the other, forming two layers. Therefore, the first dimension represents the axis coming out of the screen in this visual 3D model. The length of this first dimension is 2 since there are two layers, the one containing the numbers from 1 to 12 and the other containing the numbers from 13 to 24.
The second dimension represents rows displayed within each block. The length of the second dimension is 3 since there are three rows in each layer. Finally, the third dimension represents the columns in each layer. The length of the third dimension is 4 since there are four columns in each layer.
Now, take a deep breath. Let's say you want the second and third elements in the second row of the first layer. Take your time to digest this. I hope I'll get this right, too. Let's slice the NumPy array to fetch these values:
We made it. This returns [6, 7]
, which are the values required. Yes, you can go back to the previous paragraph to confirm this. Let's look at all three axes in numbers[0, 1, 1:3]
:
The first axis represents the layers. Therefore,
0
represents the first layer.The second axis represents the rows. Therefore,
1
represents the second row. In this case, this is the second row of the first layer.The third axis represents the columns. Therefore,
1:3
is the slice starting from the second item up to–but excluding–the fourth item.
You're ready to level up. Let's say you want the second and third items from all the rows in the first layer:
The second axis is now a slice that represents the whole dimension. Therefore, the :
slice in the second axis position means "all the rows".
Finally, let's extend this to get the second and third items from all the rows in all the layers:
You might be thinking I lost track of today's topic. I can hear you shout at your screen, "You're talking about ellipsis today, Stephen!"
So, let's get back to the ...
and its use in NumPy. The last slicing operation can be simplified using the Ellipsis
:
You can also write numbers[Ellipsis, 1:3]
, but this is rare.
The Ellipsis
replaces the "whole dimension" slice, :
, for all the remaining dimensions. In this example, the ...
replaces the first and second dimensions.
So, here's a spot test you must pass before reading on! What will the following return?
Do you have an answer?
Here's the solution:
The first axis is 0
, which represents the first layer, and the ...
represents the remaining dimensions in full—all the rows and all the columns. Note that in this case, since you're fetching the entire first axis, you can achieve the same result with the simpler numbers[0]
.
Final Words
You'll find Ellipsis
used in other places, especially in conjunction with type hints. But this is a good place to stop!
But before we put the final full stop (or period), here's one more bit of trivia for that Python pub quiz. The Ellipsis
is truthy:
And that's a bit of information you'll never need to use. I think!
Code in this article uses Python 3.12
Stop Stack
#52
Two announcements:
• The 50% off on The Python Coding Place membership ends 29 February (end of day GMT). Full disclosure: I'll have a smaller discount while I add more courses, but the 50% off only has a few more days left!
• I’ve announced dates and topic for the hybrid cohort course in April: Beyond the Basics. This is included with membership.
If you read my articles often, and perhaps my posts on social media, too, you've heard me talk about The Python Coding Place several times. But you haven't heard me talk a lot about is Codetoday Unlimited, a platform for teenagers to learn to code in Python. The beginner levels are free so everyone can start their Python journey. If you have teenage daughters or sons, or a bit younger, too, or nephews and nieces, or neighbours' children, or any teenager you know, really, send them to Codetoday Unlimited so they can start learning Python or take their Python to the next level if they've already covered some of the basics.
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles. Alternatively, if you become a member of The Python Coding Place, you'll get access to all articles on The Stack as part of that membership. Of course, there's plenty more at The Place, too.
Appendix: Code Blocks
Code Block #1
...
# Ellipsis
... is Ellipsis
# True
type(...)
# <class 'ellipsis'>
f"This article is about {...}"
# 'This article is about Ellipsis'
Code Block #2
import types
isinstance(..., types.EllipsisType)
# True
Code Block #3
first_ellipsis = types.EllipsisType()
second_ellipsis = types.EllipsisType()
first_ellipsis
# Ellipsis
second_ellipsis
# Ellipsis
first_ellipsis is second_ellipsis
# True
Code Block #4
# ...
def turn_left():
...
def turn_right():
...
def shoot():
...
Code Block #5
# ...
def turn_left():
def turn_right():
def shoot():
Code Block #6
import numpy as np
numbers = np.array(
[
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]
)
print(numbers.shape)
# (2, 3, 4)
print(numbers)
# [[[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
#
# [[13 14 15 16]
# [17 18 19 20]
# [21 22 23 24]]]
Code Block #7
import numpy as np
numbers = np.array(
[
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]
)
print(numbers[0, 1, 1:3])
# [6, 7]
Code Block #8
import numpy as np
numbers = np.array(
[
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]
)
print(numbers[0, :, 1:3])
# [[ 2 3]
# [ 6 7]
# [10 11]]
Code Block #9
import numpy as np
numbers = np.array(
[
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]
)
print(numbers[:, :, 1:3])
# [[[ 2 3]
# [ 6 7]
# [10 11]]
#
# [[14 15]
# [18 19]
# [22 23]]]
Code Block #10
import numpy as np
numbers = np.array(
[
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]
)
print(numbers[..., 1:3])
# [[[ 2 3]
# [ 6 7]
# [10 11]]
#
# [[14 15]
# [18 19]
# [22 23]]]
Code Block #11
print(numbers[0, ...])
Code Block #12
import numpy as np
numbers = np.array(
[
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
],
[
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
],
]
)
print(numbers[0, ...])
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
Code Block #13
bool(...)
# True
if ...:
print("Yay!")
else:
print("Nay!")
# Yay!