The pass Statement: How to Do Nothing in Python

The pass Statement: How to Do Nothing in Python

by Moshe Zadka Dec 16, 2020 basics python

In Python, the pass keyword is an entire statement in itself. This statement doesn’t do anything: it’s discarded during the byte-compile phase. But for a statement that does nothing, the Python pass statement is surprisingly useful.

Sometimes pass is useful in the final code that runs in production. More often, pass is useful as scaffolding while developing code. In specific cases, there are better alternatives to doing nothing.

In this tutorial, you’ll learn:

  • What the Python pass statement is and why it’s useful
  • How to use the Python pass statement in production code
  • How to use the Python pass statement as an aid while developing code
  • What the alternatives to pass are and when you should use them

Python pass Statement: Syntax and Semantics

In Python syntax, new indented blocks follow a colon character (:). There are several places where a new indented block will appear. When you start to write Python code, the most common places are after the if keyword and after the for keyword:

>>>
>>> for x in [1, 2, 3]:
...     y = x + 1
...     print(x, y)
...
1 2
2 3
3 4

After the for statement is the body of the for loop, which consists of the two indented lines immediately following the colon.

In this case, there are two statements in the body that are repeated for each value:

  1. y = x + 1
  2. print(x, y)

The statements inside this type of block are technically called a suite in the Python grammar. A suite must include one or more statements. It can’t be empty.

To do nothing inside a suite, you can use Python’s special pass statement. This statement consists of only the single keyword pass. While you can use pass in many places in Python, it’s not always useful:

>>>
>>> if 1 + 1 == 2:
...     print("math is ok")
...     pass
...     print("but this is to be expected")
...
math is ok
but this is to be expected

In this if statement, removing the pass statement would keep the functionality the same and make your code shorter. You might be wondering why the Python syntax includes a statement that tells the interpreter to do nothing. Couldn’t you achieve the same result by not writing a statement at all?

In some cases, explicitly telling Python to do nothing serves an important purpose. For example, because the pass statement doesn’t do anything, you can use it to fulfill the requirement that a suite include at least one statement:

>>>
>>> if 1 + 1 == 3:
...
  File "<stdin>", line 2

    ^
IndentationError: expected an indented block

Even if you don’t want to add any code inside the if block, an if block with no statement creates an empty suite, which is invalid Python syntax.

To fix this, you can use pass:

>>>
>>> if 1 + 1 == 3:
...     pass
...

Now, thanks to pass, your if statement is valid Python syntax.

Temporary Uses of pass

There are many situations in which pass can be useful to you while you’re developing, even if it won’t appear in the final version of your code. Much like scaffolding, pass can be handy for holding up the main structure of your program before you fill in the details.

It might sound strange to write code that will be deleted later, but doing things this way can accelerate your initial development.

Future Code

There are many cases where the structure of the code requires, or could use, a block. While you may eventually have to write code there, it’s sometimes hard to get out of the flow of working on something specific and start working on a dependency. In these cases, a pass statement is a useful way to do the minimal amount of work for the dependency so you can go back to what you were working on.

As a concrete example, imagine writing a function that processes a string and then both writes the result to a file and returns it:

def get_and_save_middle(data, fname):
    middle = data[len(data)//3:2*len(data)//3]
    save_to_file(middle, fname)
    return middle

This function saves and returns the middle third of a string. You don’t need to finish implementing save_to_file() before you can test the output for an off-by-one error. However, if save_to_file() doesn’t exist in some form, then you’ll get an error.

It’s possible to comment out the call to save_to_file(), but then you’d have to remember to uncomment the call after confirming get_and_save_middle() works well. Instead, you can quickly implement save_to_file() with a pass statement:

def save_to_file(data, fname):
    pass # TODO: fill this later

This function doesn’t do anything, but it allows you to test get_and_save_middle() without errors.

Another use case for pass is when you’re writing a complicated flow control structure, and you want a placeholder for future code. When implementing the fizz-buzz challenge with the modulo operator, for example, it’s useful to first understand the structure of the code:

if idx % 15 == 0:
    pass # Fizz-Buzz
elif idx % 3 == 0:
    pass # Fizz
elif idx % 5 == 0:
    pass # Buzz
else:
    pass # Idx

This structure identifies what should be printed in each case, which gives you the skeleton of the solution. Such structural skeletons are useful when trying to figure out the branching logic of which if statements are needed and in which order.

For example, in this case, a critical insight is that the first if statement needs to check divisibility by 15 because any number that is divisible by 15 would also be divisible by 5 and 3. This structural insight is useful regardless of the details of the specific output.

After you figure out the core logic of the problem, you can decide whether you’ll print() directly in the code:

def fizz_buzz(idx):
    if idx % 15 == 0:
        print("fizz-buzz")
    elif idx % 3 == 0:
        print("fizz")
    elif idx % 5 == 0:
        print("buzz")
    else:
        print(idx)

This function is straightforward to use since it directly prints the strings. However, it’s not a pleasant function to test. This can be a useful trade-off. However, in coding interviews, the interviewer will sometimes ask you to write tests. Writing the structure first allows you to make sure you understand the logical flow before checking what the other requirements are.

An alternative would be to write a function that returns the string and then do the looping elsewhere:

def fizz_buzz(idx):
    if idx % 15 == 0:
        return "fizz-buzz"
    elif idx % 3 == 0:
        return "fizz"
    elif idx % 5 == 0:
        return "buzz"
    else:
        return str(idx)

This function pushes the printing functionality up the stack and is easier to test.

Figuring out the core conditionals and structure of the problem using pass makes it easier to decide exactly how the implementation should work later on.

This approach is also useful when writing classes. If you need to write a class to implement something, but you don’t fully understand the problem domain, then you can use pass to first understand the best layout for your code architecture.

For example, imagine you’re implementing a Candy class, but the properties you need aren’t obvious. Eventually you’ll need to conduct some careful requirement analysis, but while implementing the basic algorithms, you can make it obvious that the class isn’t ready yet:

class Candy:
    pass

This allows you to instantiate members of the class and pass them around without having to decide what properties are relevant to the class.

Commented Out Code

When you comment out code, it’s possible to invalidate the syntax by removing all code in a block. If you have an ifelse condition, then it might be useful to comment out one of the branches:

def process(context, input_value):
    if input_value is not None:
        expensive_computation(context, input_value)
    else:
        logging.info("skipping expensive: %s", input_value)

In this example, expensive_computation() runs code that takes a long time, such as multiplying big arrays of numbers. While you’re debugging, you might need to temporarily comment out the expensive_computation() call.

For example, maybe you want to run this code against some problematic data and see why there are so many values that aren’t None by checking the logs for the description. Skipping the expensive computation for the valid values would speed up testing quite a bit.

However, this isn’t valid code:

def process(context, input_value):
    if input_value is not None:
        # Temporarily commented out the expensive computation
        # expensive_computation(context, input_value)
    else:
        logging.info("skipping expensive: %s", input_value)

In this example, the if branch doesn’t have any statements in it. Comments are stripped early in the parsing process, before the indentation is inspected to see where blocks begin and end.

In this case, adding a pass statement makes the code valid:

def process(context, input_value):
    if input_value is not None:
        # Temporarily commented out the expensive computation
        # expensive_computation(context, input_value)
        # Added pass to make code valid
        pass
    else:
        logging.info("skipping expensive: %s", input_value)

Now it’s possible to run the code, skip the expensive computation, and generate the logs with the useful information.

Partially commenting out code while troubleshooting behavior is useful in many cases. In a case like the example above, you might comment out code that takes a long time to process and isn’t the source of the problem.

Another situation in which you might want to comment out code while troubleshooting is when the commented-out code has an undesirable side effect, like sending an email or updating a counter.

Similarly, sometimes it’s useful to comment out a whole function while keeping the call. If you’re using a library that needs a callback, then you might write code like this:

def write_to_file(fname, data):
    with open(fname, "w") as fpout:
        fpout.write(data)

get_data(source).add_callback(write_to_file, "results.dat")

This code calls get_data() and attaches a callback to the result.

It might be useful to have a test run that discards the data in order to make sure that the source is given correctly. However, this isn’t valid Python code:

def write_to_file(fname, data):
    # Discard data for now
    # with open(fname, "w") as fpout:
    #     fpout.write(data)

get_data(source).add_callback(write_to_file, "results.dat")

Since the function has no statements in its block, Python can’t parse this code.

Once again, pass can help you:

def write_to_file(fname, data):
    # Discard data for now
    # with open(fname, "w") as fpout:
    #     fpout.write(data)
    pass

get_data(source).add_callback(write_to_file, "results.dat")

This is valid Python code that will discard the data and help you confirm that the arguments are correct.

Markers for Debuggers

When you run code in a debugger, it’s possible to set a breakpoint in the code where the debugger will stop and allow you to inspect the program state before continuing.

When a test run triggers a breakpoint often, such as in a loop, there might be many instances where the program state isn’t interesting. To address this problem, many debuggers also allow a conditional breakpoint, a breakpoint that will trigger only when a condition is true. For example, you might set a breakpoint in a for loop that’s triggered only if a variable is None to see why this case isn’t handled correctly.

However, many debuggers allow you to set only a few basic conditions on your breakpoints, such as equality or maybe a size comparison. You might need a more complicated condition, such as checking that a string is a palindrome before breaking.

While the debugger might not be capable of checking for palindromes, Python can do so with minimal effort. You can take advantage of that functionality by having a do-nothing if statement and setting a breakpoint on the pass line:

for line in filep:
    if line == line[::-1]:
        pass # Set breakpoint here
    process(line)

By checking for palindromes with line == line[::-1], you now have a line that executes only if the condition is true.

Although the pass line doesn’t do anything, it makes it possible for you to set a breakpoint there. Now you can run this code in a debugger and break only on strings that are palindromes.

Empty Functions

In some cases, it may even be useful for you to include an empty function in the deployed version of your code. For example, a function in a library might expect a callback function to be passed in.

An even more common case is when your code defines a class that inherits from a class that expects a method to be overridden. However, in your specific case, you don’t need to do anything. Or perhaps the reason you’re overriding the code is to prevent an overridable method from doing anything.

In all those cases, you’ll need to write an empty function or method. Once again, the problem is that having no lines after the def line isn’t valid Python syntax:

>>>
>>> def ignore_arguments(record, status):
...
  File "<stdin>", line 2

    ^
IndentationError: expected an indented block

This fails because a function, like other blocks, has to include at least one statement. To fix this problem, you can use pass:

>>>
>>> def ignore_arguments(record, status):
...     pass
...

Now that the function has a statement, even one that does nothing, it’s valid Python syntax.

As another example, imagine you have a function that expects a file-like object to write to. However, you want to call the function for another reason and would like to discard the output. You can use pass to write a class that discards all data:

class DiscardingIO:
    def write(self, data):
        pass

Instances of this class support the .write() method but discard all data immediately.

In both of these examples, it’s important that a method or function exists, but it doesn’t need to do anything. Because Python blocks must have statements, you can make empty functions or methods valid by using pass.

Empty Classes

In Python, exception inheritance is important because it marks which exceptions are caught. For example, the built-in exception LookupError is a parent of KeyError. A KeyError exception is raised when a nonexistent key is looked up in a dictionary. This means you can use LookupError to catch a KeyError:

>>>
>>> empty={}
>>> try:
...     empty["some key"]
... except LookupError as exc:
...     print("got exception", repr(exc))
...
got exception KeyError('some key')
>>> issubclass(KeyError, LookupError)
True

The exception KeyError is caught even though the except statement specifies LookupError. This is because KeyError is a subclass of LookupError.

Sometimes you want to raise specific exceptions in your code because they have a specific recovery path. However, you want to make sure that those exceptions inherit from a general exception in case someone is catching the general exception. These exception classes have no behavior or data. They’re just markers.

In order to see the usefulness of a rich exception hierarchy, you can consider password rule checking. Before trying to change the password on a website, you want to test it locally for the rules it enforces:

  • At least eight characters
  • At least one number
  • At least one special character, such as a question mark (?), an exclamation point (!), or a period (.).

Each of those errors should have its own exception. The following code implements those rules:

# password_checker.py
class InvalidPasswordError(ValueError):
    pass

class ShortPasswordError(InvalidPasswordError):
    pass

class NoNumbersInPasswordError(InvalidPasswordError):
    pass

class NoSpecialInPasswordError(InvalidPasswordError):
    pass

def check_password(password):
    if len(password) < 8:
        raise ShortPasswordError(password)
    for n in "0123456789":
        if n in password:
            break
    else:
        raise NoNumbersInPasswordError(password)
    for s in "?!.":
        if s in password:
            break
    else:
        raise NoSpecialInPasswordError(password)

This function will raise an exception if the password doesn’t follow the specified rules. A more realistic example would note all the rules that haven’t been followed, but that’s beyond the scope of this tutorial.

You can use this function in a wrapper to print the exception in a nice way:

>>>
>>> from password_checker import check_password
>>> def friendly_check(password):
...     try:
...         check_password(password)
...     except InvalidPasswordError as exc:
...         print("Invalid password", repr(exc))
...
>>> friendly_check("hello")
Invalid password ShortPasswordError('hello')
>>> friendly_check("helloworld")
Invalid password NoNumbersInPasswordError('helloworld')
>>> friendly_check("helloworld1")
Invalid password NoSpecialInPasswordError('helloworld1')

In this case, friendly_check() catches only InvalidPasswordError since other ValueError exceptions are probably bugs in the checker itself. It prints out the exception’s name and value, which shows the rule that wasn’t followed.

In some situations, your users might not care exactly which problems exist in the input. In that case, you would just want to catch ValueError:

def get_username_and_password(credentials):
    try:
        name, password = credentials.split(":", 1)
        check_password(password)
    except ValueError:
        return get_default_credentials()
    else:
        return name, value

In this code, all invalid input is treated the same since you don’t care what problems the credentials have.

Because of these differing use cases, check_password() needs all four exceptions:

  1. InvalidPasswordError
  2. ShortPasswordError
  3. NoNumbersPasswordError
  4. NoSpecialPasswordError

Each of these exceptions describes a different rule being violated. In code that matches a string against more sophisticated rules, there might be many more of these, arranged in a complex structure.

Despite the need for four different classes, none of the classes has any behavior. The pass statement allows you to define all four classes quickly.

Marker Methods

Some methods in classes exist not to be called but to mark the class as somehow being associated with this method.

The Python standard library has the abc module. The name of the module stands for abstract base class. This module helps define classes that aren’t meant to be instantiated but rather serve as a common base for some other classes.

If you’re writing code to analyze usage patterns of a web server, then you might want to differentiate between requests coming from logged-in users and those coming from unauthenticated connections. You could model this by having an Origin superclass that has two subclasses: LoggedIn and NotLoggedIn.

Nothing should ever instantiate the Origin class directly. Each request should come from either a LoggedIn origin or a NotLoggedIn origin. Here’s a minimalist implementation:

import abc

class Origin(abc.ABC):
    @abc.abstractmethod
    def description(self):
        # This method will never be called
        pass

class NotLoggedIn(Origin):
    def description(self):
        return "unauthenticated connection"

class LoggedIn(Origin):
    def description(self):
        return "authenticated connection"

While a real Origin class would be more complicated, this example shows some of the basics. Origin.description() will never be called since all the subclasses must override it.

Because Origin has an abstractmethod, it can’t be instantiated:

>>>
>>> Origin()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Origin with abstract...
>>> logged_in.description()
'authenticated connection'
>>> not_logged_in.description()
'unauthenticated connection'

Classes with abstractmethod methods can’t be instantiated. This means that any object that has Origin as a superclass will be an instance of a class that overrides description(). Because of this, the body in Origin.description() doesn’t matter, but the method needs to exist to indicate that all subclasses must instantiate it.

Because method bodies can’t be empty, you have to put something in Origin.description(). Once again, the do-nothing statement pass is a good option to make it obvious that you’ve included the line just for syntactic reasons.

A more modern way to indicate methods are needed is to use a Protocol, which is available in the standard library in Python 3.8 and above. In older Python versions, it’s available with the typing_extensions backports.

A Protocol is different from an abstract base class in that it’s not explicitly associated with a concrete class. Instead, it relies on type matching to associate it at type-check time with mypy.

The methods in a Protocol are never called. They serve only to mark the types of needed methods:

>>>
>>> from typing_extensions import Protocol
>>> class StringReader(Protocol):
...     def read(self, int) -> str:
...         pass

Demonstrating how to use a Protocol like this in mypy isn’t relevant to the pass statement. But it is important to see that the body of the method has only the pass statement.

There are more examples of such markers being used outside the Python language and standard libraries. For example, they’re used in the zope.interface package to indicate interface methods and in automat to indicate inputs to a finite-state automaton.

In all these cases, classes need to have methods but never call them. Because of this, the body doesn’t matter. But since the body can’t be empty, you can use the pass statement to add a body.

Alternatives to pass

The pass statement isn’t the only way to do nothing in your code. It’s not even the shortest, as you’ll see later. It’s not even always the best or most Pythonic approach.

Any expression in Python is a valid statement, and every constant is a valid expression. So the following expressions all do nothing:

  • None
  • True
  • 0
  • "hello I do nothing"

You can use any one of these expressions as the only statement in a suite, and it will accomplish the same task as pass. The main reason to avoid using them as do-nothing statements is that they’re unidiomatic. When you use them, it’s not obvious to people who read your code why they’re there.

In general, the pass statement, while taking more characters to write than, say, 0, is the best way to communicate to future maintainers that the code block was intentionally left blank.

Docstrings

There’s one important exception to the idiom of using pass as a do-nothing statement. In classes, functions, and methods, using a constant string expression will cause the expression to be used as the object’s .__doc__ attribute.

The .__doc__ attribute is used by help() in the interactive interpreter and by various documentation generators, many IDEs, and other developers reading the code. Some code styles insist on having it in every class, function, or method.

Even when a docstring isn’t mandatory, it’s often a good substitute for the pass statement in an empty block. You can modify some examples from earlier in this this tutorial to use a docstring instead of pass:

class StringReader(Protocol):
      def read(self, length: int) -> str:
          """
          Read a string
          """

class Origin(abc.ABC):
    @abc.abstractmethod
    def description(self):
        """
        Human-readable description of origin
        """

class TooManyOpenParens(ParseError):
    """
    Not all open parentheses were closed
    """

class DiscardingIO:
    def write(self, data):
        """
        Ignore data
        """

In all these cases, the docstring makes the code clearer. The docstring will also be visible when you use this code in the interactive interpreter and in IDEs, making it even more valuable.

One technical advantage of docstrings, especially for those functions or methods that never execute, is that they’re not marked as “uncovered” by test coverage checkers.

Ellipsis

In mypy stub files, the recommended way to fill a block is to use an ellipsis (...) as a constant expression. This is an obscure constant that evaluates to Ellipsis:

>>>
>>> ...
Ellipsis
>>> x = ...
>>> type(x), x
(<class 'ellipsis'>, Ellipsis)

The Ellipsis singleton object, of the built-in ellipsis class, is a real object that’s produced by the ... expression.

The original use for Ellipsis was in creating multidimensional slices. However, it’s now also the recommended syntax to fill in a suite in a stub file:

# In a `.pyi` file:
def add(a: int, b: int)-> int:
    ...

This function not only does nothing, but it’s also in a file that the Python interpreter never evaluates.

Raise an Error

In cases where the functions or methods are empty because they never execute, sometimes the best body for them is raise NotImplementedError("this should never happen"). While this does technically do something, it’s still a valid alternative to a pass statement.

Permanent Uses of pass

Sometimes the use of the pass statement isn’t temporary—it’ll remain in the final version of the running code. In those cases, there’s no better alternative or more common idiom to fill an otherwise empty block than using pass.

Using pass in Exception Catching

When using try ... except to catch an exception, you sometimes don’t need to do anything about the exception. In that situation, you can use the pass statement to silence the error.

If you want to make sure a file doesn’t exist, then you can use os.remove(). This function will raise an error if the file isn’t there. However, the file not being there is exactly what you want in this case, so the error is unnecessary.

Here’s a function that removes a file and doesn’t fail if the file doesn’t exist:

import os

def ensure_nonexistence(fname):
    try:
        os.remove(fname)
    except FileNotFoundError:
        pass

Because nothing needs to be done if a FileNotFoundError is raised, you can use pass to have a block with no other statements.

Note that the pass statement will often be replaced by a logging statement. However, there’s no requirement to do this if the error is expected and well understood.

In this case, you could also use the context manager contextlib.suppress() to suppress the error. However, if you need to handle some errors while ignoring others, then it’s more straightforward to have an empty except class with nothing except the pass statement.

For example, if you wanted to have ensure_nonexistence() deal with directories as well as files, then you could use this approach:

import os
import shutil

def ensure_nonexistence(fname):
    try:
       os.remove(fname)
    except FileNotFoundError:
       pass
    except IsADirectoryError:
       shutil.rmtree(fname)

Here, you ignore the FileNotFoundError while retrying the IsADirectoryError.

In this example, the order of the except statements doesn’t matter because FileNotFoundError and IsADirectoryError are siblings, and both inherit from OSError. If there were a case that handled the general OSError, perhaps by logging and ignoring it, then the order would matter. In that scenario, FileNotFoundError and its pass statement would have to come before OSError.

Using pass in ifelif Chains

When you use long ifelif chains, sometimes you don’t need to do anything in one case. However, you can’t skip that elif because execution would continue through to the other condition.

Imagine that a recruiter gets tired of using the fizz-buzz challenge as an interview question and decides to ask it with a twist. This time, the rules are a bit different:

  • If the number is divisible by 20, then print "twist".
  • Otherwise, if the number is divisible by 15, then print nothing.
  • Otherwise, if the number is divisible by 5, then print "fizz".
  • Otherwise, if the number is divisible by 3, then print "buzz".
  • Otherwise, print the number.

The interviewer believes that this new twist will make answers more interesting.

As with all coding interview questions, there are many ways to solve this challenge. But one way is to use a for loop with a chain that mimics the description above:

for x in range(100):
    if x % 20 == 0:
       print("twist")
    elif x % 15 == 0:
       pass
    elif x % 5 == 0:
       print("fizz")
    elif x % 3 == 0:
       print("buzz")
    else:
       print(x)

The ifelif chain mirrors the logic of moving to the next option only if the previous one did not take.

In this example, if you removed the if x % 15 clause completely, then you would change the behavior. Instead of printing nothing for numbers divisible by 15, you would print "fizz". The clause is essential even if there’s nothing to do in that case.

This use case for the pass statement allows you to avoid refactoring the logic and to keep the code arranged in a way that matches the description of the behavior.

Conclusion

You now understand what the Python pass statement does. You’re ready to use it to improve your development and debugging speed as well as to deploy it tactfully in your production code.

In this tutorial, you’ve learned:

  • What the Python pass statement is and why it’s useful
  • How to use the Python pass statement in production code
  • How to use the Python pass statement as an aid while developing code
  • What the alternatives to pass are and when you should use them

Now you’ll be able to write better and more efficient code by knowing how to tell Python to do nothing.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Moshe Zadka

Moshe Zadka Moshe Zadka

Moshe has been using Python since 1998. He has contributed to CPython, and is a founding member of the Twisted project. He has been teaching Python in various venues since 2002.

» More about Moshe

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Real Python Comment Policy: The most useful comments are those written with the goal of learning from or helping out other readers—after reading the whole article and all the earlier comments. Complaints and insults generally won’t make the cut here.

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Keep Learning

Related Tutorial Categories: basics python