Python 3.7 Notable Features

Vigneswaran SVigneswaran S
6 min read

Python 3.7 introduced several significant features and improvements that streamlined development and enhanced the language's capabilities.

1. Data Classes (PEP 557)

Data classes are a fantastic addition for creating classes that primarily store data. They reduce boilerplate code by automatically generating methods like __init__, __repr__, __eq__, and others based on type hints.

Before Python 3.7:

Python

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1)
print(p1 == p2)
print(p1 == p3)

With Python 3.7 Data Classes:

Python

from dataclasses import dataclass, field

@dataclass
class Point:
    x: int
    y: int
    # You can add default values
    z: int = 0

@dataclass(order=True) # Enables comparison operators (<, <=, >, >=)
class Person:
    name: str = field(compare=False) # Exclude from comparison
    age: int

    # You can still add custom methods
    def introduce(self):
        return f"Hi, my name is {self.name} and I am {self.age} years old."

p1 = Point(1, 2)
p2 = Point(1, 2, 5) # Specifying z
p3 = Point(3, 4)

print(p1)
print(p2)
print(p1 == p2) # False, because z is different
print(p1 == Point(1,2,0)) # True

person1 = Person("Alice", 30)
person2 = Person("Bob", 25)
person3 = Person("Alice", 35)

print(person1)
print(person1 > person2) # Compares based on age (since name is excluded)
print(person1 == person3) # True, since name is excluded from comparison
print(person1.introduce())

# Field options
@dataclass
class Book:
    title: str
    author: str
    # A list field that should not be part of the initializer
    tags: list[str] = field(default_factory=list, init=False)

book = Book("The Great Novel", "Jane Doe")
print(book)
book.tags.append("fiction")
book.tags.append("adventure")
print(book)

Key benefits of Data Classes:

  • Less boilerplate: No need to write __init__, __repr__, __eq__, etc., manually.

  • Readability: Clearly defines data-holding classes.

  • Type Hinting: Encourages and leverages type hints for better code clarity and static analysis.

  • Customization: The field() function allows for fine-grained control over how fields behave (e.g., whether they are included in repr, eq, hash, or have default factories).

2. Guaranteed Dictionary Order (PEP 509 & PEP 557 for CPython 3.6, guaranteed in 3.7)

In Python 3.6, dictionary insertion order was preserved as an implementation detail in CPython. However, in Python 3.7, this became a guaranteed language feature across all Python implementations. This means you can now rely on dictionaries maintaining the order in which items are inserted.

Before Python 3.7 (relying on OrderedDict for guaranteed order):

Python

from collections import OrderedDict

my_dict = OrderedDict()
my_dict['apple'] = 1
my_dict['banana'] = 2
my_dict['cherry'] = 3

print(my_dict)
for key, value in my_dict.items():
    print(f"{key}: {value}")

With Python 3.7 (standard dict maintains order):

Python

my_dict = {}
my_dict['apple'] = 1
my_dict['banana'] = 2
my_dict['cherry'] = 3
my_dict['date'] = 4

print(my_dict)

# Deleting and re-inserting moves the item to the end
del my_dict['banana']
my_dict['banana'] = 2

print(my_dict)

for key, value in my_dict.items():
    print(f"{key}: {value}")

This change simplifies code that previously relied on collections.OrderedDict for maintaining order, making standard dictionaries more versatile.

3. breakpoint() Built-in Function (PEP 553)

Python 3.7 introduced a new built-in function breakpoint() that provides a standardized way to enter the debugger at a specific point in your code. By default, it will invoke the pdb (Python Debugger) module.

Before Python 3.7 (using pdb.set_trace()):

Python

import pdb

def calculate_sum(a, b):
    result = a + b
    # pdb.set_trace() # Old way to set a breakpoint
    return result

x = 10
y = 20
total = calculate_sum(x, y)
print(total)

With Python 3.7 (breakpoint()):

Python

def calculate_sum(a, b):
    result = a + b
    breakpoint() # New, simpler way to set a breakpoint
    return result

x = 10
y = 20
total = calculate_sum(x, y)
print(total)

How to use it:

When you run the script, execution will pause at breakpoint(). You'll see a (Pdb) prompt in your console, allowing you to interact with the debugger.

Common pdb commands:

  • n (next): Execute the current line and move to the next line in the same function.

  • s (step): Step into a function call.

  • c (continue): Continue execution until the next breakpoint or end of the program.

  • p <variable_name>: Print the value of a variable.

  • l (list): Show the code context around the current line.

  • q (quit): Exit the debugger.

Customizing breakpoint():

You can customize which debugger breakpoint() invokes by setting the PYTHONBREAKPOINT environment variable or by modifying sys.breakpointhook. For example, to use ipdb (an enhanced pdb):

Bash

# In your terminal before running the script
export PYTHONBREAKPOINT=ipdb.set_trace

Or programmatically:

Python

import sys
import ipdb

sys.breakpointhook = ipdb.set_trace

def my_function():
    print("Before breakpoint")
    breakpoint() # This will now call ipdb.set_trace
    print("After breakpoint")

my_function()

4. async and await are reserved keywords (PEP 560)

While async and await were introduced in Python 3.5 for asynchronous programming, they were not reserved keywords until Python 3.7. This meant you could still use them as variable or function names (though it was highly discouraged). In Python 3.7, they are formally reserved, preventing their use as identifiers and ensuring future compatibility.

Before Python 3.7 (possible, but bad practice):

Python

# This would run in Python 3.6 or earlier
async = 10
def await():
    print("Hello from await function")

print(async)
await()

With Python 3.7 (SyntaxError):

Python

# This will raise a SyntaxError in Python 3.7+
# async = 10
# def await():
#     print("Hello from await function")

# print(async)
# await()

This change helps prevent name collisions and makes asynchronous code more robust and consistent.

5. Postponed Evaluation of Type Annotations (PEP 563)

This feature allows type annotations to be evaluated at runtime, rather than compile time. This is particularly useful for circular dependencies in type hints (e.g., class A references class B, and class B references class A) and for forward references (referencing a class that hasn't been defined yet).

To enable this feature, you add from __future__ import annotations at the top of your module.

Example without postponed evaluation (would cause NameError for circular reference):

Python

# This example would fail in Python 3.6 without forward references as strings
# or in Python 3.7 without from __future__ import annotations

# class Node:
#     def __init__(self, value: int, next_node: 'Node' = None): # 'Node' as string works
#         self.value = value
#         self.next_node = next_node

# If you had `from __future__ import annotations`, then 'Node' as a string is not needed.

# N1 = Node(1)
# N2 = Node(2, N1)
# print(N2.next_node.value)

With from __future__ import annotations (Python 3.7+):

Python

from __future__ import annotations # Enable postponed evaluation

class Node:
    def __init__(self, value: int, next_node: Node | None = None):
        self.value = value
        self.next_node = next_node

n1 = Node(1)
n2 = Node(2, n1)
print(n2.next_node.value)

# Example of forward reference:
class MyClass:
    def __init__(self, value: 'AnotherClass'): # 'AnotherClass' is not yet defined
        self.value = value

class AnotherClass:
    def __init__(self, name: str):
        self.name = name

ac = AnotherClass("Test")
mc = MyClass(ac)
print(mc.value.name)

This makes type hinting more flexible and powerful, especially in complex projects with interconnected types.

Conclusion

Python 3.7 brought several quality-of-life improvements and powerful new features. Data classes simplify data model creation, guaranteed dictionary order enhances reliability, breakpoint() provides a convenient debugging entry point, and postponed evaluation of type annotations makes type hinting more robust. Embracing these features can lead to cleaner, more efficient, and more maintainable Python code.

0
Subscribe to my newsletter

Read articles from Vigneswaran S directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vigneswaran S
Vigneswaran S

With profound zeal, I delve into the essence of coding, striving to imbue it with beauty and clarity. Conjuring wonders through code is, to me, a delightful pastime interwoven with an enduring passion.