What Is New in Python 2.2
Python 2.2 was one of the most fundamental Python releases -- it unified the old-style and new-style object system, introduced descriptors, properties, and slots, and brought generators into the language. These changes laid the groundwork for everything Python's object model is today.
| Category | Change | PEP / Reference |
|---|---|---|
| Object Model | New-style classes: inherit from object |
PEP 252, PEP 253 |
| Object Model | Descriptors, property(), classmethod(), staticmethod() |
PEP 252 |
| Object Model | __slots__ for memory-efficient classes |
PEP 252 |
| New Syntax | Generators with yield keyword |
PEP 255 |
| Builtins | iter() and next() protocol formalised |
PEP 234 |
| Standard Library | itertools predecessor: __iter__ protocol everywhere |
-- |
| Performance | MRO (Method Resolution Order) changed to C3 linearization | -- |
| Standard Library | Improved os.path, doctest, difflib |
-- |
The Most Important Python 2.2 Changes
New-Style Classes and the Unified Object Model
Before Python 2.2, there were two class flavors: "old-style" (classic) classes defined with class Foo: and the built-in types like int, list, dict. They were separate hierarchies with different behaviors. Python 2.2 unified them -- new-style classes inherit from object (explicitly or via a built-in type) and have consistent semantics for __init__, __new__, descriptors, and MRO.
# Old-style class (Python 2 only, deprecated)
class OldFoo:
pass
# New-style class (Python 2.2+, only kind in Python 3)
class NewFoo(object):
pass
# In Python 3, all classes are new-style automatically
class Foo: # implicitly inherits from object
pass
Descriptors and property() (PEP 252)
Descriptors are objects that define __get__, __set__, or __delete__ -- they control what happens when you access an attribute on a class or instance. The property() built-in uses descriptors to create managed attributes with getter, setter, and deleter methods.
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
Generators (PEP 255)
Functions containing yield become generator functions. Calling one returns a generator object that produces values lazily. This was the building block for Python's async and iteration story in later versions.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
gen = fibonacci()
first_10 = [next(gen) for _ in range(10)]
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
FAQ
Why did the old-style / new-style class distinction matter so much?
Old-style classes did not support descriptors, MRO was broken for diamond inheritance, type(instance) returned <type 'instance'> regardless of the actual class, and you could not subclass built-in types. New-style classes fixed all of this. In Python 3, all classes are new-style and the distinction no longer exists.
What is C3 MRO and why was it important?
The old MRO used depth-first left-to-right lookup, which broke for diamond inheritance. C3 linearization (used by Dylan and CLOS) computes a consistent, monotonic order that respects local precedence and inheritance graph constraints. In practice this means super() works correctly in complex class hierarchies.
What is the difference between classmethod and staticmethod?
@classmethod receives the class (cls) as its first argument -- useful for alternative constructors. @staticmethod receives no implicit first argument -- it is just a regular function namespaced inside the class. Use classmethod when the method needs to access or create instances of the class; use staticmethod for utility functions logically grouped with the class.
When should __slots__ be used?
When you have many instances (thousands or millions) of a class and memory is tight. __slots__ prevents the per-instance __dict__, reducing memory to a fixed set of typed slots. The downside: you lose flexibility to add arbitrary attributes, and subclassing becomes more complex.
Are generators lazy by default in Python 2.2?
Yes. A generator function only executes code up to the next yield when next() is called. The state is frozen between calls. The full sequence is never materialized in memory. This is why generators are the right choice for large data pipelines.