Syntactic Sugar: Why Python Is Sweet and Pythonic

Syntactic Sugar: Why Python Is Sweet and Pythonic

by Leodanis Pozo Ramos Oct 14, 2024 intermediate best-practices python

Python has several pieces of syntax that are syntactic sugar. This sugar is syntax that isn’t strictly necessary but gives Python some of its flavor as a readable, beginner-friendly, and powerful language. In this tutorial, you’ll explore some of Python’s most used pieces of syntactic sugar.

In practice, you already use most of these pieces of syntax, as they include many well-known Pythonic constructs. As you read on, you’ll see how Python works under the hood and learn how to use the language efficiently and securely.

In this tutorial, you’ll learn:

  • What syntactic sugar is
  • How syntactic sugar applies to operators
  • How assignment expressions are syntactic sugar
  • How for loops and comprehensions are syntactic sugar
  • How other Python constructs are also syntactic sugar

To get the most out of this tutorial, you should be familiar with the basics of Python, including operators, expressions, loops, decorators, classes, context managers, and more.

Take the Quiz: Test your knowledge with our interactive “Syntactic Sugar: Why Python Is Sweet and Pythonic” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Syntactic Sugar: Why Python Is Sweet and Pythonic

You can take this quiz to test your understanding of Python's most common pieces of syntactic sugar and how they make your code more Pythonic and readable.

Syntactic Sugar

In programming, syntactic sugar refers to pieces of syntax that simplify the code and make it more readable or concise. Syntactic sugar lets you express things in a clearer and more readable way.

It makes the language sweeter for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer. (Source)

However, syntactic sugar is something that you may not need in practice because you can get the same result using a different, and often more involved, construct.

Python has many pieces of syntactic sugar that you’ll regularly use in your code. These syntax constructs make Python more readable, quicker to write, and user-friendly. Understanding these syntactic sugar pieces and their significance will help you better understand the inner workings of Python.

In rare situations, you’ll find that desugared versions of a given piece of syntactic sugar can better fulfill your needs. So, knowing about the alternative code to a given sugar can be a good skill to have.

Operators in Python

As with most programming languages, Python makes extensive use of operators. You’ll find several categories of operators, including arithmetic, assignment, augmented assignment, comparison, Boolean, and membership operators. All these operators are part of Python’s syntactic sugar constructs because they let you write expressions in a quick and readable way.

For example, arithmetic operators allow you to create math expressions that are quick to write and read because they look pretty similar to what you learned in math class:

Python
>>> 5 + 7
12

>>> 10 - 4
6

>>> 2 * 4
8

>>> 20 / 2
10

In the first example, you use the plus operator (+) to add two numbers. In the second example, you use the subtraction operator (-) to subtract two numbers. The final two examples perform multiplication and division.

Python supports its arithmetic operators through special methods. Here’s a quick summary:

Operator Operation Method
+ Addition .__add__()
- Subtraction .__sub__()
* Multiplication .__mul__()
/ Division .__truediv__()
// Integer division .__floordiv__()
** Exponentiation .__pow__()

What does it mean to say Python supports its operators through special methods? It means that every time you use an operator, Python calls the corresponding special method under the hood.

To illustrate, here’s how you can express the arithmetic operations you wrote earlier using the appropriate special methods:

Python
>>> 5 + 7
12
>>> (5).__add__(7)
12

>>> 10 - 4
6
>>> (10).__sub__(4)
6

>>> 2 * 4
8
>>> (2).__mul__(4)
8

>>> 20 / 2
10
>>> (20).__truediv__(2)
10

In these examples, you first have the usual way to write an arithmetic expression using the operators, and then you have the equivalent construct using the corresponding special method.

As you can see, using the special method construct makes your code harder to read and understand. So, the operators make your life easier and your code more readable. They’re syntactic sugar.

If you consider augmented assignment operators, then you realize that they’re an even better example of a syntactic sugar construct. Here’s an example of an augmented addition:

Python
>>> count = 0
>>> count += 1
>>> count
1

In this example, the += symbol is the augmented addition operator, which allows you to sum a value on top of an existing variable. This expression is a shortcut for the following expression:

Python
>>> count = 0
>>> count = count + 1
>>> count
1

As you can conclude, both expressions are equivalent. However, the expression using the += operator is quicker to write.

When it comes to comparison operators, you’ll find that you can also replace them with special methods:

Operator Operation Method
< Less than .__lt__()
<= Less than or equal .__le__()
> Greater than .__gt__()
>= Greater than or equal .__ge__()
== Equality .__eq__()
!= Inequality .__ne__()

With these methods, you can create constructs that work like usual comparison expressions. Consider the following examples of expressions and their equivalent method calls:

Python
>>> 2 < 1
False
>>> (2).__lt__(1)
False

>>> 3 <= 7
True
>>> (3).__le__(7)
True

>>> 5 > 3
True
>>> (5).__gt__(3)
True

>>> 3 >= 7
False
>>> (3).__ge__(7)
False

>>> 1 == 1
True
>>> (1).__eq__(1)
True

>>> 1 != 1
False
>>> (1).__ne__(1)
False

In these examples, you confirm that all the comparison operators are syntactic sugar because you can replace them with method calls.

You also have the in and not in operators to run membership tests. A membership test allows you to check whether a value is in a given collection of values:

Python
>>> 5 in [1, 2, 3, 4, 5]
True

>>> 5 not in [1, 2, 3, 4, 5]
False

>>> 100 in [1, 2, 3, 4, 5]
False

>>> 100 not in [1, 2, 3, 4, 5]
True

Because 5 is in the list of values, in returns True and not in returns False. Similarly, because 100 isn’t in the list, in returns False and not in returns True.

In practice, you can replace these operators with calls to the .__contains__() method:

Python
>>> [1, 2, 3, 4, 5].__contains__(5)
True

>>> not [1, 2, 3, 4, 5].__contains__(5)
False

>>> [1, 2, 3, 4, 5].__contains__(100)
False

>>> not [1, 2, 3, 4, 5].__contains__(100)
True

Alternatively, you can implement the algorithm in a function that iterates over the values in the target iterable:

Python
>>> def is_member(value, iterable):
...     for current_value in iterable:
...         if current_value == value:
...             return True
...     return False
...

>>> is_member(5, [1, 2, 3, 4, 5])
True

>>> not is_member(5, [1, 2, 3, 4, 5])
False

>>> is_member(100, [1, 2, 3, 4, 5])
False

>>> not is_member(100, [1, 2, 3, 4, 5])
True

In this example, the is_member() function takes a target value and an iterable as arguments. Then, it checks whether the value is in the iterable using a loop. The result of calling the function is equivalent to using the membership operators.

Chained Conditions

Sometimes, you have two comparisons joined with an and operator. For example, say that you want to know whether a number is in a given interval. In this situation, you can do something like the following:

Python
>>> number = 5

>>> if number >= 1 and number <= 10:
...     print("Inside interval")
... else:
...     print("Outside interval")
...
Inside interval

With the and expression shown in this example, you can check if a given number is in the interval from 1 to 10, both included.

Python has a shortcut to express the same condition. You can use chained operators as shown below:

Python
>>> if 1 <= number <= 10:
...     print("Inside interval")
... else:
...     print("Outside interval")
...

Now, your condition doesn’t explicitly include the and operator. However, it produces the same result. So, both conditions are equivalent.

Chaining operators, as you did in this example, is another syntactic sugar piece in Python.

Ternary Operator

Python has a syntax construct known as the ternary operator or conditional expressions. These expressions are inspired by the ternary operator that looks like a ? b : c and is used in other programming languages, such as C.

This construct evaluates to b if the value of a is true, and otherwise evaluates to c. Because of this, sometimes the equivalent Python syntax is also known as the ternary operator, and it looks as shown below:

Python Syntax
variable = expression_1 if condition else expression_2

This expression returns expression_1 if the condition is true and expression_2 otherwise. In practice, this syntax is equivalent to a conditional like the following:

Python Syntax
if condition:
    variable = expression_1
else:
    variable = expression_2

Because you can replace the ternary operator with an equivalent ifelse statement, you can say that this operator is another piece of syntactic sugar in Python.

Assignment Expressions

Traditional assignments built with the = operator don’t return a value, so they’re statements but not expressions. In contrast, assignment expressions are assignments that return a value. You can build them with the walrus operator (:=).

Assignment expressions allow you to assign the result of an expression used, say, in a conditional or a while loop to a name in one step. For example, consider the following loop that takes input from the keyboard until you type the word "stop":

Python
>>> while (line := input("Type some text: ")) != "stop":
...     print(line)
...
Type some text: Python
Python
Type some text: Walrus
Walrus
Type some text: stop

In this example, you get the user’s input in the line variable using an assignment expression. At the same time, the expression returns the user’s input so that it can be compared to the sentinel value, "stop".

In practice, you rewrite this loop without using the walrus operator by lifting the variable assignment:

Python user_input.py
line = input("Type some text: ")

while line != "stop":
    print(line)
    line = input("Type some text: ")

This code snippet works the same as the code you wrote above using the walrus operator. However, the operator isn’t in the equation anymore. So, you can conclude that this operator is another piece of syntactic sugar in Python.

This example has an additional drawback: It unnecessarily repeats the call to input(), which doesn’t happen with the syntactic-sugared version of the code. You can skip the repetition using a while loop like the following:

Python user_input.py
while True:
    line = input("Type some text: ")
    if line == "stop":
        break
    print(line)

This loop avoids the repetition. However, the entire code is now a bit harder to understand because the condition is buried within the loop.

Unpacking in Python

Iterable unpacking is one of those lovely features of Python. Unpacking an iterable means assigning its values to a series of variables one by one. In the following sections, you’ll learn how iterable unpacking is another piece of syntactic sugar.

Iterable Unpacking

Iterable unpacking is a powerful feature that can be used in various situations. It can help you write more readable and concise code.

You can use unpacking to distribute the values in an iterable into a series of variables:

Python
>>> one, two, three, four = [1, 2, 3, 4]

>>> one
1
>>> two
2
>>> three
3
>>> four
4

In this example, you have a tuple of variables on the left and a list of values on the right. Once Python runs this assignment, the values are unpacked into the corresponding variable by position.

You can replace this code construct with the following series of assignments:

Python
>>> numbers = [1, 2, 3, 4]

>>> one = numbers[0]
>>> one
1
>>> two = numbers[1]
>>> two
2
>>> three = numbers[2]
>>> three
3
>>> four = numbers[3]
>>> four
4

In this case, you manually assign the values to each variable using the corresponding indexing operation. This code is less readable than the previous version but produces the same result.

An excellent use case for unpacking is when you need to swap values between variables. In languages that don’t have an unpacking feature, you’ll have to use a temporary variable:

Python
>>> a = 200
>>> b = 400

>>> temp = a
>>> a = b
>>> b = temp

>>> a
400
>>> b
200

In this example, you use the temp variable to hold the value of a so that you can swap the values between a and b. Using the unpacking syntactic sugar, you can do something like the following:

Python
>>> a = 200
>>> b = 400

>>> a, b = b, a

>>> a
400
>>> b
200

In this example, the highlighted line does the magic, allowing you to swap values in a clean and readable way.

*args and **kwargs

In Python, you can define functions that take an undefined number of positional or keyword arguments using the *args and **kwargs syntax in the function’s definition. The *args argument packs a series of positional arguments into a tuple.

Consider the following toy example:

Python
>>> def show_args(*args):
...     print(args)
...

>>> show_args(1, 2, 3, 4)
(1, 2, 3, 4)

This function uses the *args syntax to tell Python that it can take an undetermined number of positional arguments. When you call the function with positional arguments, they’re packed into a tuple.

On the other hand, the **kwargs argument packs keyword arguments into a dictionary:

Python
>>> def show_kwargs(**kwargs):
...     print(kwargs)
...

>>> show_kwargs(one=1, two=2, three=3, four=4)
{'one': 1, 'two': 2, 'three': 3, 'four': 4}

In this example, you use the **kwargs syntax to tell Python that this function will take an undetermined number of keyword arguments.

You can replace *args with a list or tuple and **kwargs with a dictionary:

Python
>>> def show_values(values):
...     print(values)
...

>>> show_values([1, 2, 3, 4])
[1, 2, 3, 4]

In this example, instead of using *args, you use a positional argument and pass a list of values to the function call.

You can do a similar thing with **kwargs and pass a dictionary to the function call:

Python
>>> def show_items(kwvalues):
...     print(kwvalues)
...

>>> show_items({"one": 1, "two": 2, "three": 3, "four": 4})
{'one': 1, 'two': 2, 'three': 3, 'four': 4}

In practice, both *args and **kwargs are syntactic sugar pieces that Python includes to make your life more pleasant and your code more explicit.

Loops and Comprehensions

Loops are a fundamental component of most programming languages. With a loop, you can run repetitive tasks, process data streams, and more. In Python, you have while and for loops. Python also has comprehensions, which are like a compact for loop.

In the following sections, you’ll learn how for loops and comprehensions are also syntactic sugar constructs in Python, as they can be rewritten as while loops.

Exploring for Loops

A for loop lets you iterate over the items of a given data stream that you typically call an iterable. Lists, tuples, sets, and dictionaries are good examples of iterables in Python. All of them support iteration, meaning you can use a for loop to traverse them.

Here’s a toy example of a for loop:

Python
>>> numbers = ["one", "two", "three", "four"]

>>> for number in numbers:
...     print(number)
...
one
two
three
four

This loop iterates over a list of strings and prints one string at a time. You can write a while loop to replace the above loop:

Python
>>> numbers = ["one", "two", "three", "four"]
>>> index = 0

>>> while index < len(numbers):
...     print(numbers[index])
...     index += 1
...
one
two
three
four

In this example, you did the same iteration and produced the same result with a while loop. The code looks less clean and readable, but it works the same. So, you can conclude that for loops are also syntactic sugar in Python.

The while loop in the above example works as a replacement for a for loop as long as you can call len() on the target iterable. In practice, something like the following is closer to how a for loop would work. Again, this example doesn’t consider the else clause of the loop:

Python
>>> it = iter(numbers)
>>> while True:
...     try:
...         number = next(it)
...     except StopIteration:
...         break
...     print(number)
...
one
two
three
four

To implement this loop, you use the built-in iter() function that creates an iterator from the input data stream. Inside the loop, you use the built-in next() function to get the next item from the iterator in every cycle of the loop. Then, you use the StopIteration exception to terminate the loop with a break statement.

Using Comprehensions

Comprehensions are a distinctive feature of Python. You can use comprehensions to create new lists, sets, and dictionaries out of a stream of data. To illustrate, say that you have a list of numbers as strings, and you want to convert them into numeric values and build a list of squared values. To do this, you can use the following comprehension:

Python
>>> numbers = ["2", "9", "5", "1", "6"]

>>> [int(number)**2 for number in numbers]
[4, 81, 25, 1, 36]

In this example, you use a list comprehension to iterate over your list of numbers. The comprehension’s expression converts the string values into integer values and computes their squares. As a result, you get a list of square numbers.

Even though comprehensions are popular and versatile tools, you can replace them with an equivalent for loop or even a while loop. Consider the for loop approach only:

Python
>>> numbers = ["2", "9", "5", "1", "6"]

>>> squares = []
>>> for number in numbers:
...    squares.append(int(number)**2)
...

>>> squares
[4, 81, 25, 1, 36]

This code is more verbose and requires an extra variable to store the list of squares. However, it produces the same result as the equivalent comprehension. Again, comprehensions are syntactic sugar in Python.

As an exercise, you can use what you learned in the previous section to transform the comprehension into a while loop.

Decorators

Decorators are functions that take another function as an argument and extend their behavior dynamically without explicitly modifying it. In Python, you have a dedicated syntax that allows you to apply a decorator to a given function:

Python Syntax
@decorator
def func():
    <body>

In this piece of syntax, the @decorator part tells Python to call decorator() with the func object as an argument. This operation lets you modify the original behavior of func() and assign the function object back to the same name, func.

To illustrate the basics of using decorators, say that you need to measure the execution time of a given function. A handy way to do this is to use a decorator that looks something like the following:

Python
>>> import functools
>>> import time

>>> def timer(func):
...     @functools.wraps(func)
...     def _timer(*args, **kwargs):
...         start = time.perf_counter()
...         result = func(*args, **kwargs)
...         end = time.perf_counter()
...         print(f"Execution time: {end - start:.4f} seconds")
...         return result
...     return _timer

This timer() function is built to be used as a decorator. It takes a function object as an argument and returns an extended function object. In the ._timer() inner function, you use the time module to measure the execution time of the input function.

Here’s how you can use this decorator in your code:

Python
>>> @timer
... def delayed_mean(sample):
...     time.sleep(1)
...     return sum(sample) / len(sample)
...

>>> delayed_mean([10, 2, 4, 7, 9, 3, 9, 8, 6, 7])
Execution time: 1.0051 seconds
6.5

In this example, you use the @decorator syntax to decorate the delayed_mean() function and modify its behavior. Now when you call delayed_mean(), you get a time report and the expected result.

The fact is that you can get the same result without using the @decorator syntax. Here’s how:

Python
>>> def delayed_mean(sample):
...     time.sleep(1)
...     return sum(sample) / len(sample)
...

>>> delayed_mean = timer(delayed_mean)

>>> delayed_mean([10, 2, 4, 7, 9, 3, 9, 8, 6, 7])
Execution time: 1.0051 seconds
6.5

The highlighted line causes the same effect as decorating the function with the @decorator syntax. After executing this assignment, you can use delayed_mean() as usual. You’ll get the time report and the computed result.

Attribute Access

In Python, when you’re working with classes and objects, you’ll often use the dot notation to access the attributes of a class or object. In other words, to access class members, you can use the following syntax:

Python Syntax
obj.attribute

This syntax uses a dot to express that you need to access attribute on obj. This clean and intuitive syntax makes your code look readable and clear. However, it’s also syntactic sugar. You can replace this syntax with an alternative construct based on the built-in getattr() function.

To illustrate, consider the following Circle class:

Python circle.py
from math import pi

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return pi * self.radius**2

    def circumference(self):
        return 2 * pi * self.radius

In this code, you define the Circle class with an instance attribute and two methods. To access the attribute and the methods, you can use the dot notation:

Python
>>> from circle import Circle

>>> circle = Circle(10)

>>> circle.radius
10
>>> circle.area()
314.1592653589793
>>> circle.circumference()
62.83185307179586

In this example, you see that the dot notation gives you access to the attributes and methods of a given instance of Circle. You can also access those members using the following constructs:

Python
>>> getattr(circle, "radius")
10

>>> # Method object
>>> getattr(circle, "area")
<bound method Circle.area of <__main__.Circle object at 0x1106d24d0>>

>>> # Method calls
>>> getattr(circle, "area")()
314.1592653589793
>>> getattr(circle, "circumference")()
62.83185307179586

The built-in getattr() function lets you access attributes and methods on a given object. Note that when you use this function to access methods, you get the method object. If you want to call the method, then you need to append a pair of parentheses with the required arguments.

Method Calls

There’s another piece of syntactic sugar associated with how you normally call methods on Python objects. To continue with the Circle class from the previous section, the usual way to call its instance methods is by using the dot notation on an instance:

Python
>>> from circle import Circle

>>> circle = Circle(10)

>>> circle.area()
314.1592653589793
>>> circle.circumference()
62.83185307179586

This way of calling the methods is also syntactic sugar. Internally, doing something like circle.area() automatically translates to something like the following:

Python
>>> Circle.area(circle)
314.1592653589793

In this example, you explicitly pass the instance to the self argument on .area(). However, in something like circle.area(), Python takes care of setting self to the provided instance for you. This behavior makes your life easier and your code cleaner.

F-String Literals

Formatted string literals, or f-strings for short, are another piece of syntactic sugar in Python. F-strings are popular in modern Python code. They allow you to interpolate and format strings with a quick and clean syntax.

Here’s a quick example of how to use f-strings in your code:

Python
>>> debit = 300
>>> credit = 450

>>> f"Debit: ${debit:.2f}, Credit: ${credit:.2f}, Balance: ${credit - debit:.2f}"
'Debit: $300.00, Credit: $450.00, Balance: $150.00'

In this example, you use an f-string to create a quick report of a bank account. Note how you use replacement fields to interpolate the debit and credit variables and format specifiers to format the output.

Instead of using an f-string, you can use the string .format() method:

Python
>>> "Debit: ${0:.2f}, Credit: ${1:.2f}, Balance: ${2:.2f}".format(
...     debit, credit, credit - debit
... )
'Debit: $300.00, Credit: $450.00, Balance: $150.00'

The .format() method produces the same result as the equivalent f-string. However, its syntax may be less readable at times. In practice, you can replace any f-string instance with a call to .format(). So, f-strings are also syntactic sugar in Python.

Alternatively, you can use string concatenation with + and calls to the built-in format() function:

Python
>>> (
...     "Debit: $" + format(debit, ".2f")
...     + ", Credit: $" + format(credit, ".2f")
...     + ", Balance: $" + format(credit - debit, ".2f")
... )
'Debit: $300.00, Credit: $450.00, Balance: $150.00'

In this example, you concatenate the different components of your string using the concatenation operator. To format the input values, you use the format() function. This syntax is quite involved but it works the same as the initial f-string.

Assertions

Python allows you to write sanity checks known as assertions. To write these checks, you’ll use the assert statement. With assertions, you can test if certain assumptions remain true while developing your code. If any of your assertions are false, then you probably have a bug in your code.

Assertions are mainly used for debugging purposes. They help ensure that you don’t introduce new bugs while adding features and fixing other bugs in your code.

The assert statement is a simple statement with the following syntax:

Python Syntax
assert expression[, assertion_message]

Here, expression can be any valid Python expression or object, which is then tested for truthiness. If expression is false, then the statement throws an AssertionError. The assertion_message parameter is optional but encouraged. It can hold a string describing the issue the statement should catch.

Here’s how this statement works in practice:

Python
>>> number = 42
>>> assert number > 0, "number must be positive"

>>> number = -42
>>> assert number > 0, "number must be positive"
Traceback (most recent call last):
    ...
AssertionError: number must be positive

With a truthy expression, the first assertion succeeds, and nothing happens. In that case, your program continues its normal execution. In contrast, a falsy expression makes the assertion fail, raising an AssertionError and breaking the program’s execution.

The assert statement is also syntactic sugar. You can replace the above assertions with the following code:

Python
>>> number = 42
>>> if __debug__:
...     if not number > 0:
...         raise AssertionError("number must be positive")
...

>>> number = -42
>>> if __debug__:
...     if not number > 0:
...         raise AssertionError("number must be positive")
...
Traceback (most recent call last):
    ...
AssertionError: number must be positive

Python’s built-in constant, __debug__, is closely related to the assert statement. It’s a Boolean constant that defaults to True, which means that the assertions are enabled, and you’re running Python in normal or debug mode.

If you run Python in optimized mode, then the __debug__ constant is False and the assertions are disabled.

The yield from Construct

The yield from construct is another syntactic sugar piece in Python. You can use this construct to yield items from an iterable.

To illustrate how you can use this construct, consider the following Stack class:

Python stack.py
class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __iter__(self):
        yield from self.items

This Stack class has two basic stack operations: push and pop. To store the data, you use a list object called .items. The .__iter__() special method enables your class to support iteration. In this example, you use the yield from construct to yield values from the .items list. The syntax is highly readable and clear.

You can replace the yield from construct with at least two different tools. One of these is the built-in iter() function:

Python stack.py
class Stack:
    # ...

    def __iter__(self):
        return iter(self.items)

The highlighted line works the same as the yield from construct in the previous version of .__iter__(). It returns an iterator that yields items on demand.

Alternatively, you can use a for loop:

Python stack.py
class Stack:
    # ...

    def __iter__(self):
        for item in self.items:
            yield item

In this other implementation of .__iter__(), you use an explicit for loop with a plain yield statement that yields items on demand.

The with Statement

Python’s with statement is a handy tool that allows you to manipulate context manager objects. These objects automatically handle the setup and teardown phases whenever you’re dealing with external resources such as files.

For example, to write some text to a file, you’ll typically use the following syntax:

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

The built-in open() function returns a file object that supports the context manager protocol. Therefore, you can use this object in a with statement as shown above.

In the statement’s code block, you can manipulate your file as needed. When you finish the file, the with statement finishes, closing the file and releasing the associated resources automatically.

The with statement is another piece of syntactic sugar that makes your life easier when handling setup and teardown logic. You can replace this statement using a tryfinally statement like the following:

Python
file = open("hello.txt", mode="w", encoding="utf-8")

try:
    file.write("Hello, World!")
finally:
    file.close()

Here, you first get the file object by calling open(). Then, you define a try block to manipulate the file as needed. In the finally clause, you manually close the file to release the associated resource. This clause always runs, so you can rest assured that the file will be properly closed.

In this example, the only action you do in the finally clause is close the file. However, if you ever need to use this syntax to replace a with statement, then you must use the finally clause to do whatever action the target context manager does in its teardown phase, which is carried out by its .__exit__() method.

Conclusion

Now you know what syntactic sugar is and how Python uses it. You also know more about the most commonly used pieces of syntactic sugar and how they can help you to write more readable, clean, concise, and secure code.

In this tutorial, you’ve learned:

  • What syntactic sugar is
  • How syntactic sugar applies to operators
  • How assignment expressions are syntactic sugar
  • How for loops and comprehensions are syntactic sugar
  • How other Python constructs are syntactic sugar

While using syntactic sugar isn’t something you have to do, incorporating it into your workflow can be a sweet way to make your code more Pythonic.

Take the Quiz: Test your knowledge with our interactive “Syntactic Sugar: Why Python Is Sweet and Pythonic” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Syntactic Sugar: Why Python Is Sweet and Pythonic

You can take this quiz to test your understanding of Python's most common pieces of syntactic sugar and how they make your code more Pythonic and readable.

🐍 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 Leodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

» More about Leodanis

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!