尚未翻译
本页面尚未翻译成中文,因此显示英文原文。 帮助翻译
Ellipsis (...) Constant Complexity¶
The Ellipsis constant, also spelled ..., is a singleton: both spellings name one object,
and nothing creates another. A type that receives it in a subscript, a type hint that contains
it, or a function that uses it as a sentinel gives it a meaning.
Every operation in the reference acts on that one object, so its rows have no size variable.
Where ... is passed to someone else's code - a __getitem__, an __eq__ - the cost is that
code's.
Complexity Reference¶
| Operation | Time | Space | Notes |
|---|---|---|---|
Ellipsis, ... |
O(1) | O(1) | Both spellings name the same object |
x is ... |
O(1) | O(1) | Identity check; no method of x is called |
x == ... |
O(1) | O(1) | ellipsis defines no __eq__, so this is identity unless the other operand's __eq__ answers |
type(...), types.EllipsisType |
O(1) | O(1) | The type; it cannot be subclassed |
type(...)() |
O(1) | O(1) | Returns the existing singleton; any argument raises TypeError |
hash(...) |
O(1) | O(1) | Hashable, so it can be a dict key or a set member |
bool(...) |
O(1) | O(1) | Always True |
repr(...), str(...) |
O(1) | O(1) | 'Ellipsis' |
copy.copy(...), copy.deepcopy(...), a pickle round trip |
O(1) | O(1) | Return the singleton itself, so is ... still holds afterwards |
obj[...] on an instance |
O(1) | O(1) | Hands the singleton to type(obj).__getitem__; that method decides cost and meaning |
The Singleton¶
import copy
import pickle
import types
assert ... is Ellipsis # O(1) - one object, two spellings
assert type(...) is types.EllipsisType # O(1)
assert type(...)() is ... # O(1) - "constructing" returns the singleton
assert copy.deepcopy(...) is ... # O(1)
assert pickle.loads(pickle.dumps(...)) is ...
assert bool(...) is True # O(1) - always truthy
assert repr(...) == 'Ellipsis'
try:
type('MyEllipsis', (types.EllipsisType,), {})
except TypeError as error:
assert 'not an acceptable base type' in str(error)
else:
raise AssertionError('the ellipsis type was subclassed')
Subscripting With Ellipsis¶
On an instance, obj[...] passes the singleton to obj.__getitem__ and nothing more, so the
receiving type decides what it means and what it costs. Instances of list, tuple, str,
bytes and range reject it;
a type that opts in can treat it as "everything", or, inside a tuple subscript, as "every leading
dimension".
data = [[1, 2, 3], [4, 5, 6]]
try:
data[...]
except TypeError as error:
assert 'list indices must be integers or slices' in str(error)
else:
raise AssertionError('a list accepted Ellipsis')
class Grid:
"""Rows of values; `...` stands for every row."""
def __init__(self, rows):
self.rows = rows
def __getitem__(self, key):
if key is ...: # O(1) - identity check
return self.rows
if isinstance(key, tuple) and key[0] is ...: # grid[..., j]
return [row[key[1]] for row in self.rows] # O(r), r = rows
return self.rows[key]
grid = Grid(data)
assert grid[...] is data # O(1) - the rows themselves, not a copy
assert grid[..., 0] == [1, 4] # O(r) - column 0 of every row
assert grid[1] == [4, 5, 6] # O(1)
Placeholder Bodies¶
A body of ... is a no-op expression statement, so a stub written with it returns None, the
same as one written with pass. In an abstract method, @abstractmethod is what blocks
instantiation; the body only has to be valid syntax.
from abc import ABC, abstractmethod
def not_written_yet():
...
assert not_written_yet() is None # O(1)
class DataStore(ABC):
@abstractmethod
def save(self, data):
...
class MemoryStore(DataStore):
def __init__(self):
self.items = []
def save(self, data):
super().save(data) # runs the `...` body: returns None
self.items.append(data) # O(1) amortized
try:
DataStore()
except TypeError as error:
assert 'abstract' in str(error)
else:
raise AssertionError('an abstract class was instantiated')
store = MemoryStore()
store.save('x')
assert store.items == ['x']
Type Hints¶
In a type hint, ... has two meanings. tuple[int, ...] is a tuple of any length whose items
are all int, and Callable[..., int] is a callable whose arguments are not checked. Either
way the hint keeps Ellipsis as an argument.
from collections.abc import Callable
from typing import get_args
Row = tuple[int, ...] # any number of ints
Pair = tuple[int, int] # exactly two
def widen(row: Row) -> Row:
return tuple(v * 2 for v in row) # O(n), n = items
assert widen((1, 2, 3)) == (2, 4, 6)
assert get_args(Row) == (int, Ellipsis) # O(1)
assert get_args(Pair) == (int, int)
assert get_args(Callable[..., int]) == (Ellipsis, int) # O(1)
Ellipsis as a Sentinel¶
None is the usual "no value" marker, which makes it ambiguous when None is itself a
legitimate argument. ... is a distinct singleton, so it can mean "not supplied" while None
stays an ordinary value, and the check is one identity test. That holds only while ... is
reserved for that meaning: a caller who passes ... explicitly is treated as passing nothing.
MISSING = ...
def get(mapping, key, default=MISSING):
"""Look up a key, distinguishing 'no default' from 'default is None'."""
if key in mapping: # O(1) average
return mapping[key] # O(1) average
if default is MISSING: # O(1) - identity check
raise KeyError(key)
return default
data = {'a': 1, 'b': None}
assert get(data, 'b') is None # the stored value
assert get(data, 'z', None) is None # the supplied default
for missing in ((), (...,)): # an explicit ... counts as no default
try:
get(data, 'z', *missing)
except KeyError:
pass
else:
raise AssertionError('a missing key with no default returned a value')
Comparing With Ellipsis¶
x is ... asks only whether x is the singleton. x == ... asks x too: the ellipsis type
has no __eq__ of its own, so whichever operand defines one decides the answer. Ordering
comparisons are not defined.
class EqualsEverything:
def __eq__(self, other):
return True
value = EqualsEverything()
assert (value == ...) is True # O(1) - value.__eq__ answered
assert (value is ...) is False # O(1) - identity cannot be overridden
assert (... == ...) is True
assert (... == None) is False
try:
... < ...
except TypeError as error:
assert "'<' not supported" in str(error)
else:
raise AssertionError('Ellipsis supported ordering')
Performance Best Practices¶
✅ Do:
- Test with
x is ...: one identity check, and no operand's__eq__can change the answer - Use
...as a "not supplied" sentinel whereNoneis a legitimate value - Write
tuple[int, ...]for a homogeneous tuple of any length, andCallable[..., R]when the arguments are not checked
❌ Avoid:
x == ...as a test for the singleton: an operand's__eq__can answerTrue- Returning
...from a binary operator method to mean "unsupported": it becomes the result, whereNotImplementedlets Python try the other operand's reflected method - Subscripting a
list,tuple,str,bytesorrangewith...: they raiseTypeError
Version Notes¶
- Python 3.10+:
types.EllipsisTypenames the type of...
Related Constants¶
- None - the usual sentinel, when
Noneis not itself a valid value - NotImplemented - what a binary operator method returns to defer to the other operand
- True - Boolean true
- False - Boolean false