Expression vs Statement in Python: What's the Difference?

Expression vs Statement in Python: What's the Difference?

by Bartosz Zaczyński Dec 04, 2024 basics python

After working with Python for a while, you’ll eventually come across two seemingly similar terms: expression and statement. When you browse the official documentation or dig through a Python-related thread on an online forum, you may get the impression that people use these terms interchangeably. That’s often true, but confusingly enough, there are cases when the expression vs statement distinction becomes important.

So, what’s the difference between expressions and statements in Python?

Take the Quiz: Test your knowledge with our interactive “Expression vs Statement in Python: What's the Difference?” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Expression vs Statement in Python: What's the Difference?

In this quiz, you'll test your understanding of Python expressions vs statements. Knowing the difference between these two is crucial for writing efficient and readable Python code.

In Short: Expressions Have Values and Statements Cause Side Effects

When you open the Python glossary, you’ll find the following two definitions:

Expression: A piece of syntax which can be evaluated to some value. (…) (Source)

Statement: A statement is part of a suite (a “block” of code). A statement is either an expression or one of several constructs with a keyword, (…) (Source)

Well, that isn’t particularly helpful, is it? Fortunately, you can summarize the most important facts about expressions and statements in as little as three points:

  1. All instructions in Python fall under the broad category of statements.
  2. By this definition, all expressions are also statements—sometimes called expression statements.
  3. Not every statement is an expression.

In a technical sense, every line or block of code is a statement in Python. That includes expressions, which represent a special kind of statement. What makes an expression special? You’ll find out now.

Expressions: Statements With Values

Essentially, you can substitute all expressions in your code with the computed values, which they’d produce at runtime, without changing the overall behavior of your program. Statements, on the other hand, can’t be replaced with equivalent values unless they’re expressions.

Consider the following code snippet:

Python
>>> x = 42
>>> y = x + 8
>>> print(y)
50

In this example, all three lines of code contain statements. The first two are assignment statements, while the third one is a call to the print() function.

When you look at each line more closely, you can start disassembling the corresponding statement into subcomponents. For example, the assignment operator (=) consists of the parts on the left and the right. The part to the left of the equal sign indicates the variable name, such as x or y, and the part on the right is the value assigned to that variable.

The word value is the key here. Notice that the variable x is assigned a literal value, 42, that’s baked right into your code. In contrast, the following line assigns an arithmetic expression, x + 8, to the variable y. Python must first calculate or evaluate such an expression to determine the final value for the variable when your program is running.

Arithmetic expressions are just one example of Python expressions. Others include logical expressions, conditional expressions, and more. What they all have in common is a value to which they evaluate, although each value will generally be different. As a result, you can safely substitute any expression with the corresponding value:

Python
>>> x = 42
>>> y = 50
>>> print(y)
50

This short program gives the same result as before and is functionally identical to the previous one. You’ve calculated the arithmetic expression by hand and inserted the resulting value in its place.

Note that you can evaluate x + 8, but you can’t do the same with the assignment y = x + 8, even though it incorporates an expression. The whole line of code represents a pure statement with no intrinsic value. So, what’s the point of having such statements? It’s time to dive into Python statements and find out.

Statements: Instructions With Side Effects

Statements that aren’t expressions cause side effects, which change the state of your program or affect an external resource, such as a file on disk. For example, when you assign a value to a variable, you define or redefine that variable somewhere in Python’s memory. Similarly, when you call print(), you effectively write to the standard output stream (stdout), which, by default, displays text on the screen.

Okay. You’ve covered statements that are expressions and statements that aren’t expressions. From now on, you can refer to them as pure expressions and pure statements, respectively. But it turns out there’s a middle ground here.

Some instructions can have a value and cause side effects at the same time. In other words, they’re expressions with side effects or, equivalently, statements with a value. A prime example of that would be Python’s next() function built into the language:

Python
>>> fruit = iter(["apple", "banana", "orange"])
>>> next(fruit)
'apple'
>>> next(fruit)
'banana'
>>> next(fruit)
'orange'

Here, you define an iterator object named fruit, which lets you iterate over a list of fruit names. Each time you call next() on this object, you modify its internal state by moving the iterator to the next item in the list. That’s your side effect. Simultaneously, next() returns the corresponding fruit name, which is the value part of the equation.

In general, it’s considered best practice not to mix values with side effects in a single expression. Functional programming encourages the use of pure functions, while procedural languages make a distinction between functions that return a value and procedures that don’t.

Finally, while this may sound counterintuitive at first, Python has a statement that doesn’t evaluate to anything nor cause any side effects. Can you guess what it is and when you’d want to use it? To give you a little hint, it’s commonly known as a no-op in computer science, which is short for no operation.

Guess what? It’s the Python pass statement! You often use it as a placeholder in places where a statement is syntactically required, but you don’t want to take any action. You might use these placeholders in empty function definitions or loops during your initial stages of development. Note that even though Python’s Ellipsis (...) might serve a similar purpose, it has a value, making the Ellipsis an expression.

Summary of Expressions vs Statements

To drive the point home, take a quick look at the following diagram. It will help you better understand the different types of expressions and statements in Python:

Types of Expressions and Statements
Types of Expressions and Statements

In summary, a statement can be any Python instruction, whether it’s a single line or a block of code. All four quadrants in the diagram above represent statements. However, this term is often used to refer to pure statements that only cause side effects without having a value.

In contrast, a pure expression is a special kind of statement that only evaluates to some value without causing side effects. Additionally, you may come across expressions with side effects, which are statements with a value. These are expressions that produce a value while also causing side effects. Finally, a no-op is a statement that does neither—it doesn’t produce a value nor cause any side effects.

Next up, you’ll learn how to recognize them in the wild.

How to Check if a Python Instruction Is an Expression vs Statement?

At this point, you already know that every Python instruction is technically always a statement. So, a more specific question that you might want to ask yourself is whether you’re dealing with an expression or a pure statement. You’ll answer this question twofold: manually and then programmatically using Python.

Checking Manually in the Python REPL

To quickly determine the answer to the question posed in this section, you can leverage the power of the Python REPL, which evaluates expressions as you type them. If an instruction happens to be an expression, then you’ll immediately see the default string representation of its value in the output:

Python
>>> 42 + 8
50

This is an arithmetic expression, which evaluates to 50. Since you haven’t intercepted its value, for example, by assigning the expression to a variable or passing it to a function as an argument, Python displays the computed result for you. Had the same line of code been present in a Python script, the interpreter would have ignored the evaluated value, which would’ve been lost.

In contrast, executing a pure statement in the REPL doesn’t show anything in the output:

Python
>>> import math
>>>

This is an import statement, which doesn’t have a corresponding value. Instead, it loads the specified Python package into your current namespace as a side effect.

However, you need to be careful with this approach. Sometimes, the default string representation of a computed value can be misleading. Consider this example:

Python
>>> fruit = {"name": "apple", "color": "red"}
>>> fruit.get("taste")
>>> fruit.get("color")
'red'

You define a Python dictionary that represents a fruit. The first time you call .get() on it, there’s no visible output. But then, you call .get() again with a different key, and it returns the value associated with that key, which is 'red'.

In the first case, .get() returns None to indicate a missing key-value pair since the dictionary doesn’t contain the key "taste". However, when you use a key that does exist, like "color", the method returns the corresponding value.

The Python REPL never displays None unless explicitly printed, which can sometimes lead to confusion if you’re unaware of this behavior. When in doubt, you can always call print() on the result to reveal its true value:

Python
>>> fruit.get("taste")
>>> print(fruit.get("taste"))
None

While this works, there’s a more reliable way of checking if an instruction is an expression in Python.

You can assign an instruction to a variable in order to check if it’s an r-value. Otherwise, if it’s a pure statement, then you won’t be able to use it as a value for your variable, and you’ll get this error:

Python
>>> x = import math
  File "<python-input-0>", line 1
    x = import math
        ^^^^^^
SyntaxError: invalid syntax

Python raises a SyntaxError to tell you that you can’t assign an import statement to a variable because that statement has no tangible value.

A somewhat special case is the chain assignment, which might initially look as though you’re trying to assign y = 42 to the variable x to check if it’s an expression:

Python
>>> x = y = 42

However, this makes multiple variables—x and y in this case—refer to the same value, 42. It’s a shorthand notation for making two separate assignments, x = 42 and y = 42.

Another way to tell if a piece of Python code is an expression or a pure statement involves wrapping it in parentheses. Parentheses usually help you group terms to change the default order of operations determined by operator precedence. But you can only wrap expressions, not statements:

Python
 1>>> (2 + 2)
 24
 3
 4>>> (x = 42)
 5  File "<python-input-7>", line 1
 6    (x = 42)
 7     ^^^^^^
 8SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

Line 1 contains an expression, which evaluates to four, whereas line 4 leads to a syntax error because Python unsuccessfully tries to evaluate an assignment statement. The displayed error message prompts you to change the assignment operator (=) to either the value equality (==) or the walrus operator (:=).

So far, you’ve been comparing expressions and statements manually in the Python REPL. In the next section, you’ll learn how to do this programmatically, which can help if it’s a repetitive task you wish to automate.

Building a Python Expression vs Statement Detector

To determine if an instruction is an expression or a statement programmatically, you can call Python’s eval() and exec() built-in functions. The first function allows you to evaluate expressions dynamically, and the second one can execute arbitrary code from a string:

Python
>>> eval("2 + 2")
4
>>> exec("x = 42")
>>> print(x)
42

The string "2 + 2" contains a Python expression that eval() evaluates to the integer 4. You may assign the value returned by eval() to a variable if you wish. On the other hand, exec() doesn’t return anything but causes side effects. Notice how you can access the variable defined in the string passed to exec() after calling this function within the same scope.

Both functions report a syntax error when the input string isn’t a valid expression or statement:

Python
>>> eval("2 +")
Traceback (most recent call last):
  ...
SyntaxError: invalid syntax

>>> exec("x = 2 +")
Traceback (most recent call last):
  ...
SyntaxError: invalid syntax

Additionally, eval() raises a syntax error when the supplied string contains a pure statement without a value. Simultaneously, the same statement works just fine with exec():

Python
>>> eval("x = 2 + 2")
Traceback (most recent call last):
  ...
SyntaxError: invalid syntax

>>> exec("x = 2 + 2")

You can take advantage of this discrepancy to differentiate between expressions and pure statements. But remember to call eval() before exec() to avoid false positives. Since all expressions are statements, exec() will almost always succeed whether it receives an expression or a statement as an argument:

Python
>>> eval("print('Hello, World!')")
Hello, World!

>>> exec("print('Hello, World!')")
Hello, World!

Both functions give you an identical result: Hello World!. If you called exec() and stopped there, you could mistakenly conclude that the code is a statement while it might be a valid expression. Meanwhile, all function calls in Python are technically expressions and should be classified as such. Here’s how you can address this.

To avoid code duplication, you can combine both eval() and exec() into a single function, which delegates to ast.parse() with the appropriate mode:

Python
>>> import ast

>>> def valid(code, mode):
...     try:
...         ast.parse(code, mode=mode)
...         return True
...     except SyntaxError:
...         return False
...

>>> valid("x = 2 + 2", mode="eval")
False

>>> valid("x = 2 + 2", mode="exec")
True

This function accepts a Python code snippet and the mode parameter, which can take one of two values: "eval" or "exec". The function returns False when the underlying expression or statement is syntactically incorrect. Otherwise, it returns True when the code runs successfully in the specified mode.

To top it off, you can write another helper function that will provide a textual representation for the given code snippet:

Python
>>> def describe(code):
...     if valid(code, mode="eval"):
...         return "expression"
...     elif valid(code, mode="exec"):
...         return "statement"
...     else:
...         return "invalid"
...

>>> describe("x = 2 + 2")
'statement'

>>> describe("2 + 2")
'expression'

>>> describe("2 +")
'invalid'

You first check if the given code is an expression. When it is, you return the string "expression". If not, then you can check if the code is a statement. If it qualifies as a statement, you return the string "statement". Finally, if neither condition is met, you conclude that the code is "invalid".

This approach can help when you need to perform such a test programmatically for whatever reason. Maybe you’re building your own Python REPL using structural pattern matching in Python, and you need to decide when to display an evaluated expression.

By now, you should more intuitively understand the difference between expressions and statements in Python. You should also be able to tell which is which. The next important question is whether it’s merely a semantic distinction for purists or if it holds any significance in your everyday coding practice. You’re going to find out now!

Does This Difference Matter in Your Day-to-Day Programming?

Most of the time, you don’t need to think too much about whether you’re working with an expression or a statement in Python. That said, there are two notable exceptions worth mentioning:

  1. lambda expressions
  2. assert statements

You’ll start with the first one, which is the lambda expression.

Lambda Expression

Python’s lambda keyword lets you define an anonymous function, which may be useful for one-shot operations like specifying the sort key or a condition to filter on:

Python
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]

>>> sorted(fruits, key=lambda item: item[1])
[('banana', 0), ('apple', 2), ('orange', 3)]

>>> list(filter(lambda item: item[1] > 0, fruits))
[('apple', 2), ('orange', 3)]

Here, you define a list of two-element tuples containing a fruit’s name and its respective quantity. Next, you sort this list in ascending order based on the quantity. Lastly, you filter the list, leaving only those fruits that have at least one unit.

This is a convenient approach as long as you don’t need to reference such inline functions beyond their immediate use. While you can always assign a lambda expression to a variable for later use, it isn’t syntactically equivalent to a regular function defined with def.

A function definition starts a new code block representing the function’s body. In Python, virtually every block of code belongs to a compound statement, so it can’t be evaluated.

Because statements don’t have values, you can’t assign a function definition to a variable like you can with a lambda expression:

Python
>>> inline = lambda: 42

>>> regular = (
...     def function():
...         return 42
... )
...
  File "<python-input-1>", line 2
    def function():
    ^^^
SyntaxError: invalid syntax

The variable inline holds a reference to an anonymous function defined as a lambda, whereas regular is an unsuccessful attempt to assign a named function definition to a variable.

However, you are allowed to assign a reference to a function that’s already been defined elsewhere:

Python
>>> def function():
...     return 42
...
>>> alias = function

Note the different meaning of the def function(): statement and the function reference that appears underneath it. The first one is the blueprint of what the function does, and the second one is the function’s address, which you can pass around without actually calling the function.

A function defined with the lambda keyword must always contain exactly one expression within its body. It’ll be evaluated and returned implicitly without you having to include the return statement. In fact, the use of statements in lambda functions is outright forbidden. If you try to use them, then you’ll cause a syntax error:

Python
>>> lambda: pass
  File "<python-input-0>", line 1
    lambda: pass
            ^^^^
SyntaxError: invalid syntax

You’ve learned that pass is a statement, so it has no place in a lambda expression. However, there’s a work-around for that. You can always wrap one or more statements in a function and call that function in your lambda expression, like so:

Python
>>> import tkinter as tk

>>> def on_click(age):
...     if age > 18:
...         print("You're an adult.")
...     else:
...         print("You're a minor.")
...

>>> window = tk.Tk()
>>> button = tk.Button(window, text="Click", command=lambda: on_click(42))
>>> button.pack(padx=10, pady=10)
>>> window.mainloop()

This is a minimal Python application featuring a graphical user interface (GUI) built with Tkinter. The highlighted line registers a listener for the button’s click event. The event handler is defined as an inline lambda expression, which calls a wrapper function that encapsulates a conditional statement. You couldn’t express such complex logic solely with a lambda expression.

Assert Statement

As to the assert statement, there’s a common point of confusion surrounding it, which stems from its somewhat misleading syntax. It’s too easy to forget that assert is a statement and doesn’t behave like an ordinary function call. This can sometimes cause unintended behavior when you’re not careful.

In its most basic form, the assert keyword must be followed by a logical predicate or an expression that evaluates to a Boolean value:

Python
>>> assert 18 < int(input("What's your age? "))
What's your age? 42

>>> assert 18 < int(input("What's your age? "))
What's your age? 15
Traceback (most recent call last):
  ...
AssertionError

If the expression becomes truthy, then nothing happens. If the expression is falsy, then Python raises an AssertionError, provided that you haven’t disabled assertions altogether with a command-line option or an environment variable.

You may optionally append a custom error message to be displayed along with the raised assertion error. To do that, place a comma after the predicate and include a string literal—or any expression that evaluates to one:

Python
>>> assert 18 < int(input("What's your age? ")), "You're a minor"
What's your age? 15
Traceback (most recent call last):
  ...
AssertionError: You're a minor

So far, so good. But, incorporating a longer error message might lead to less readable code, tempting you to break that line in some way.

Python offers a neat feature called implicit line continuation, which allows you to split a long instruction across multiple lines without using an explicit backslash (\). You can achieve this by enclosing your expressions in parentheses, brackets, or curly braces, making the code more organized and easier to read:

Python
>>> assert (
...     18 < int(input("What's your age? ")),
...     "You're a minor"
... )
<python-input-0>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
  assert (
What's your age? 15
>>>

This usually works, but not in this case. As you can see, modern Python versions will even issue a warning to let you know that something is probably wrong.

Remember that an assert statement expects an expression right after the keyword. When you surround your predicate and custom message with parentheses, you essentially define a tuple, which is treated as a single expression. Python evaluates such non-empty sequences to True. So, your assert statement will always pass, regardless of the actual condition you’re trying to check.

To avoid this problem, you’re better off using an explicit line continuation when you want to break a long line:

Python
>>> assert 18 < int(input("What's your age? ")), \
...     "You're a minor"
What's your age? 15
Traceback (most recent call last):
  ...
AssertionError: You're a minor

Now, you observe the expected behavior again because the predicate is evaluated correctly.

Earlier, you learned that code blocks in Python are part of compound statements. Next up, you’ll explore the differences between simple and compound statements in Python.

What Are Simple and Compound Statements in Python?

Statements are the core building blocks of your Python programs. They let you control the flow of execution and perform actions, such as assigning values to variables. You can categorize statements into two primary types:

  1. Simple statements
  2. Compound statements

Simple statements can fit on a single line, while compound statements comprise other statements that typically span multiple lines of indented code followed by a newline character.

The table below depicts some common examples of both types of statements:

Simple Statement Compound Statement
assert age > 18 if age > 18: ...
import math while True: ...
return 42 for _ in range(3): ...
pass try: ...
x = 42 + 8 def add(x, y): ...

As you can see, simple statements cause a particular side effect—except for the pass statement, which doesn’t. Compound statements, on the other hand, include traditional control flow constructs like loops, conditionals, and function definitions.

When you look closer at compound statements, you’ll find that they consist of one or more clauses, each containing a header and a suite. Consider the following conditional statement as an example:

Python
if age > 18:
    print("Welcome!")
elif age < 18:
    raise ValueError("You're too young to be here.")
else:
    import sys
    sys.exit(1)

The highlighted lines depict the compound statement’s clause headers, while the remaining lines represent their corresponding suites. For instance, the if clause checks whether the age is greater than eighteen, and conditionally calls the print() function within its suite.

All the clause headers within a compound statement are aligned at the same indentation level. They start with a Python keyword, such as if, elif, or else, and conclude with a colon (:) that marks the start of a code block of the suite. A suite is a collection of statements governed by its respective clause. In this case, the three clauses determine which of the suites to execute based on the age condition.

There’s a special kind of a compound statement known as the statement list, which consists of a sequence of simple statements. Although each of them must be placed on a single line, you can squeeze multiple simple statements into a single line. Do you know how?

How to Put Multiple Statements on a Single Line?

In most cases, it’s preferable to place only one statement or expression per line for the sake of readability. However, if you insist on fitting more than one on the same line, then you can use the semicolon (;) as a statement separator.

A popular use case where this might be helpful involves running a one-liner program in the command line by using the python -c option:

Shell
$ python -c 'import sys; print(sys.version)'
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]

This lets you quickly test ideas in the form of short code snippets. But these days, most terminals let you spread the code across multiple lines without any issues:

Shell
$ python -c '
> import sys
> print(sys.version)
> '
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]

The command that the Python interpreter expects can consist of multiple lines.

Another common use case for the semicolon is to insert a breakpoint into your code. Prior to Python 3.7, which introduced the built-in breakpoint() function, you’d jump into the debugger with the following idiomatic line of code:

Python
import pdb; pdb.set_trace()

When the interpreter hits pdb.set_trace(), it’ll pause execution, and you’ll enter the interactive debugging session using the Python Debugger (pdb).

Additionally, you can try using this trick to intentionally obfuscate or minify your Python code to a degree. However, you won’t be able to evade some syntactical limitations, so you’ll get better results with external tools like Pyarmor.

Before closing this tutorial, you need to answer one final question about expressions and statements in Python. One that’ll give you the foundation for tackling more advanced programming challenges.

Can Statements Have a Dual Nature in Python?

Since all instructions in Python are statements, you can execute an expression that has side effects without considering the computed value. This is often the case when you call a function or method but ignore its return value:

Python
>>> with open("file.txt", mode="w", encoding="utf-8") as file:
...     file.write("Hello, World!")
...
13

In the above example, you call a file object’s .write() method with the string "Hello, World!" as an argument. Although this method returns the number of characters written, which is thirteen, you disregard them by not intercepting the returned value into a variable or processing it further in any way. Notice, however, that the Python REPL automatically displays the result of the last evaluated expression in this situation.

Okay. So you can take advantage of expressions solely for their side effects if they have any. But what about the other way around? Can you evaluate statements? You’re about to find out!

Assignments

Some programming languages blur the lines between statements and expressions. For example, you can evaluate an assignment statement in C or C++:

C echo.c
#include <stdio.h>

int main() {
    int x;
    while (x = fgetc(stdin)) {
        if (x == EOF)
            break;
        putchar(x);
    }
    return 0;
}

This short program echoes whatever the user types on the keyboard. Notice the highlighted line, which gets the next character from the standard input and assigns it to a local variable, x. Simultaneously, this assignment is evaluated as a Boolean expression and used as a continuation condition for the while loop. In other words, as long as the ordinal number of the input character is other than zero, the loop continues.

This has been a notorious source of bugs that has plagued C and C++ programs for ages. Programmers would often mistakenly use the assignment operator (=) in such conditions instead of the intended equality test operator (==) due to their visual resemblance. Although the example above uses this feature intentionally, it was typically the result of human error that could lead to unexpected behavior.

For a long time, core developers shied away from implementing a similar feature in Python because of concerns about this potential confusion. That was the case up until Python 3.8, which introduced the walrus operator (:=) to allow assignment expressions:

Python
MAX_BYTES = 1024

buffer = bytearray()
with open("audio.wav", mode="rb") as file:
    while chunk := file.read(MAX_BYTES):
        buffer.extend(chunk)

In this code snippet, you incrementally read a WAV file in binary chunks until stumbling on the end-of-file control byte, which is indicated by an empty chunk. However, instead of explicitly checking if the chunk returned by .read() is non-empty—or if it evaluates to True—you leverage the assignment expression to execute and evaluate the assignment in one step.

This saves you a few lines of code, which otherwise would’ve looked like this:

Python
MAX_BYTES = 1024

buffer = bytearray()
with open("audio.wav", mode="rb") as file:
    while True:
        chunk = file.read(MAX_BYTES)
        if not chunk:
            break
        buffer.extend(chunk)

You turned a deterministic loop into an infinite one and added three more statements: the assignment statement, conditional statement, and break statement.

Unlike the C example, there’s no way to confuse the assignment expression with its statement counterpart. Python continues to disallow assignment statements in a Boolean context. On such occasions, you must explicitly use the walrus operator, which aligns nicely with one of the aphorisms from the Zen of Python:

Explicit is better than implicit. (Source)

There are other examples of statements that have their expression analogs in Python. Next up, you’ll look into conditional statements and expressions.

Conditionals

Many programming languages provide a ternary conditional operator, which combines three elements: a logical condition, a value if the condition evaluates to True, and an alternative value should the condition evaluate to False.

In the C-family languages, the ternary operator (?:) resembles Elvis Presley’s emoticon with his distinctive hairstyle. While those languages call it the Elvis operator, Python sticks to the more conservative term conditional expression. Here’s what it looks like:

Python
>>> def describe(users):
...     if not users:
...         print("No people")
...     else:
...         print(len(users), "people" if len(users) > 1 else "person")
...

>>> describe([])
No people

>>> describe(["Alice"])
1 person

>>> describe(["Alice", "Bob"])
2 people

At first glance, Python’s conditional expression resembles the standard conditional statement condensed into a single line. It starts with an expression associated with a truthy value, followed by the condition to check, and then the expression corresponding to a falsy value. It allows you to evaluate conditional logic, which would normally require the use of a conditional statement.

Comprehensions

Another example of statements occupying the gray area are all kinds of comprehension expressions, such as the list comprehension or the generator expression, which you can take advantage of to avoid explicit loops:

Python
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(name.title() for name, quantity in fruits if quantity > 0)
['Apple', 'Orange']

Again, the syntax is similar to the for loop and the if statement, but you collapse them into one line and rearrange their individual pieces. This only works well when the condition and the expression to evaluate are reasonably small so that you can fit them on a line or two. Otherwise, you’ll end up with a cluttered piece of code that’s hard to read.

Lastly, Python generators deserve mention here because they use syntax that can be both an expression and a statement at the same time.

Generators

The yield and yield from keywords appear inside generator functions, which allow you to handle large data streams efficiently or define coroutines.

You let Python execute yield as a statement when you want to produce values from a generator function :

Python
>>> import random

>>> def generate_noise(size):
...     for _ in range(size):
...         yield 2 * random.random() - 1
...

>>> list(generate_noise(3))
[0.7580438973021972, 0.5273057193944659, -0.3041263216813208]

This generator is a producer of random values between -1 and 1. Using yield here is somewhat similar to using the return statement. It relays a series of values to the caller as a side effect, but instead of terminating the function completely, the yield statement suspends the function’s execution, allowing it to resume and continue later.

Now, you can optionally evaluate yield as an expression to turn your generator into a prosumer (producer and consumer) with potentially many entry and exit points. Each yield expression can provide and receive values both ways. The yielded values represent the generator’s output, while values sent back into the generator are the input:

Python
>>> def lowpass_filter():
...     a = yield
...     b = yield a
...     while True:
...         a, b = b, (yield (a + b) / 2)
...

The first yield expression implicitly yields nothing (None) but receives a value from the outside world when evaluated as an expression. This value is then assigned to a local variable named a.

The second yield expression yields a, while simultaneously storing another value in b. At this point, the filter becomes saturated and ready to process incoming data. The ability to consume and produce values at each suspension point (yield) makes it possible to push a signal through your filter like an electric current flows through a circuit.

The last yield expression calculates and yields the average of both numbers, overwrites a with b, and then assigns a new value to b.

You can hook up both generators to each other by calling .send() on your low-pass filter, which computes a two-point moving average that smooths out the signal:

Python
>>> lf = lowpass_filter()
>>> lf.send(None)
>>> for value in generate_noise(10):
...     print(f"{value:>5.2f}: {lf.send(value):>5.2f}")
...
 0.66:  0.66
-0.01:  0.32
 0.30:  0.15
-0.10:  0.10
-0.98: -0.54
 0.79: -0.10
 0.31:  0.55
-0.10:  0.11
-0.25: -0.18
 0.57:  0.16

The numbers in the left column come straight from the noise generator and are fed directly to the low-pass filter. The right column contains the output of that filter. Note that you must first prime your prosumer by sending None or calling next() on it so that it advances to the first yield expression.

Conclusion

In this tutorial, you’ve explored the fundamental differences between expressions and statements in Python. You’ve learned that expressions are pieces of syntax that evaluate to a value, while statements are instructions that can cause side effects. At the same time, there’s a gray area between them.

Understanding this distinction is crucial for Python developers, as it affects how you write and structure your code. Now that you have these skills, you can write more precise and expressive Python code, making full use of expressions and statements to create robust and efficient programs.

Take the Quiz: Test your knowledge with our interactive “Expression vs Statement in Python: What's the Difference?” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Expression vs Statement in Python: What's the Difference?

In this quiz, you'll test your understanding of Python expressions vs statements. Knowing the difference between these two is crucial for writing efficient and readable Python code.

🐍 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 Bartosz Zaczyński

Bartosz is a bootcamp instructor, author, and polyglot programmer in love with Python. He helps his students get into software engineering by sharing over a decade of commercial experience in the IT industry.

» More about Bartosz

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

Locked learning resources

Join us and get access to thousands 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

Locked learning resources

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

Level Up Your Python Skills »

What Do You Think?

Rate this article:

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.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!