When is the usage of the Python Ellipsis to be preferred over 'pass'?

Question:

I was just looking up the way to go for defining an abstract base class in Python, when I found the abc module (https://docs.python.org/3/library/abc.html).

After a bit of reading I saw the following class:

class C(ABC):
    @abstractmethod
    def my_abstract_method(self, ...):
        ...
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...

Wondering about the triple dots, which I found it is called the Ellipsis (https://docs.python.org/3/library/constants.html#Ellipsis). I’ve seen and used it so far only in combination with type hints, where it maked perfectly sense.

But why would one use the Ellipsis in the definition of an abstract method? Personally, I would either do

def my_abstract_method(self):
    raise RuntimeError("NotImplemented")

or

def my_abstract_method(self):
    pass

So why is the Ellipsis preferred over pass in the official documentation? Is it just opiniated?

Asked By: samwise

||

Answers:

Using the Ellipsis literal as the body of a function does nothing. It’s purely a matter of style if you use it instead of pass or some other statement. If you give your function a docstring, you don’t even need to put any statement after the line with the docstring.

If the official Python documentation is inconsistent, it’s probably because the Python core developers who write the docs don’t themselves have a consistent opinion on which looks best. PEP 8 does not give any recommendation on what to use for the body of dummy functions (though it does use lots of ...s to stand in for things in its examples, sometimes in places where you can’t actually use an Ellipsis in real code).

So for your own code, use whatever you like best. If you want to be formal about it, write down your preferences in your style guide and then always do whatever the guide says. You don’t need to follow the same style as anyone else (unless that person is your boss).

One final note: You can’t use ... as a parameter name in a function definition (def my_abstract_method(self, ...): raises a SyntaxError, regardless of what’s on the next line).

Answered By: Blckknght

I looked into python (3.7) docs and

pydoc ...
The Ellipsis Object
*******************

This object is commonly used by slicing (see Slicings).  It supports
no special operations.  There is exactly one ellipsis object, named
"Ellipsis" (a built-in name).  "type(Ellipsis)()" produces the
"Ellipsis" singleton.

It is written as "Ellipsis" or "...".

Related help topics: SLICINGS

and

pydoc pass
The "pass" statement
********************

   pass_stmt ::= "pass"

"pass" is a null operation Ś when it is executed, nothing happens. It
is useful as a placeholder when a statement is required syntactically,
but no code needs to be executed, for example:

   def f(arg): pass    # a function that does nothing (yet)

   class C: pass       # a class with no methods (yet)

So it looks like that at least from pydoc point of view pass is way to define function which does nothing.

Answered By: Daweo

I almost only ever use ellipses in abstract classes, because of how abstract classes work.

To me, pass means "do nothing" or "NOOP", but ... means "details to be defined elsewhere".

An abstract method literally cannot not be overridden. Now, you can technically call super().abstractmeth() and run code that’s defined in the abstractmethod definition, but the usual pattern I use is to use ellipses here to indicate when reading that the content of the method should be completely redefined somewhere else.

If you just put a pass someone might think that this is a stub method to be filled out later, but the ellipses usually makes people realize that something a little different is going on.

Answered By: noharmpun

I agree with @Blckknght and @noharmpun. The resulting bytecode is pretty much the same.

In [13]: dis.dis("def B(): ...; B()")


  1           0 LOAD_CONST               0 (<code object B at 0x111bf6130, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('B')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (B)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

Disassembly of <code object B at 0x111bf6130, file "<dis>", line 1>:
  1           0 LOAD_GLOBAL              0 (B)
              2 CALL_FUNCTION            0
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE
In [14]: dis.dis("def A(): pass; A()")


  1           0 LOAD_CONST               0 (<code object A at 0x111bf59a0, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('A')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (A)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

Disassembly of <code object A at 0x111bf59a0, file "<dis>", line 1>:
  1           0 LOAD_GLOBAL              0 (A)
              2 CALL_FUNCTION            0
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

One more little note:
I’m using pass over ... because the last one is not so easy to exclude in coverage reports.
Both pass and ... will be reported as uncovered lines inside @abstractmethod.
coverage has special syntax to exclude lines, but ... considerated as regex "skip all text".
Probably it can be solved somehow by escape symbols or special syntax…
P.S. Another workaround is to exclude @abstractmehod lines in coverage.

Answered By: Давид Шико
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.