What Is New in Python 3.8
Python 3.8 was released on October 14, 2019. The most visible addition is the walrus operator := (assignment expressions). Beyond that, positional-only parameters, f-string debugging with =, shared-memory multiprocessing, and a fast vectorcall protocol for CPython all landed in this release.
| Category | Change | PEP / Reference |
|---|---|---|
| New Syntax | Assignment expressions -- the walrus operator := |
PEP 572 |
| New Syntax | Positional-only parameters via / in function signatures |
PEP 570 |
| New Syntax | f-string = specifier for self-documenting expressions |
-- |
| Performance | Vectorcall -- fast call protocol for CPython extension types | PEP 590 |
| Standard Library | multiprocessing.shared_memory -- shared memory between processes |
PEP 567 |
| Standard Library | Pickle Protocol 5 with out-of-band data buffers | PEP 574 |
| Security | Runtime audit hooks for monitoring sensitive operations | PEP 578 |
| Typing | Final, Literal, TypedDict added to typing |
PEP 591, 586, 589 |
| Interpreter | Richer Python initialization configuration C API | PEP 587 |
| Performance | LOAD_GLOBAL opcode significantly faster |
-- |
| Build | Debug builds share ABI with release builds -- no more separate debug extensions | -- |
| Performance | Parallel filesystem cache for __pycache__ via PYTHONPYCACHEPREFIX |
-- |
What Are the Key Language Changes in Python 3.8?
Assignment Expressions -- the Walrus Operator (PEP 572)
The := operator assigns a value to a variable inside an expression. This cuts duplicated evaluation in loops, comprehensions, and conditionals.
# Avoid calling len() twice
if (n := len(data)) > 10:
print(f"Too long: {n} items")
# Read until empty block
while chunk := f.read(8192):
process(chunk)
# Filter and compute in one pass
results = [y for x in data if (y := transform(x)) is not None]
In practice, the walrus operator is most useful when a computed value is needed both in the condition and the body. Keep usage deliberate -- overuse hurts readability more than it helps.
Positional-Only Parameters (PEP 570)
The / separator in a function signature marks all preceding parameters as positional-only -- callers cannot pass them by keyword name. This mirrors how many built-in functions already behave.
def hypot(x, y, /, *, precision=10):
# x and y must be positional; precision must be keyword
...
hypot(3, 4) # OK
hypot(x=3, y=4) # TypeError
hypot(3, 4, precision=5) # OK
This matters for API design: renaming x or y becomes a non-breaking change, and it prevents fragile code that passes positional args by name.
f-string = Specifier for Debugging
Adding = to an f-string expression prints both the expression text and its value. This replaces the common pattern of writing print(f"x = {x}") manually.
x = 42
theta = 2.5
print(f"{x=}") # x=42
print(f"{theta=:.2f}") # theta=2.50
print(f"{x + 1 = }") # x + 1 = 43
Standard Library Additions in Python 3.8
Shared Memory for Multiprocessing
The new multiprocessing.shared_memory.SharedMemory class lets multiple Python processes share a block of memory directly, bypassing serialization entirely. This is a significant throughput win for high-volume inter-process data exchange like image buffers or large arrays.
from multiprocessing import shared_memory
import numpy as np
shm = shared_memory.SharedMemory(create=True, size=1024)
arr = np.ndarray((128,), dtype=np.float64, buffer=shm.buf)
arr[:] = 0.0 # write directly into shared memory
Pickle Protocol 5 (PEP 574)
Protocol 5 adds out-of-band buffer support, allowing the data payload to be transferred separately from the pickle stream. This is particularly relevant for multiprocessing and distributed frameworks that want to avoid copying large buffers like NumPy arrays into the pickle bytestream.
typing Additions: Final, Literal, TypedDict
Final-- marks a variable or attribute as non-reassignable. Type checkers enforce this at analysis time.Literal-- restricts a parameter to specific constant values:def set_level(level: Literal["debug", "info", "error"]) -> NoneTypedDict-- defines dicts with specific key names and types, giving type checkers something to check against in JSON-heavy code.
statistics Module Updates
New functions: statistics.mean() already existed, but 3.8 adds statistics.NormalDist, statistics.fmean() (float mean, faster), statistics.geometric_mean(), and statistics.multimode(). These land in the stdlib without needing scipy for basic distribution work.
Security and Audit Hooks (PEP 578)
Python 3.8 introduces runtime audit hooks -- a mechanism to observe or restrict sensitive operations like file opens, network connections, and subprocess launches. You register a hook via sys.addaudithook() and it receives events with associated arguments.
import sys
def my_audit(event, args):
if event == "open":
print(f"Opening file: {args[0]}")
sys.addaudithook(my_audit)
This is aimed at embedding scenarios and application-level monitoring, not kernel-level sandboxing. Hooks cannot be removed once added within a process.
Performance and CPython Internals
LOAD_GLOBALis faster due to an optimized inline cache -- a measurable speedup in attribute-heavy code.- The vectorcall protocol (PEP 590) speeds up calls to C extension functions by reducing temporary object allocations. Functions like
len(),isinstance(), and many others benefit. - Debug builds now share the ABI with release builds -- you no longer need separate debug-mode extension packages.
pickledefaults to Protocol 4 (was Protocol 3), improving performance and reducing output size for complex objects.- The
PYTHONPYCACHEPREFIXenv var redirects__pycache__to a single location, useful for immutable source trees and containers.
FAQ
Is the walrus operator := the same as = in behavior?
No. The walrus operator returns the assigned value as an expression, so it can appear inside conditions, comprehensions, and function call arguments. Regular = is a statement and cannot appear inside expressions. The scoping rules also differ slightly -- walrus in a comprehension leaks to the enclosing scope, unlike loop variables in list comprehensions.
When does positional-only syntax actually matter for library authors?
When a parameter name is an implementation detail and you want freedom to rename it without breaking callers, or when you specifically want to prevent callers from using keyword syntax. The built-in pow(x, y, mod=None) is a good reference -- x and y are positional-only.
What does the TypedDict syntax look like in practice?
You can define it as a class or via the functional form. Class form is cleaner: class Movie(TypedDict): name: str; year: int. Type checkers then flag any dict literal that's missing required keys or has wrong types when assigned to a Movie annotation.
Do audit hooks from PEP 578 slow down normal code?
Only when events are actually triggered. There is a small overhead on operations that emit audit events (file opens, imports, etc.), but non-audited code paths are unaffected. The overhead is negligible for most applications.
Can I use shared_memory across machines, not just processes on the same host?
No. multiprocessing.shared_memory is strictly for processes on the same machine sharing physical memory. For cross-machine data sharing, you still need a network protocol or something like Redis or Apache Arrow Flight.