Python 3.7 Notable Features


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 inrepr
,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.
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.