Classes

Python 3 drops support for “old-style” classes, and introduces dedicated syntax for metaclasses. Read on for details.

New-Style Classes

  • Fixer: None
  • Prevalence: Very common

Python 2 had two styles of classes: “old-style” and “new-style”.

Old-style classes were defined without a superclass (or by deriving from other old-style classes):

class OldStyle:
    pass

class OldStyleChild(OldStyle):
    pass

New-style classes derive from a built-in class – in most cases, object:

class NewStyle(object):
    pass

class NewInt(int):
    pass

In Python 3, all classes are new-style: object is the default superclass.

For code compatible across Python versions, all classes should be defined with explicit superclasses: add (object) to all class definitions with no superclass list. To find all places to change, you can run the following command over the codebase:

grep --perl 'class\s+[a-zA-Z_]+:'

However, you will need to test the result thoroughly. Old- and new-style classes have slightly differend semantics, described below.

Method resolution

From a developer’s point of view, the main difference between the two is method resolution in multiple inheritance chains. This means that if your code uses multiple inheritance, there can be differences between which method is used for a particular subclass.

The differences are summarized on the Python wiki, and the new semantics are explained in a Howto document from Python 2.3.

Object model details

Another difference is in the behavior of arithmetic operations: in old-style classes, operators like + or % generally coerced both operands to the same type. In new-style classes, instead of coercion, several special methods (e.g. __add__/__radd__) may be tried to arrive at the result.

Other differences are in the object model: only new-style classes have __mro__ or mro(), and writing to special attributes like __bases__, __name__, __class__ is restricted or impossible.

Metaclasses

  • Fixer: python-modernize -wnf libmodernize.fixes.fix_metaclass
  • Prevalence: Rare

For metaclasses, Python 2 uses a specially named class attribute:

class Foo(Parent):
    __metaclass__ = Meta

In Python 3, metaclasses are more powerful, but the metaclass needs to be known before the body of the class statement is executed. For this reason, metaclasses are now specified with a keyword argument:

class Foo(Parent, metaclass=Meta):
    ...

The new style is not compatible with Python 2 syntax. However, the Compatibility library: six library provides a workaround that works in both versions – a base class named with_metaclass. This workaround does a bit of magic to ensure that the result is the same as if a metaclass was specified normally:

import six

class Foo(six.with_metaclass(Meta, Parent)):
    pass

The recommended fixer will import six and add with_metaclass quite reliably, but do test that the result still works.