devxlogo

A Developer’s Guide to Python 3.0: Core Language Changes

A Developer’s Guide to Python 3.0: Core Language Changes

ython 3.0, a.k.a Python 3000, a.k.a Python 3K, is out. It aims to fix the language design problems accumulated over the Python 2.x’s lifetime, and to clean up the language and its standard library. But this upgrade is a bitter pill for developers to swallow, because Python 3.0 is not backward compatible with Python 2.x. Python 3.0 even breaks the venerable “hello world!” program.

But there’s some sugar to go with that medicine, too. Python 2 developers were not abandoned completely. Python 2.6 was released a few months prior to Python 3.0 and it aims to help in the migration process. For example, it includes a tool called 2to3 that assists in migrating Python 2.x code to Python 3.0. This article covers the major Python 3.0 language features, and future articles will cover:

  • Additional changes to the language
  • Changes to the standard library
  • Python 2.6
  • Migration from Python 2.x to Python 3.0

The Python Development Process

Python gets developed by an organized community effort led by BDFL (Benevolent Dictator for Life) Guido van Rossum, who hands down final judgments when the community can’t reach a consensus. Possible Python changes are specified using PEPs (Python Enhancement Proposals), and you can often find out more about a particular language feature or change by reading the appropriate PEP.

Python 3.0: A Bird’s Eye View

Python 3.0 touches almost every aspect of the language. There are deep changes to the type system, classes, metaclasses, and abstract base classes. Exception handling has been cleaned up. Numeric data types have been souped up. Strings are now Unicode, and string formatting has been improved. But perhaps the first change you’ll notice is that the print statement is now a function.

I’ve started here because print is the probably the first thing beginners encounter when learning Python. A “hello world” program in Python used to look like this:

   >>> print 'hello world!'   hello world!

But if you run that using Python 3.0, it looks like this:

   >>> print 'hello world!'     File "", line 1       print 'hello world!'                          ^   SyntaxError: invalid syntax

The right way to do it in Python 3.0 is to surround the string in parentheses:

   >>> print('hello world!')   hello world!

Why did print become a function? According to PEP-3105 (Make print a function) there are five reasons:

PEP-3105 Reason 1: In 2.x, print is the only application-level functionality that’s a statement rather than a function. In the Python world, syntax is generally used as a last resort—only when it’s impossible to accomplish some task without compiler assistance. And print doesn’t qualify for such an exception.

  • Author’s Response: That’s true, but print is especially useful in interactive sessions where the extra two parentheses really get in the way.

PEP-3105 Reason 2: During application development it’s quite common to replace print output with something more sophisticated, such as logging calls, or calls into some other I/O library. Using a print() function, that substitution involves only a straightforward string replacement; but with a statement, you have to add the parentheses and possibly convert to >>stream-style syntax.

  • Author’s Response: I don’t agree with this argument. I don’t think it’s a problem to replace all these print statements. I have had the pleasure (or misfortune) of performing similar (and worse) changes across huge code bases several times. You must consider each case and decide whether you want the original simple behavior or the new fancy behavior. Replacing the print function with something else is a cool trick, but it also violates the “explicit is better than implicit” principle. When I see print or print>> today in a piece of code, I know exactly what to expect (assuming no one messed around with sys.stdout). But if print can be replaced easily, it could be pretty confusing to see no output because everything goes to some obscure log file.

PEP-3105 Reason 3: Having special syntax for print puts up a much larger barrier for evolution. For example, it’s not too far-fetched to consider a hypothetical new printf() function, which would coexist with a print() function.

  • Author’s Response: Changing such a basic feature of the language due to speculative future change in the language seems very inappropriate and is in opposition to Python’s design philosophy.

PEP-3105 Reason 4: There’s no easy way to convert print statements into another call if one needs a separator other than spaces, or no separator. Also, there’s no easy way to conveniently print objects with some other separator than a space.

  • Author’s Response: I don’t agree with this argument either. If you need special formatting for objects, just format them into a string and print the string. Later, you’ll see an example using both Python 2.X and Python 3.0—and you’ll see that both look about the same and use clean code.

PEP-3105 Reason 5: It would be much easier to replace print() as a function within just one module (just def print(*args):…) or throughout a program (by putting a different function in __builtin__.print). As it is, one can do this by writing a class with a write() method and assigning that to sys.stdout. That’s not a bad solution, but it’s definitely a much larger conceptual leap, and it works at a different level than print.

  • Author’s Response: This argument is quite similar to reason #2, and I actually see it as a counter-argument. The fact that it is cumbersome to override print using sys.stdout makes doing so less prevalent, which is a good thing. In the rare cases when you actually need a print override (and simply using a different function won’t do, such as for testing purposes) it is possible to override the original print.

That’s quite a list of arguments, and I’ve included them because even this one issue should give you a sense of the deep controversies that Python 3.0 has—and will—engender. To me, overall, the benefits from the print function seem marginal, and useful only to people who want to modify the way print works. It also seems as if there are good enough ways to do that in Python 2.x, by using a separate function or manipulating sys.stdout. The downside is more cumbersome syntax in interactive sessions (the 2to3 tool can take care of the problem during migration).

So although I don’t like the print function, it’s here to stay. The greatest minds in the Python world have decreed that it should be so. Here’s the signature:

   def print(*args, sep=' ', end='
', file=None)

The *args are the arguments to be printed, sep is the string that will be printed between *args, and end is the character to be printed after the last argument. Sep and end default to a space and
, respectively—which is exactly the default behavior of the Python 2.x print statement. The file argument, if specified, sends the output to a provided file-like object (which should have a write() method) that provides the functionality of print>>.

Suppose you want to display addition exercises, for example:

   2 + 4 + 7 = 13

In Python 2.x printing the exercise might look like this:

   >>> print ' + '.join([repr(x) for x in numbers]) +       ' = %d
' % sum(numbers)   2 + 4 + 7 = 13

But in Python 3.0 it would look like this:

   >>> numbers = [2, 4, 7]   >>> print(*numbers, sep=' + ', end=' =%d
' % sum(numbers))   2 + 4 + 7 = 13

Both versions look a little on the cumbersome side. The following Python 3.0 code snippet wraps this logic in a function called print_numbers(). It stores the original print function in a variable called original_print. The new print_numbers() function relies on the original_print function to actually print. After printing a couple of exercises it restores the original print function.

   original_print = print      def print_numbers(*numbers):     sep = ' + '     end = ' = %d
' % sum(numbers)     original_print(*numbers, sep=sep, end=end)      # Make print_numbers the current print function   print = print_numbers      print(1, 2, 3)   print(3, 7)      # Restore the original print function   print = original_print      print(1, 2, 3)      Output:      1 + 2 + 3 = 6   3 + 7 = 10   1 2 3

After all my complaining about the print function here’s something nice: Because it’s a function, you can assign it to a variable with a shorter name (e.g. p) in your Python startup file. Then, in interactive sessions, the shorter name more than makes up for the parentheses:

   >>> p = print   >>> p('Yeah, it works!')   Yeah, it works!   >>> p(5 + 9)   14

If you take this route, then you may want to use the pprint function from the pprint module rather than print. The pprint function can display nested Python data structures in a nice layout. The following code snippet prints a dictionary that contains several lists using both print and pprint so you can see the difference:

   >>> r = list(range(10))   >>> d=dict(a=r, b=r, c=r)   >>> print(d)   {'a': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'c': [          0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'b': [          0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}   >>> from pprint import pprint as pp   >>> pp(d)   {'a': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],    'b': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],    'c': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}   >>> 

Note that pprint also sorted the dictionary items by key (as of Python 2.5). To write the preceding code, add the following statement to your Python startup file:

   from pprint import pprint as pp

I find such shorthand constructs useful in my day-to-day work when exploring complex data structures.

Explore the New Type System

In Python 3.0, the old classes are gone. Python 2.2 introduced new-style classes that unified built-in types and user-defined types, but old-style classes were kept for backward compatibility purposes. In Python 2.x (where x >=2) you defined classes like this:

   class OldStyleClass:       pass      class NewStyleClass(object):       pass

In Python 3.0 you don’t need to inherit from object anymore. The old-style classes were cramping Python’s style, and many new features couldn’t be applied to old-style classes. You’ll see more about decorators, function annotations, new metaclasses, and abstract base classes in the following sections.

Abstract Base Classes

The Abstract Base Classes (ABCs) feature is part of an ongoing trend to make Python’s object model richer, and provide metadata for classes. Abstract base classes let you test the capabilities of objects (such as function call parameters) reliably. Here’s a quick example:

   def foo(arg):      assert isinstance(arg, dict)      print arg['name']

The foo() function needs to access its arg argument as a dictionary, so it checks whether arg is an instance of dict—but that’s limiting in many situations. Any class that defines a __getitem__ method may be accessed as a dictionary:

   class A(object):       def __getitem__(key):           return len(key) * 'A'         >>> a = A()   >>> print a['123']   AAA

It is possible to test for the existence of every attribute of you plan to use, but that’s both tedious and error-prone, and still might not provide assurance that you’re dealing with the right object.

Abstract base classes solve this problem elegantly. You can assign an abstract class, which is a syntactic and (unenforced) semantic contract to a class, and then later test the object via isinstance() and/or issubclass(), as long as the object you are dealing with complies with the ABC.

Here’s a simple example. You write a space shooter game with spaceships, planets, asteroids, missiles, lasers and what not. Of course, you need to detect collisions between various objects, and to do that you need to access multiple attributes in your code such as object position and speed, and check to determine whether the object is still “alive” after the collision. In the detect_collision() function you need to ensure that the objects being tested support all the necessary attributes and methods. Here’s the function (the main logic is elided):

   def detect_collision(obj_1, obj_2):     assert isinstance(obj_1, MovingObject)     assert isinstance(obj_2, MovingObject)     # Collision detection logic follows...     ...

The preceding function uses isinstance() to verify that both obj_1 and obj_2 are instances of MovingObject, which is an ABC that defines the methods and attributes that detect_Collision() needs. Here’s the definition for MovingObject:

   from abc import ABCMeta, abstractmethod, abstractproperty      class MovingObject(metaclass=ABCMeta):     @abstractmethod     def is_alive(self):       return self._lifeCount > 0        @abstractmethod     def move(self, x, y):       pass        @abstractproperty     def speed(self):       pass        def get_position(self):       return self._position        def set_position(self, position):       self._position = position        position = abstractproperty(get_position, set_position)

The code starts by importing the new module abc that contains the ABCMeta metaclass and the @abstractmethod and @abstractproperty decorators used to mark the abstract methods and properties of the ABC. Abstract methods and properties must be implemented by the class that implements the ABC contract even if the ABC provides implementation; however, the implementing class may simply call the ABC’s implementation. This is similar to implemented C++ pure virtual functions (many people mistakenly believe that pure virtual functions in C++ can’t have implementation).

The MovingObject class has ABCMeta as a metaclass (Note the new syntax for metaclasses in Python 3.0, covered later). The ABCMeta metaclass is not necessary for ABCs, but it makes life much easier because it has a default implementation of __instancecheck__() and __subclasscheck__() that are the key methods for ABCs. Note that you can declare read-only properties such as speed using the @abstractproperty decorator, but you must declare read-write properties such as position using the abstractproperty class.

Ok, you have a MovingObject ABC, but if you try to instantiate it, you’ll get an error:

   >>> MovingObject()   Traceback (most recent call last):     File "", line 1, in    TypeError: Can't instantiate abstract class MovingObject       with abstract methods is_alive, move, position, speed

The error occurs because it’s an abstract class. You can’t instantiate a MovingObject instance, but MovingObject can serve as a base class. For example, the Spaceship class subclasses MovingObject and is thus obligated to implement all the abstract methods and properties, is_alive(), move(), speed, and position:

   class Spaceship(MovingObject):     def __init__(self):       self._lifeCount = 1       self._speed = 5       self.position = (100, 100)        def speed(self):       return self._speed        def get_life_count(self):       return self._lifeCount        def is_alive (self):       return MovingObject.is_alive(self)        def move(self, x, y):       self.position = (self.position[0] + x, self.position[1] + y)        position = property(MovingObject.get_position, MovingObject.set_position)

If your subclass doesn’t implement all the abstract methods and properties it is still considered an abstract class, and therefore, if you try to instantiate it you will again get a nice error message with the names of the missing abstract methods or properties (is_alive and position in this case).

   class StillAbstractMovingObject(MovingObject):     def move(self):       pass        def speed(self):       pass      >>> StillAbstractMovingObject()   Traceback (most recent call last):     File "", line 1, in    TypeError: Can't instantiate abstract class StillAbstractMovingObject       with abstract methods is_alive, position

One potential problem is that implemented abstract methods don’t need to have the same signature as the original method in the ABC; Python verifies only the method names. That means that the ABC framework will not ensure that your subclasses fully implement the abstract contract.

Python 3.0 comes preloaded with a bunch of ABCs for containers, iterators and numbers. You’ll see more about the new number type hierarchy and ABCs later. The collections module contains the ABC definitions for containers and iterators, while the numbers module defines the ABCs for number. Both modules conveniently define hierarchies of abstract classes that you can use to make sure an object supports the functionality you need. In collections for example you’ll find a Mapping abstract class that all mapping (dictionary-like) objects should adhere to. Mapping subclasses the Sized, Iterable and Container abstract classes that define the abstract methods __len__, __iter__ and __contains__. The Mapping class itself (see Listing 1) defines another abstract method called __getitem__, which implements the abstract __contains__ method. So, at the end of the day Mapping has the following unimplemented abstract methods (stored in the __abstractmethods__ attribute): __getitem__, __iter__ and __len__. Concrete instantiable mapping objects must implement all three methods.

Classes don’t always have to subclass an ABC to fulfill it and respond properly to isinstance() and issubclass() calls. You can also register classes and built-in types. For example, the built-in dict class subclasses only objects yet still returns True for the following queries:

   >>> issubclass(dict, collections.Mapping)   True   >>> issubclass(dict, collections.MutableMapping)   True   >>> isinstance({}, collections.Sized)   True

Registration is useful if you don’t want to mess around with the base classes list of your classes. You should be careful when you register classes with ABCs, because registered classes always return True for issubclass() and isinstance() checks—even if the registered class does not actually implement the required abstract methods or properties. For example, the Spaceship class is registered as a Sequence even though it doesn’t implement the required __len__ and __getitem__ abstract methods:

   s = Spaceship()   assert isinstance(s, collections.Sequence) == False   collections.Sequence.register(Spaceship)   assert isinstance(s, collections.Sequence) == True   assert hasattr(s, '__len__') == False   assert hasattr(s, '__getitem__') == False
Author’s Note: Read PEP-3119 for further details.

Class Decorators

Class decorators let you modify a class when it is first declared by passing the class to a function that may manipulate it by adding /removing/modifying methods, attributes, properties, base classes, etc. This is particularly useful when you need to apply some modifications to a set of classes (not necessarily all derived from a common base class).

In the following example the dump() function is a class decorator that takes its input class and adds a method called dump_methods() to it. The dump_methods() method scans the dictionary of its class and prints all the callable methods with a nice title. Note that it returns the cls input class. You’ll use this decorator class to instantiate objects in a minute:

   def dump(cls):     def dump_methods(self):       cls = type(self)       s = cls.__name__ + ' methods'       print (s + '
' + '-' * len(s))       for k,v in cls.__dict__.items():         # The callable() builtin has been removed in Python 3.0         if hasattr(v, '__call__'):           print (k)       print ()        # Attch the dump_methods nested function as a method to the input class     cls.dump_methods = dump_methods     return cls

Now, that you have a class decorator here’s an example of decorating a couple of classes. The classes A and B are pretty boring, but serve to demonstrate the class decorator in action:

   @dump   class A:     def foo():       pass     def bar():       pass      @dump   class B:     def baz():       pass

Both classes A and B are decorated by @dump, meaning you can call dump_methods() on their instances:

   A().dump_methods()   B().dump_methods()      A methods   ---------   bar   dump_methods   foo      B methods   ---------   dump_methods   baz

As you can see, dump_methods() itself shows up in the output, because decorator-added methods are indistinguishable from the original methods.

Class decorators are a natural extension to function and method decorators. During the Python 2.4 design process (when function and method decorators were introduced into the language) they seemed redundant, because metaclasses provided a very similar capability. The main distinction (other than syntax) is that metaclasses are inherited. In other words, when you applied a metaclass to a class, all that class’s subclasses inherit the metaclass. In contrast, class decorators don’t affect sub-classes. Just to complicate things, you can compose class decorators with only a single metaclass (things get complicated if you inherit from multiple base classes, each with its own metaclass, but in the end only one metaclass will prevail). I find the mechanics of decorators much simpler to understand than metaclasses, and it’s nice to have uniform syntax and semantics for class, function and method decorators.

For more information, read PEP-3129.

Function Annotations

Function annotations are my favorite new feature in Python 3000 (along with ABCs). You can attach arbitrary Python expressions to function arguments and/or return values. You decide how you want to annotate your functions and what they mean. Type checking is a classic example; you can annotate each argument and the return value of a function with a type. Doing that helps users to figure out how to call the function. For example, the function calc_circumference() expects an integral radius and returns a float number. Note the annotation syntax for the radius argument and for the return value.

   def calc_circumference(radius: int) -> float:     return 2 * math.pi * radius

This is not so impressive by itself, because you could have just documented it in the docstring (although it is more readable as an annotation). Function annotations are stored as a dict in a function attribute called __anotations__.

   >>> calc_circumference.__annotations__   {'radius': , 'return': }

You can access the annotations inside the function too, of course, and verify that the type of each argument matches its annotation. This turns out to be a little more complicated than you might expect, because Python stores the annotations in the dict as an unordered collection. The following simple function accepts three int arguments and prints them:

   def print_3(a: int, b: int, c: int)

The following example shows one way to access the function annotations to validate that the radius is actually an integer:

   def calc_circumference(radius: int) -> float:     assert isinstance(radius, calc_circumference.__annotations__['radius'])     return 2 * math.pi * radius

Unfortunately, that’s pretty silly code. It’s cumbersome, error-prone and if you need to verify multiple arguments it seriously hinders readability. Moreover, if you write such function-specific validations you don’t need annotations because as the function author you already know that radius is supposed to be an int, and can just write:

   assert isinstance(radius, int)

The real power comes when you access the function annotations via a general-purpose decorator. You can use the call_check decorator (see Listing 2) to decorate any function or method that has type annotations for its arguments. The decorator extracts the annotations and asserts that the type of each argument matches its type annotation on every call. It also verifies the return value type (if it was annotated—the return annotation). You need a little finicky code to extract the names of non-keyword arguments from the function’s code object because the annotations in the dictionary are unordered; if you just iterate over the items the order may not be the order of the function arguments. This is a serious weakness of function annotations that I hope will be fixed in Python 3.1.

To test the decorator, the code below applies it to the calc_circumference method:

   @call_check   def calc_circumference(radius: int) -> float:     return 2 * math.pi * radius

Now, if you try calling calc_circumference with both valid and invalid arguments, you’ll find that a call with radius 10 returns the correct answer. A second call, using the invalid argument 10.5 (not an int), results in an exception that explains exactly what the problem is and tells the caller that the argument radius with value 10.5 must be an int.

   >>> calc_circumference(10)   62.8318530718      >>> calc_circumference(10.5)   Traceback (most recent call last):     File "", line 1, in      File "article_1.py", line 117, in decorated       raise Exception(s)   Exception: The type of radius=10.5 should be int

The next step (which I’ll leave to readers) is to use a class decorator or metaclass to decorate all the methods of a class with the call_check decorator automatically.

There are many more use cases for function annotations, such as help strings, rich type information (e.g. allowed range of values), direct validation functions, hints to let IDEs provide better experience, mapping, adapters, and corresponding C types.

One concern with function annotations is that excessive use might create a “language within a language,” where you can’t really tell what happens when you call a function until you see the code that processes the annotations.

PEP-3115 Metaclasses in Python 3000

Metaclasses are classes whose instances are also classes. I like to think of them as class customizers. They have the same role as class decorators, but different mechanics (more complicated). The features that distinguish metaclasses from class decorators are:

  1. Metaclasses are inherited.
  2. Each class can have exactly one metaclass.

Even these simple features already present a problem. For example, what happens if you inherit from two classes with different metaclasses? The answer is: it depends. If the two metaclasses are unrelated you are not allowed to multiply inherit:

   class M1(type): pass   class M2(type): pass      class A(metaclass=M1): pass   class B(metaclass=M2): pass      class C(A, B): pass      Traceback (most recent call last):     File "", line 1, in    TypeError: metaclass conflict: the metaclass of a derived class must be       a (non-strict) subclass of the metaclasses of all its bases

To make this work, one of the metaclasses must be a subclass (possibly indirect) of the other. The most derived metaclass will be used as the metaclass for the new class.

   class M1(type): pass   class M2(M1): pass      class A(metaclass=M1): pass   class B(metaclass=M2): pass      class C(A, B): pass      >>> C.__class__   

Metaclasses in Python 3.0 fix a thorny issue in Python 2.X: The order of class attributes couldn’t be determined just from its __dict__. In many cases, the order is important when interfacing with external systems (ORM, foreign language bridges, COM objects, etc). As a result, if you wanted to use metaclasses for these use cases you had to require each class to provide the order using some contrived convention, such as providing a sequenced list of all the relevant fields:

   >>> class A(object):   ...   x = 3   ...   y = 4   ...   z = 5   ...   order = ['x', 'y', 'z']   ...   >>> A.__dict__.keys()   ['__module__', '__doc__', '__dict__', 'y', 'x', 'z', '__weakref__', 'order']   >>> A.order   ['x', 'y', 'z']

In contrast, Python 3.0 metaclasses can implement a method called __prepare__() that returns an object that serves in lieu of the standard dict() to store the members. The trick is that __prepare__() may return an object that either stores the order of the fields or ensures that iteration will always return the items in the order they were inserted.

For example, suppose you want to create a linear workflow class where the methods must be called in a certain sequence. If you have multiple workflows and you keep adding and removing steps, it can get quite tricky to do the bookkeeping and make sure that users call the workflow methods in the right order. Using that same scenario, here’s a metaclass-based solution in which the final result is a class whose methods you must call in the order of their declaration in the class. The following code shows both the class and demonstrations for using it both properly and improperly.

The LinearWorkflow class is very simple and has four methods: start, step_1, step_2, and step_3, declared in that order. It also has a metaclass (you’ll see more about that later), that ensures the methods are called in the proper sequence. For this example, the methods just print their names. Here’s the class:

   class LinearWorkflow(metaclass=PedanticMetaclass):     def __init__(self):       pass        def start(self):       print('start')        def step_1(self):       print('step_1')        def step_2(self):       print('step_2')        def step_3(self):       print('step_3')

The following interactive session instantiates a LinearWorkflow object and starts calling methods. It calls start() and step_1(), which execute. Then, it tries to call step_1 again and get an exception that step_1 was called out of order. It calls step_2() successfully and then stubbornly tries to call step_1() one more time (again, out of order) and gets another exception.

   >>> x = LinearWorkflow()   >>> x.start()   start   >>> x.step_1()   step_1   >>> x.step_1()   Traceback (most recent call last):     File "", line 1, in      File "article_1.py", line 192, in decorated       raise Exception('Method %s called out of order' % f.name)   Exception: Method step_1 called out of order      >>> x.step_2()   step_2   >>> x.start()   Traceback (most recent call last):     File "", line 1, in      File "article_1.py", line 192, in decorated       raise Exception('Method %s called out of order' % f.name)   Exception: Method start called out of order

How is this magic accomplished? The PedanticMetaclass keeps tabs on the order of the methods by providing an OrderedDict (a dictionary-like class that stores items in insertion order as a list of pairs). When you call the PedanticMetaclass’s __new__() method it adds each method of its decorated class (LinearWorkflow in this case) that does not start with an underscore in the OrderedDict. Then it records each method called on its decorated class, and raises an exception if a method is called out of order. Here are the OrderedDict and PedanticMetaclass classes:

   class OrderedDict(collections.Mapping):     def __init__(self):       self._items = []        def __getitem__(self, key):       for k, v in self._items:         if k == key:           return v       raise KeyError(key)        def __setitem__(self, key, value):       for i, (k, v) in enumerate(self._items):         if k == key:           self._items[i] = (k, value)           return       self._items.append((key, value))        def __iter__(self):       return iter([k for k,v in self._items])        def __len__(self):       return len(self._items)        def __repr__(self):       s = '{'       for k, v in self._items:         s += '%s: %s, ' % (str(k), str(v))       s += '}'       return s
Author’s Note: OrderedDict is intended for demonstration purposes only. I don’t recommend using it in production code because it is very inefficient: It performs a linear search for every item access.

The PedanticMetaclass is not completely trivial, but you should be able to follow the code. The __prepare__() method must be a classmethod; it simply returns an empty OrderedDict. The decorate() function performs the order-checking logic by wrapping each call to methods in the _order list. The decorator is applied manually in the __new__() method and then the decorated method is assigned to the new_class using setattr (replacing the original method).

   class PedanticMetaclass(type):     @classmethod     def __prepare__(metacls, name, bases):       return OrderedDict()        def decorate(self, f):       """This method is called everytime an attribute is accessed       it ensures that the attributes are accessed in the order they were declared       """       def decorated(*args, **keywds):         assert f.name in self._order         if self._called + [f.name] != self._order[:len(self._called) + 1]:           raise Exception('Method %s called out of order' % f.name)         self._called.append(f.name)         if len(self._called) == len(self._order):           self._called = []         return f(*args, **keywds)       return decorated        def __new__(metacls, name, bases, ordered_dict):       # Must convert the OrderedDict back to a regular dict       new_class = type.__new__(metacls, name, bases, dict(ordered_dict.items()))       # Create the ordered list of public methods automatically       # (ignore non-callables and methods that start with underscore)       order = [k for k,v in ordered_dict.items() if hasattr(v, '__call__')                                               and not k.startswith('_')]       new_class._order = order       # Keep the last accessed       new_class._called = []          # Decorate each method to record and check call order       for x in order:         m = getattr(new_class, x)         m.name = x         m = PedanticMetaclass.decorate(new_class, m)         setattr(new_class, x, m)          return new_class

PEP-3109 Raising Exceptions in Python 3000

In Python 2.X you can raise exceptions in two equivalent ways:

   raise E, V   raise E(V)

In both cases E is the exception class and V is the value. However, Python 3.0 allows only the second form. Python 2.X also supports one other (rare) way to raise an exception with an arbitrary traceback:

   raise E, V, T

In Python 3.0 that becomes:

   e = E(V)   e.__traceback__ = T   raise E

Although the Python 3.0 form is more cumbersome, because this form of raising exceptions is so rare (I can’t think of a single use where I would want to attach an arbitrary traceback to an exception) that may not be important. If you want to re-raise the active exception inside an except or finally block, just continue using raise with no arguments.

Generators had an asynchronous facility for raising exceptions via the throw() method that accepted three arguments similar to the raise E, V, T form. In Python 3.0 throw() accepts only a single exception argument, so it’s consistent with raise.

PEP-3110 Catching Exceptions in Python 3000

There are a few problems with the syntax and semantics of exception catching in Python 2.x. For example, to catch an exception type and its instance you use the following semantics:

   except , :

If you needed to catch just a couple of exception types, you could wrap the types in parentheses:

   except (, ):

Unfortunately, that syntax can lead to some pretty nasty bugs if you forget the parentheses. In the following code snippet the except clause catches Exception and str (without parentheses). The intention is to catch either the general Exception class or the str type. The result is that only the Exception type is caught. The specific instance of Exception (DivisionInZeroException in this case) gets assigned to str. Trying to use str() later ends in an obscure and catastrophic fashion.

   >>> try:   ...   3 / 0   ... except Exception, str:   ...   print repr(str)   ...    ZeroDivisionError('integer division or modulo by zero',)   >>> str(5)   Traceback (most recent call last):     File "", line 1, in    TypeError: 'exceptions.ZeroDivisionError' object is not callable

If this exception-handling code happened to buried somewhere deep in your code, so that str() was used much later, it could be pretty difficult to locate.

Python 3.0 addresses these issues by replacing the first form (except TYPE, INSTANCE) with the following form:

   except  as 

In addition, the exception type must derive from exceptions.BaseException. If you want to catch multiple exception types you may still use the tuple syntax. Here’s an example of how to catch exceptions in Python 3.0:

   >>> try:   ...   3 / 0   ... except Exception as e:   ...   print(repr(e))   ...    ZeroDivisionError('int division or modulo by zero',)      >>> try:   ...   3 / 0   ... except (Exception, ZeroDivisionError):   ...   pass   ... 

Other changes related to exception catching are that the sys.exc_type, sys.exc_value, and sys.exc_traceback have been removed. The information is available as the tuple returned from sys.exc_info():

   >>> try:    ...   3/0   ... except:   ...   print(sys.exc_info())        ...    (, ZeroDivisionError('int division or modulo by zero',),       )

Even sys.exc_info() may go away in the future. Alternatives like exception attributes were discussed—but dropped due to the difficulties involved in auto-fixing existing code. The traceback has become an attribute as you will see in the next section.

Finally, Python 3.0 will make sure that the exception instance gets deleted at the end of the except clause to avoid garbage collection issues.

PEP-3134 Exception Chaining and Embedded Tracebacks

Exception objects in Python 3.0 acquire three new attributes: __context__, __cause__, and __traceback__. The __context__ attribute is useful when you explicitly raise an exception that was originally raised during the handling of another exception. In Python 2.x the original exception was lost and replaced by the second exception as shown below:

   >>> try:   ...   raise Exception('first')   ... except:   ...   raise Exception('second')   ...   Traceback (most recent call last):     File "", line 4, in    Exception: second

Here’s the same code running in Python 3.0:

   >>> try:   ...   raise Exception('first')   ... except:   ...   raise Exception('second')   ...   Traceback (most recent call last):     File "", line 2, in    Exception: first      During handling of the above exception, another exception occurred:      Traceback (most recent call last):     File "", line 4, in    Exception: second

As you can see the second exception object stores the original exception object in its __context__ attribute:

   >>> e.__context__   Exception('first',)   >>> type(e.__context__)   

This process of exception chaining can go on and on if you have nested try-except blocks. Each exception stores the preceding exception in its __context__.

The __cause__ attribute is useful when you want to explicitly translate one exception to another. The syntax is: raise EXCEPTION from CAUSE. CAUSE must be an exception too. Here’s an example:

   try:     raise Exception('duh!') from Exception('Gigi')   except Exception as e:     assert e.__context__ is None     assert repr(e.__cause__) == "Exception('Gigi',)"

I don’t really understand why this syntax is needed. The from CAUSE is just syntactic sugar, because (except that CAUSE will be stored in __context__) it’s equivalent to:

   try:     try:       raise CAUSE     except:       raise EXCEPTION

The fact that in one form the original exception is stored in __context__ and in the other form it’s stored in __cause__ might lead to confusing and complex error handling code where you have to check not only the caught exception, but also the __cause__ and the __context__ because you can’t be sure what to expect. I can accept the syntactic sugar of from CAUSE, but I think CAUSE should just be stored in __context__.

The last new Exception attribute is __traceback__, which is a traceback object that tells you the exception origin. In Python 2.x you had to call sys.exc_info()[2] or sys.exc_traceback to get the traceback object. Now, it’s an attribute of the exception object itself, which makes it more discoverable and the code more concise. Remember to use the traceback module to make sense of traceback objects:

   import traceback      def f():     g()      def g():     raise Exception('nested')      t = None   try:     f()   except Exception as e:     t = e.__traceback__     print('----------------')     print('The  stack trace')     print('----------------')     traceback.print_tb(t)     print('----------------')      ----------------   The  stack trace   ----------------     File "article_1.py", line 244, in        f()     File "article_1.py", line 237, in f       g()     File "article_1.py", line 240, in g       raise Exception('nested')   ----------------

This first article in the “Developer’s Guide to Python 3.0” series provided information about the important changes to the core language and its type system. The next article in the series focuses on how Python 3.0 treats the basic data types: numbers, text and binary data.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist