Python keeps evolving. Let’s look at the most useful features added in recent versions (3.8 through 3.12) that we’ll actually use in day-to-day code.
Walrus Operator := (Python 3.8)
The walrus operator lets us assign and use a value in the same expression. It’s called “walrus” because := looks like a walrus turned sideways.
Without it, we often compute something, check it, then use it — requiring extra lines:
# Before — compute, then check
line = input("Enter: ")
while line != "quit":
print(f"You said: {line}")
line = input("Enter: ")
# With walrus — assign and check in one shot
while (line := input("Enter: ")) != "quit":
print(f"You said: {line}")
It’s especially handy in list comprehensions and conditions:
# Filter and transform in one pass
results = [clean for name in names if (clean := name.strip()) != ""]
# Avoid computing a regex match twice
import re
if (match := re.search(r"\d+", text)):
print(f"Found number: {match.group()}")
The rule is: don’t overuse it. If it makes the line harder to read, stick with two lines.
Positional-Only Parameters / (Python 3.8)
We can now force certain parameters to be positional-only using /:
def greet(name, /, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Manish") # works
greet("Manish", greeting="Hi") # works
greet(name="Manish") # TypeError — name is positional-only
Everything before / must be passed by position. This is useful for library authors who want to keep parameter names as implementation details.
Structural Pattern Matching match/case (Python 3.10)
This is Python’s version of switch/case, but way more powerful. It matches patterns, not just values.
def handle_command(command):
match command.split():
case ["quit"]:
print("Goodbye!")
case ["go", direction]:
print(f"Going {direction}")
case ["pick", "up", item]:
print(f"Picked up {item}")
case _:
print("Unknown command")
handle_command("go north") # Going north
handle_command("pick up sword") # Picked up sword
We can match types, destructure objects, and use guards:
def describe(value):
match value:
case int(n) if n > 0:
print(f"Positive integer: {n}")
case str(s) if len(s) > 5:
print(f"Long string: {s}")
case [first, *rest]:
print(f"List starting with {first}")
case {"name": name, "age": age}:
print(f"{name} is {age}")
case _:
print("Something else")
The _ is the wildcard — it matches anything, like default in other languages.
Union Type X | Y (Python 3.10)
Instead of Union[str, int] from the typing module, we can now use the pipe operator:
# Before (3.5-3.9)
from typing import Union, Optional
def parse(value: Union[str, int]) -> str: ...
def find(id: int) -> Optional[str]: ...
# After (3.10+)
def parse(value: str | int) -> str: ...
def find(id: int) -> str | None: ...
Much cleaner. Works in isinstance() too:
isinstance(42, int | str) # True
Exception Groups and except* (Python 3.11)
When multiple things fail at once (like in asyncio.gather()), we can now handle groups of exceptions:
try:
raise ExceptionGroup("errors", [
ValueError("bad value"),
TypeError("wrong type"),
])
except* ValueError as eg:
print(f"Value errors: {eg.exceptions}")
except* TypeError as eg:
print(f"Type errors: {eg.exceptions}")
The except* syntax matches specific exception types within the group. Multiple except* blocks can each handle different parts of the same group.
F-string Improvements (Python 3.12)
F-strings got more flexible — we can now nest quotes and use expressions that were previously forbidden:
# Python 3.12 — nested quotes and complex expressions
names = ["Alice", "Bob"]
print(f"Users: {", ".join(names)}") # was a SyntaxError before 3.12
# Multiline expressions inside f-strings
print(f"Result: {
sum(x**2 for x in range(10))
}")
type Statement (Python 3.12)
A cleaner way to define type aliases:
# Before
from typing import TypeAlias
UserID: TypeAlias = int
# Python 3.12
type UserID = int
type Matrix = list[list[float]]
type Handler = Callable[[Request], Response]
In simple language, Python’s modern features make our code shorter and more expressive. The walrus operator saves lines, pattern matching replaces clunky if/elif chains, and the pipe syntax makes type hints readable. We don’t need all of them, but knowing they exist helps us write cleaner code.