view 2.00/compat.py @ 31:fe1463e7e24d

Clean up try-except clauses More try-except-else goodness (with the stress on else). Bug fix: compat.py now creates zip_longest in Python 2.5 instead of raising an exception.
author Oleg Oshmyan <chortos@inbox.lv>
date Thu, 25 Nov 2010 00:03:29 +0000
parents dc4be35d17e0
children 8fec38b0dd6e
line wrap: on
line source

#! /usr/bin/env python
# Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>

# A compatibility layer for Python 2.5+. This is what lets test.py
# run on all versions of Python starting with 2.5, including Python 3.

# A few notes regarding some compatibility-driven peculiarities
# in the use of the language that can be seen in all modules:
#
# * Except statements never specify target; instead, when needed,
#   the exception is taken from sys.exc_info(). Blame the incompatible
#   syntaxes of the except clause in Python 2.5 and Python 3 and the lack
#   of preprocessor macros in Python of any version ;P.
#
# * Keyword-only parameters are never used, even for parameters
#   that should never be given in as arguments. The reason is
#   the laziness of some Python developers who have failed to finish
#   implementing them in Python 2 even though they had several years
#   of time and multiple version releases to sneak them in.
#
# * Abstract classes are only implemented for Python 2.6 and 2.7.
#   ABC's require the abc module and the specification of metaclasses,
#   but in Python 2.5, the abc module does not exist, while in Python 3,
#   metaclasses are specified using a syntax totally incompatible
#   with Python 2 and not usable conditionally via exec() and such
#   because it is a detail of the syntax of the class statement itself.

try:
	import builtins
except ImportError:
	import __builtin__ as builtins

__all__ = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'items',
           'keys', 'values', 'zip_longest', 'callable',
           'ABCMeta', 'abstractmethod', 'CompatBuiltins')

try:
	# Python 3
	exec('say = print')
except SyntaxError:
	try:
		# Python 2.6/2.7
		# An alternative is exec('from __future__ import print_function; say = print');
		# if problems arise with the current line, one should try replacing it
		# with this one with the future import before abandoning the idea altogether
		say = getattr(builtins, 'print')
	except Exception:
		# Python 2.5
		import sys
		# This should fully emulate the print function of Python 2.6 in Python 2.3+
		# The error messages are taken from Python 2.6
		# The name bindings at the bottom of this file are in effect
		def saytypeerror(value, name):
			return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__)))
		def say(*values, **kwargs):
			sep  = kwargs.pop('sep' , None)
			end  = kwargs.pop('end' , None)
			file = kwargs.pop('file', None)
			if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0])
			if sep  is None: sep  = ' '
			if end  is None: end  = '\n'
			if file is None: file = sys.stdout
			if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep')
			if not isinstance(end, basestring): raise saytypeerror(end, 'end')
			file.write(sep.join(map(str, values)) + end)

def import_urllib():
	try:
		# Python 3
		import urllib.request
		return urllib.request, lambda url: urllib.request.urlopen(url).read().decode()
	except ImportError:
		# Python 2
		import urllib
		return urllib, lambda url: urllib.urlopen(url).read()

try:
	from abc import ABCMeta, abstractmethod
except ImportError:
	ABCMeta, abstractmethod = None, lambda x: x

# In all of the following, the try clause is for Python 2 and the except
# clause is for Python 3. More checks are performed than needed
# for standard builds of Python to ensure as much as possible works
# on custom builds.
try:
	basestring = basestring
except NameError:
	basestring = str

try:
	range = xrange
except NameError:
	range = range

try:
	callable = callable
except NameError:
	callable = lambda obj: hasattr(obj, '__call__')

try:
	from itertools import imap as map
except ImportError:
	map = map

try:
	from itertools import izip as zip
except ImportError:
	zip = zip

try:
	from itertools import ifilter as filter
except ImportError:
	filter = filter

items = dict.iteritems if hasattr(dict, 'iteritems') else dict.items
keys = dict.iterkeys if hasattr(dict, 'iterkeys') else dict.keys
values = dict.itervalues if hasattr(dict, 'itervalues') else dict.values

try:
	# Python 3
	from itertools import zip_longest
except ImportError:
	try:
		# Python 2.6/2.7
		from itertools import izip_longest as zip_longest
	except ImportError:
		# Python 2.5
		from itertools import chain, repeat
		# Adapted from the documentation of itertools.izip_longest
		def zip_longest(*args, **kwargs):
			fillvalue = kwargs.get('fillvalue')
			def sentinel(counter=([fillvalue]*(len(args)-1)).pop):
				yield counter()
			fillers = repeat(fillvalue)
			iters = [chain(it, sentinel(), fillers) for it in args]
			try:
				for tup in zip(*iters):
					yield tup
			except IndexError:
				pass

# Automatically import * from this module into testconf.py's
class CompatBuiltins(object):
	__slots__ = 'originals'
	def __init__(self):
		self.originals = {}
	def __enter__(self):
		g = globals()
		for name in __all__:
			if hasattr(builtins, name):
				self.originals[name] = getattr(builtins, name)
			setattr(builtins, name, g[name])
	def __exit__(self, exc_type, exc_val, exc_tb):
		for name in self.originals:
			setattr(builtins, name, self.originals[name])

# Support simple testconf.py's written for test.py 1.x
builtins.xrange = range