xoutil.eight – Extensions for writing code that runs on Python 2 and 3

Xoutil extensions for writing code that runs on Python 2 and 3

The name comes from (Manu’s idea’) “2 raised to the power of 3”.

There is an existing library written by “Benjamin Peterson” named six, both (xoutil.eight and six) can be used together since this module don’t claim to be a replacement of six, just some extra extensions. Nevertheless, there are some simple definitions that even when are in six also are defined also here.

This package also fixes some issues from PyPy interpreter.

xoutil.eight.exec_(_code_, _globs_=None, _locs_=None)[source]

Execute code in a namespace.

xoutil.eight.intern(string) → string[source]

``Intern’’ the given string. This enters the string in the (global) table of interned strings whose purpose is to speed up dictionary lookups. Return the string itself or the previously interned string object with the same value.

xoutil.eight.iteritems(d)[source]

Return an iterator over the (key, value) pairs of a dictionary.

xoutil.eight.iterkeys(d)[source]

Return an iterator over the keys of a dictionary.

xoutil.eight.itervalues(d)[source]

Return an iterator over the values of a dictionary.

xoutil.eight.typeof(obj)[source]

Obtain the object’s type compatible with Py 2**3.

Tools

Implements the metaclass() function using the Py3k syntax.

xoutil.eight.meta.metaclass(meta, **kwargs)[source]

Define the metaclass of a class.

New in version 1.7.0: It’s available as xoutil.objects.metaclass() since 1.4.1. That alias is now deprecated and will be removed in 1.8.

This function allows to define the metaclass of a class equally in Python 2 and 3.

Usage:

>>> class Meta(type):
...   pass

>>> class Foobar(metaclass(Meta)):
...   pass

>>> class Spam(metaclass(Meta), dict):
...   pass

>>> type(Spam) is Meta
True

>>> Spam.__bases__ == (dict, )
True

New in version 1.5.5: The kwargs keywords arguments with support for __prepare__.

Metaclasses are allowed to have a __prepare__ classmethod to return the namespace into which the body of the class should be evaluated. See PEP 3115.

Warning

The PEP 3115 is not possible to implement in Python 2.7.

Despite our best efforts to have a truly compatible way of creating meta classes in both Python 2.7 and 3.0+, there is an inescapable difference in Python 2.7. The PEP 3115 states that __prepare__ should be called before evaluating the body of the class. This is not possible in Python 2.7, since __new__ already receives the attributes collected in the body of the class. So it’s always too late to call __prepare__ at this point and the Python 2.7 interpreter does not call it.

Our approach for Python 2.7 is calling it inside the __new__ of a “side” metaclass that is used for the base class returned. This means that __prepare__ is called only for classes that use the metaclass() directly. In the following hierarchy:

class Meta(type):
     @classmethod
     def __prepare__(cls, name, bases, **kwargs):
         from xoutil.collections import OrderedDict
         return OrderedDict()

class Foo(metaclass(Meta)):
     pass

class Bar(Foo):
     pass

when creating the class Bar the __prepare__() class method is not called in Python 2.7!

Warning

You should always place your metaclass declaration first in the list of bases. Doing otherwise triggers twice the metaclass’ constructors in Python 3.1 or less.

If your metaclass has some non-idempotent side-effect (such as registration of classes), then this would lead to unwanted double registration of the class:

>>> class BaseMeta(type):
...     classes = []
...     def __new__(cls, name, bases, attrs):
...         res = super(BaseMeta, cls).__new__(cls, name, bases, attrs)
...         cls.classes.append(res)   # <-- side effect
...         return res

>>> class Base(metaclass(BaseMeta)):
...     pass

>>> class SubType(BaseMeta):
...     pass

>>> class Egg(metaclass(SubType), Base):   # <-- metaclass first
...     pass

>>> Egg.__base__ is Base   # <-- but the base is Base
True

>>> len(BaseMeta.classes) == 2
True

>>> class Spam(Base, metaclass(SubType)):
...     'Like "Egg" but it will be registered twice in Python 2.x.'

In this case the registration of Spam ocurred twice:

>>> BaseMeta.classes  
[<class Base>, <class Egg>, <class Spam>, <class Spam>]

Bases, however, are just fine:

>>> Spam.__bases__ == (Base, )
True

New in version 1.7.1: Now are accepted atypical meta-classes, for example functions or any callable with the same arguments as those that type accepts (class name, tuple of base classes, attributes mapping).

All functions implemented in module types in Python 3 but not in Python 2.

xoutil.eight.types.MemberDescriptorType

alias of member_descriptor

class xoutil.eight.types.SimpleNamespace(**kwargs)[source]

A simple attribute-based namespace.

SimpleNamespace(**kwargs)

class xoutil.eight.types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)[source]

Route attribute access on a class to __getattr__().

This is a descriptor, used to define attributes that act differently when accessed through an instance and through a class. Instance access remains normal, but access to an attribute through a class will be routed to the class’s __getattr__() method; this is done by raising AttributeError.

This allows one to have properties active on an instance, and have virtual attributes on the class with the same name (see Enum for an example).

New in version 1.5.5.

Note

The class Enum mentioned has not yet been backported.

Note

In Python 3.4+ this is an alias to types.DynamicClassAttribute.

xoutil.eight.types.new_class(name, bases=(), kwds=None, exec_body=None)[source]

Create a class object dynamically using the appropriate metaclass.

xoutil.eight.types.prepare_class(name, bases=(), kwds=None)[source]

Call the __prepare__ method of the appropriate metaclass.

Returns (metaclass, namespace, kwds) as a 3-tuple

metaclass is the appropriate metaclass namespace is the prepared class namespace kwds is an updated copy of the passed in kwds argument with any ‘metaclass’ entry removed. If no kwds argument is passed in, this will be an empty dict.

Extensions to Python’s io module.

You may use it as drop-in replacement of io. Although we don’t document all items here. Refer to io documentation.

In Python 2, buil-int open() is different from io.open(); in Python 3 are the same function.

So, generated files with the built-in funtion in Python 2, can not be processed using abc types, for example:

f = open('test.rst')
assert isinstance(f, io.IOBase)

will fail in Python 2 and not in Python 3.

Another incompatibilities:

  • file type doesn’t exists in Python 3.
  • Python 2 instances created with io.StringIO:class`, or with io.open() using text mode, don’t accept str values, so it will be better to use any of the standards classes (StringIO.StringIO, cStringIO.StringIO or open() built-in).

New in version 1.7.0.

xoutil.eight.io.is_file_like(obj)[source]

Return if obj is a valid file type or not.

Compatibility exceptions between Python 2 and 3.

Python 2 defines an module named exceptions but Python 3 doesn’t. Also, there are some exception classes defined in Python 2 but not in Python 3.

xoutil.eight.exceptions.reraise(tp, value, tb=None)[source]

Raise an exception with a traceback.

In Python 2.7 this is the same as:

raise T, V, tb

In Python 3+ this is:

raise T(V).with_traceback(tb)

“”“Abstract Base Classes (ABCs) according to PEP 3119.”“”

Compatibility module between Python 2 and 3.

This module defines one symbol that is defined in Python 3 as a class:

class ABC(metaclass=ABCMeta):
“”“Helper class that provides a standard way to create an ABC using inheritance. “”” pass

In our case it’s defined as ABC = metaclass(ABCMeta), that is a little tricky (see xoutil.eight.meta.metaclass:func`).

abstractclassmethod is deprecated. Use classmethod with abstractmethod instead.

abstractstaticmethod is deprecated. Use staticmethod with abstractmethod instead.