DekGenius.com
[ Team LiB ] Previous Section Next Section

23.3 "New Style" Classes in Python 2.2

In Release 2.2, Python introduced a new flavor of classes, known as "new style" classes; the classes covered so far in this part of the book are known as "classic classes" when comparing them to the new kind.

New style classes are only slightly different than classic classes, and the ways in which they differ are completely irrelevent to the vast majority of Python users. Moreover, the classic class model, which has been with Python for over a decade, still works exactly as we have described previously.

New style classes are almost completely backward-compatible with classic classes, in both syntax and behavior; they mostly just add a few advanced new features. However, because they modify one special case of inheritance, they had to be introduced as a distinct tool, so as to avoid impacting any existing code that depends on the prior behavior.

New style classes are coded with all the normal class syntax we have studied. The chief coding difference is that you subclass from a built-in type (e.g., list) to produce a new style class. A new built-in name, object, is provided to serve as a superclass for new style classes if no other built-in type is appropriate to use:

class newstyle(object):
    ...normal code...

More generally, any object derived from object or other built-in type is automatically treated as a new style class. By derived, we mean that this includes subclasses of object, subclasses of subclasses of object, and so on—as long as a built-in is somewhere in the superclass tree. Classes not derived from built-ins are considered classic.

23.3.1 Diamond Inheritance Change

Perhaps the most visible change in new style classes is their slightly different treatment of inheritance for the so-called diamond pattern of multiple inheritance trees—where more than one superclass leads to the same higher superclass further above. The diamond pattern is an advanced design concept, which we have not even discussed for normal classes.

In short, with classic classes inheritance search is strictly depth first, and then left to right—Python climbs all the way to the top before it begins to back up and look to the right in the tree. In new style classes, the search is more breadth-first in such cases—Python chooses a closer superclass to the right before ascending all the way to the common superclass at the top. Because of this change, lower superclasses can overload attributes of higher superclasses, regardless of the sort of multiple inheritance trees they are mixed into.

23.3.1.1 Diamond inheritance example

To illustrate, consider this simplistic incarnation of the diamond inheritance pattern for classic classes:

>>> class A:      attr = 1             # Classic
>>> class B(A):   pass
>>> class C(A):   attr = 2
>>> class D(B,C): pass                 # Tries A before C
>>> x = D(  )
>>> x.attr
1

The attribute here was found in superclass A, because inheritance climbs as high as it can before backing up and moving right—it searches D, B, A, then C (and stops when attr is found in A above B). With the new style classes derived from a built-in like object, though, inheritance looks in C first (to the right) before A (above B)—it searches D, B, C, then A (and in this case stops in C):

>>> class A(object): attr = 1          # New style
>>> class B(A):      pass
>>> class C(A):      attr = 2
>>> class D(B,C):    pass              # Tries C before A
>>> x = D(  )
>>> x.attr
2

This change in inheritance is based upon the assumption that if you mix in C lower in the tree, you probably intend to grab its attributes in preference to A's. It also assumes that C probably meant to overide A's attribute always—it does when used standalone, but not when mixed into a diamond with classic classes. You might not know that C may be mixed-in like this at the time you code it.

23.3.1.2 Explicit conflict resolution

Of course, the problem with assumptions is that they assume things. If this search order deviation seems too subtle to remember, or if you want more control over the search process, you can always force the selection of an attribute from anywhere in the tree by assigning or otherwise naming the one you want at the place where classes are mixed together:

>>> class A:      attr = 1            # Classic
>>> class B(A):   pass
>>> class C(A):   attr = 2
>>> class D(B,C): attr = C.attr       # Choose C, to the right.
>>> x = D(  )
>>> x.attr                            # Works like new style
2

Here, a tree of classic classes is emulating the search order of new style classes; the assignment to the attribute in D picks the version in C, thereby subverting the normal inheritance search path (D.attr will be lowest in the tree). New style classes can similarly emulate classic classes, by choosing the attribute above at the place where the classes are mixed together:

>>> class A(object): attr = 1         # New style
>>> class B(A):      pass
>>> class C(A):      attr = 2
>>> class D(B,C):    attr = B.attr    # Choose A.attr, above.
>>> x = D(  )
>>> x.attr                            # Works like classic
1

If you are willing to always resolve conflicts like this, you can largely ignore the search order difference, and not rely on assumptions about what you meant when you coded your classes. Naturally, the attributes we pick this way can also be method functions—methods are normal, assignable objects:

>>> class A:
...    def meth(s): print 'A.meth'
>>> class C(A):
...     def meth(s): print 'C.meth'
>>> class B(A): 
...     pass

>>> class D(B,C): pass                 # Use default search order.
>>> x = D(  )                          # Will vary per class type
>>> x.meth(  )                         # Defaults to classic order
A.meth

>>> class D(B,C): meth = C.meth      # Pick C's method: new style.
>>> x = D(  )
>>> x.meth(  )
C.meth

>>> class D(B,C): meth = B.meth      # Pick B's method: classic.
>>> x = D(  )
>>> x.meth(  )
A.meth

Here, we select methods by assignments to same names lower in the tree. We might also simply call the desired class explicity; in practice, this pattern might be more common, especially for things like constructors:

class D(B,C):
    def meth(self):                  # Redefine lower.
        ...
        C.meth(self)                 # Pick C's method by calling.

Such selections by assignment or call at mix-in points can effectively insulate your code from this difference in class flavors. By explicitly resolving the conflict this way, your code won't vary per Python version in the future (apart from perhaps needing to derive classes from a built-in for the new style).[2]

[2] Even without the classic/new divergence, this technique may sometimes come in handy in multiple inheritance scenarios in general. If you want part of a superclass on the left, and part of a superclass on the right, you might need to tell Python which same-named attributes to choose by such explicit assignments in subclasses. We'll revisit this notion in a gotcha at the end of this chapter. Also note that diamond inheritance can be more problematic in some cases than we've implied (e.g., what if B and C both have required constructors that call to A's?), but this is beyond this book's scope.

By default, the diamond pattern is searched differently in classic and new style classes, and this is a non-backward compatible change. However, keep in mind that this change only effects diamond pattern cases; new style class inheritance works unchanged for all other inheritance tree structures. Further, it's not impossible that this entire issue may be of more theoretical than practical importance—since it wasn't significant enough to change until 2.2, it seems unlikely to impact much Python code.

23.3.2 Other New Style Class Extensions

Beyond this change in the diamond inheritance pattern (which is itself too obscure to matter to most readers of this book), new style classes open up a handful of even more advanced possibilities. Here's a brief look at each.

23.3.2.1 Static and class methods

It is possible to define methods within a class that can be called without an instance: static methods work roughly like simple instance-less functions inside a class, and class methods are passed a class instead of an instance. Special built-in functions must be called within the class to enable these method modes: staticmethod and classmethod. Because this is also a solution to a longstanding gotcha in Python, we'll present these calls later in this chapter in Section 23.4 . Note that the new static and class methods also work for classic classes in Python release 2.2.

23.3.2.2 Instance slots

By assigning a list of string attribute names to a special __slots__ class attribute, it is possible for new style classes to limit the set of legal attributes that instances of the class will have. This special attribute is typically set by assigning to variable __slots__ at the top level of a class statement. Only those names in the __slots__ list can be assigned as instance attributes. However, like all names in Python, instance attribute names must still be assigned before they can be referenced, even if listed in __slots__. Here's an example to illustrate:

>>> class limiter(object):
...     __slots__ = ['age', 'name', 'job']
        
>>> x = limiter(  )
>>> x.age                     # Must assign before use
AttributeError: age

>>> x.age = 40
>>> x.age
40
>>> x.ape = 1000              # Illegal: not in slots
AttributeError: 'limiter' object has no attribute 'ape'

This feature is envisioned as a way to catch "typo" errors (assignment to illegal attribute names not in __slots__ is detected) and as a possible optimization mechanism in the future. Slots are something of a break with Python's dynamic nature, which dictates that any name may be created by assignment. They also have additional constraints and implications that are far too complex for us to discuss here (e.g., some instances with slots may not have an attribute dictionary __dict__); see Python 2.2 release documents for details.

23.3.2.3 Class properties

A mechanism known as properties provides another way for new style classes to define automatically called methods for access or assignment to instance attributes. This feature is an alternative for many current uses of the __getattr__ and __setattr__ overloading methods studied in Chapter 21. Properties have a similar effect to these two methods, but incur an extra method call only for access to names that require dynamic computation. Properties (and slots) are based on a new notion of attribute descriptors, which is too advanced for us to cover here.

In short, properties are a type of object assigned to class attribute names. They are generated by calling a property built-in with three methods (handlers for get, set, and delete operations), as well as a docstring; if any argument is passed as None or omitted, it is not supported. Properties are typically assigned at the top level of a class statement (e.g., name=property(...)). When thus assigned, accesses to the class attribute itself (e.g., obj.name) are automatically routed to one of the accessor methods passed into the property. For example, the __getattr__ method allows classes to intercept undefined attribute references:

>>> class classic:
...     def __getattr__(self, name):
...         if name == 'age':
...             return 40
...         else:
...             raise AttributeError
...        
>>> x = classic(  )
>>> x.age                                    # Runs __getattr__
40
>>> x.name                                   # Runs __getattr__
AttributeError

Here is the same example, coded with properties instead:

>>> class newprops(object):
...     def getage(self):
...         return 40
...     age = property(getage, None, None, None)      # get,set,del,docs
... 
>>> x = newprops(  )
>>> x.age                                    # Runs getage
40
>>> x.name                                   # Normal fetch
AttributeError: newprops instance has no attribute 'name'

For some coding tasks, properties can be both less complex and quicker to run than the traditional techniques. For example, when we add attribute assignment support, properties become more attractive—there's less code to type, and you might not incur an extra method call for assignments to attributes you don't wish to compute dynamically:

>>> class newprops(object):
...     def getage(self):
...         return 40
...     def setage(self, value):
...         print 'set age:', value
...         self._age = value
...     age = property(getage, setage, None, None)
...
>>> x = newprops(  )
>>> x.age                     # Runs getage
40
>>> x.age = 42                # Runs setage 
set age: 42
>>> x._age                    # Normal fetch; no getage call
42
>>> x.job = 'trainer'         # Normal assign; no setage call
>>> x.job                     # Normal fetch; no getage call
'trainer'

The equivalent classic class might trigger extra method calls, and may need to route attribute assignments through the attribute dictionary to avoid loops:

>>> class classic:
...     def __getattr__(self, name):            # On undefined reference
...         if name == 'age':
...             return 40
...         else:
...             raise AttributeError
...     def __setattr__(self, name, value):     # On all assignments
...         print 'set:', name, value
...         if name == 'age':
...             self.__dict__['_age'] = value
...         else:
...             self.__dict__[name] = value
...
>>> x = classic(  )
>>> x.age                     # Runs __getattr__
40
>>> x.age = 41                # Runs __setattr__
set: age 41
>>> x._age                    # Defined: no __getattr__ call
41
>>> x.job = 'trainer'         # Runs __setattr__ again
>>> x.job                     # Defined: no __getattr__ call

Properties seem like a win for this simple example. However, some applications of __getattr__ and __setattr__ may still require more dynamic or generic interfaces than properties directly provide. For example, in many cases, the set of attributes to be supported cannot be determined when the class is coded, and may not even exist in any tangible form at all (e.g., when delegating arbitrary method references to a wrapped/embedded object generically). In such cases, a generic __getattr__ or __setattr__ attribute handler with a passed-in attribute name may be an advantage. Because such generic handlers can also handle simpler cases, properties are largely an optional extension.

23.3.2.4 New __getattribute__ overload method

The __getattribute__ method, available for new style classes only, allows a class to intercept all attribute references, not just undefined references like __getattr__. It is also substantially trickier to use than both __getattr__ or __setattr__ (it is prone to loops). We'll defer to Python's standard documentation for more details.

Besides all these feature additions, new style classes integrate with the notion of subclassable types that we met earlier in this chapter; subclassable types and new style classes were both introduced in conjunction with a merging of the type/class dichotomy in 2.2 and beyond.

Because new style class features are all advanced topics, we are going to skip further details in this introductory text. Please see Python 2.2 release documentation and the language reference for more information.

It is not impossible that new style classes might be adopted as the single class model in future Python releases. If they are, you might simply need to make sure your top-level superclasses are derived from object or other built-in type name (if even that will be required at all); everything else we've studied in this part of the book should continue to work as described.

    [ Team LiB ] Previous Section Next Section