What Is New in Python 3.5
Python 3.5 was released on September 12, 2015. The defining change is the introduction of native async/await syntax, which made asynchronous programming a first-class language feature rather than a generator-based workaround. Additional highlights include the matrix multiplication operator @, extended unpacking generalizations, and the typing module for optional type hints.
| Category | Change | PEP / Reference |
|---|---|---|
| New Syntax | Native coroutines with async def and await |
PEP 492 |
| New Syntax | Matrix multiplication operator @ and @= |
PEP 465 |
| New Syntax | Unpacking generalizations -- *iterable in more contexts |
PEP 448 |
| New Modules | typing -- optional type hints and generic types |
PEP 484 |
| New Modules | zipapp -- create self-contained Python application archives |
PEP 441 |
| Standard Library | os.scandir() -- faster directory traversal replacing os.listdir() |
PEP 471 |
| Standard Library | math.isclose() for float proximity comparison |
PEP 485 |
| Standard Library | subprocess.run() -- unified subprocess execution API |
-- |
| Performance | memoryview improvements; faster io.BytesIO |
-- |
| asyncio | New transport/protocol APIs; coroutine-based SSL; async with, async for |
PEP 492 |
What Changed About Async Programming in Python 3.5?
Native Coroutines with async def / await (PEP 492)
Before 3.5, asyncio coroutines were written using @asyncio.coroutine with yield from -- a generator-based approach that was functional but visually ambiguous. Python 3.5 introduced dedicated async def and await keywords that cannot be used outside async contexts, making coroutine intent explicit.
# Python 3.4 style (generator-based)
@asyncio.coroutine
def fetch(url):
response = yield from aiohttp.get(url)
return response
# Python 3.5+ style
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
PEP 492 also introduced async with (for async context managers) and async for (for async iterators), rounding out the async syntax.
Matrix Multiplication Operator @ (PEP 465)
The @ operator is reserved for matrix multiplication. NumPy and similar libraries implement __matmul__ to support it. This brings mathematical notation much closer to Python code for linear algebra work.
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
C = A @ B # Matrix multiplication
# [[19, 22], [43, 50]]
# Previously required:
C = np.dot(A, B)
Extended Unpacking Generalizations (PEP 448)
The * and ** unpacking operators can now appear in more contexts -- multiple times in a single expression, inside lists, dicts, tuples, and function calls.
# Multiple * in one expression
merged = [*list1, *list2, *list3]
# Multiple ** in dict literals
config = {**defaults, **user_config, "timestamp": now}
# Function calls
print(*[1, 2], *[3, 4]) # 1 2 3 4
The typing Module and Optional Type Hints (PEP 484)
Python 3.5 introduced the typing module as a standard home for type hint constructs. Annotations existed before, but there was no standard way to express generic types like "a list of strings." The typing module provides: List, Dict, Optional, Union, Callable, Tuple, Any, and more.
from typing import List, Optional, Dict
def process(items: List[str], limit: Optional[int] = None) -> Dict[str, int]:
result: Dict[str, int] = {}
for i, item in enumerate(items[:limit]):
result[item] = i
return result
Type hints are still optional and not enforced at runtime -- they exist for static analysis tools like mypy. The typing module in 3.5 was the beginning of Python's gradual typing story.
Standard Library Additions in Python 3.5
os.scandir() -- Faster Directory Listing (PEP 471)
os.scandir() returns an iterator of DirEntry objects that include file attributes alongside names. This avoids a second os.stat() call per file, making it 2-20x faster than os.listdir() followed by os.stat() -- especially on Windows where stat data is embedded in the directory listing.
subprocess.run() -- Unified API
The subprocess.run() function consolidates the functionality of subprocess.call(), subprocess.check_call(), and subprocess.check_output() into a single call with a CompletedProcess result object.
import subprocess
result = subprocess.run(
["git", "status"],
capture_output=True, text=True, check=True
)
print(result.stdout)
math.isclose() (PEP 485)
Comparing floats with == is unreliable due to floating-point representation. math.isclose(a, b) checks that abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol), covering both relative and absolute tolerance in one call.
FAQ
Can I use await outside of async def in Python 3.5?
No. await is a syntax error outside of a coroutine function defined with async def. This is a hard constraint -- it prevents accidental use in regular functions where the value would not be awaited.
Does the @ matrix operator work with plain Python lists?
Not out of the box. Lists do not implement __matmul__. The @ operator calls __matmul__ on the left operand (or __rmatmul__ on the right). NumPy arrays support it; standard Python lists do not.
Is the typing module in 3.5 the same as in 3.11+?
No. The module has grown substantially. 3.5 shipped a fairly bare set: Optional, Union, List, Dict, Tuple, Callable, Any, TypeVar, Generic. Many features like Literal, Final, TypedDict, Protocol, ParamSpec, and TypeAlias came in later minor versions. Always check the version-added notes in the docs.
Is os.scandir() safe to use in the same code as os.listdir()?
Yes. They are independent functions. os.scandir() is a drop-in improvement for directory traversal -- prefer it whenever you need type or size information alongside filenames. If you only need names, os.listdir() is fine and slightly simpler.
What is the practical difference between subprocess.run() and subprocess.Popen()?
subprocess.run() blocks until the process completes and returns a CompletedProcess. subprocess.Popen() is lower-level, gives you a handle to the running process, and lets you communicate with it while it runs. Use run() for fire-and-forget commands; use Popen() when you need streaming output, parallel execution, or fine-grained process control.