Python documentation is very good and should be the first place to look for explanation of concepts you don't understand. Unfortunately, in case of Ellipsis, it doesn't tell us much.
The same as the ellipsis literal "
...". Special value used mostly in conjunction with extended slicing syntax for user-defined container data types. https://docs.python.org/library/constants.html#Ellipsis
When documentation is lacking, we have to do some explorations ourselves.
Ellipsis is equivalent to
(three dots). Interactive python shows us, it's true.
>>> ... Ellipsis >>> Ellipsis is ... True
We can do some other stuff with it. As I mentioned in my
article about metaclasses - everything is
an object. Therefore, even
Ellipsis should have some methods and attributes
we can inspect.
>>> dir(...) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
This can lead to some interesting syntax weirdness.
>>> ....__eq__(...) True
Class of Ellipsis
Another interesting thing about
Ellipsis is its class. Usually all classes
of standard objects are directly callable.
>>> type(False) <class 'bool'> >>>bool() False
This isn't true for
Ellipsis. The class seems to be inaccessible by name. But
we can use a little trick.
>>> type(...) <class 'ellipsis'> >>> ellipsis() Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'ellipsis' is not defined >>> type(...)() Ellipsis
I was wondering, where does the class comes from. I was expecting
and inspection of the class would suggest so.
>>> type(...).__module__ 'builtins'
But it cannot be imported from there.
>>> from builtins import ellipsis Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: cannot import name 'ellipsis'
And it's not even listed in the module. It only contains its instance.
>>> import builtins >>> 'Ellipsis' in dir(builtins) True >>> 'ellipsis' in dir(builtins) False
Ellipsis is not very consistent with other constants. I believe
it's because it's special status of being both a literal and an object at the same time.
Not intended usage
Ellipsis says its intended purpose is in slicing of array
and matrices. We will go through that usage in detail in next article, before we do so, let's look at
some example of bad usage I found on the internet.
... instead of
Everyone knows they can use
pass to denotate an empty code block.
def empty_function(): pass
Some people suggest we could use
... instead. I have to say that purely based
on esthetics, I like it more.
def empty_function(): ...
This is possible not because of some magical properties of
... but because
Python will accept almost anything, as long as the block isn't empty.
def empty_function(): 42
Even just docstring is good enough.
def empty_function(): """ Does nothing """
... used in imports
This is not exactly bad usage, just a technical detail that might be a bit confusing. Imports in Python
are parsed a bit differently from other parts of the code. We already established that
... is Ellipsis. Therefore,
should be exactly the same as
import .... But it isn't.
>>> import Ellipsis Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'Ellipsis' >>> import ... File "<stdin>", line 1 import ... ^ SyntaxError: invalid syntax
The reason is simple. When importing, token
... is translated to "two steps
up in the package tree". We can use it to import modules from package by their relative path.
Imagine a directory structure like this:
$ tree . . └── a ├── a_mod.py └── b └── c └── c_mod.py
c_mod.py needs to import
a_mod.py from package
a. One way to do it is like this:
# module a/b/c/c_mod.py from ... import a_mod
# module a/a_mod.py print('module "a" was imported")
... in this case doesn't mean
Ellipses but a
relative path from current module two steps up.
>>> from a.b.c import c_mod module "a" was imported
... used as
Even authors of Python got trapped by
... before, namely when implementing
Exception Chaining. It's a mechanism making it
possible to mark one exception as a direct cause of another.
>>> try: ... 1/0 ... except ZeroDivisionError as e: ... raise Exception() from e ... Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> Exception
Python deals with this by setting a
__cause__ attribute on the second
exception witch contains the exception that caused it. This all works fine but there is a dilemma; you
raise Exception() from None or you can just
raise Exception. How do you differentiate between exceptions that don't have
cause at all and exception caused by
PEP-409 tried to solve this by setting the
Ellipsis if it wasn't set.
PEP-415 removed this behavior. You can read
the reasoning behind these decisions in the PEPs.
In next part we'll look at some practical examples and usage.