What Is New in Python 2.4
Python 2.4 was released on November 30, 2004. The standout additions are function and class decorators (@decorator syntax), built-in set and frozenset types, generator expressions, subprocess module as a unified process management API, and the Decimal type for exact decimal arithmetic. String formatting gained "{0}".format()-style syntax was not yet here, but the % operator gained new features.
| Category | Change | PEP / Reference |
|---|---|---|
| New Syntax | Function (and class) decorators: @decorator |
PEP 318 |
| New Syntax | Generator expressions: (x**2 for x in range(10)) |
PEP 289 |
| Builtins | set() and frozenset() become built-in types |
PEP 218 |
| Builtins | sorted() built-in function returning a new sorted list |
-- |
| Builtins | reversed() built-in for reverse iteration |
PEP 322 |
| New Modules | subprocess -- unified process management |
PEP 324 |
| New Modules | decimal -- exact decimal arithmetic |
PEP 327 |
| Standard Library | collections.deque -- O(1) append and popleft |
-- |
| Standard Library | cookielib, improvements to urllib2, better os.walk() |
-- |
Key Features in Python 2.4
Function Decorators (PEP 318)
The @decorator syntax arrived in 2.4 for functions. A decorator is syntactic sugar for func = decorator(func). This unlocked clean implementations of memoization, authentication, logging, and function transformation.
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time() - start:.3f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.5)
Generator Expressions (PEP 289)
Generator expressions are like list comprehensions but lazy -- they produce values on demand without building the full list in memory. They use parentheses instead of square brackets.
# List comprehension -- builds entire list in memory
squares_list = [x**2 for x in range(10**6)]
# Generator expression -- one value at a time
squares_gen = (x**2 for x in range(10**6))
total = sum(squares_gen) # no intermediate list created
Built-in set and frozenset
Sets became first-class built-ins in 2.4 (they were in a sets module before). set is mutable; frozenset is immutable and hashable (usable as a dict key or in another set).
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # union: {1, 2, 3, 4, 5, 6}
print(a & b) # intersection: {3, 4}
print(a - b) # difference: {1, 2}
print(a ^ b) # symmetric difference: {1, 2, 5, 6}
Decimal -- Exact Decimal Arithmetic (PEP 327)
Binary floating-point arithmetic cannot exactly represent many decimal fractions. The decimal module provides arbitrary-precision decimal arithmetic, essential for financial calculations.
from decimal import Decimal, getcontext
getcontext().prec = 28
print(0.1 + 0.2) # 0.30000000000000004 (float)
print(Decimal("0.1") + Decimal("0.2")) # 0.3 (exact)
FAQ
What is the difference between sorted() and list.sort()?
sorted() returns a new sorted list and accepts any iterable. list.sort() sorts in place and returns None. Both accept key and reverse arguments. Use sorted() when you want the original unmodified, or when the input is not a list.
Can I use a generator expression directly as a function argument?
Yes, if it is the only argument: sum(x**2 for x in range(10)) works without extra parentheses. With multiple arguments, you need the extra pair: max((x for x in items), key=len).
Why use Decimal instead of rounding floats?
Rounding floats is a workaround, not a fix. The representation error accumulates over repeated operations. Decimal stores numbers as true decimal fractions, so Decimal("0.10") * 3 gives exactly Decimal("0.30"), not 0.30000000000000004.
What replaced os.popen() and os.system() that subprocess was meant to fix?
os.popen(), os.popen2/3/4(), os.system(), commands.getoutput(), and others were scattered, inconsistent, and platform-specific. subprocess unified all of them with a single, cross-platform API and better security (no shell injection when using lists instead of strings).
When should frozenset be used over set?
When you need a set that is hashable -- as a dict key, as an element of another set, or as a function argument that should not be mutated by the callee. Frozensets are also thread-safe to read from multiple threads since they cannot be modified.