• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Python 2/3 compat layer."""
2
3from __future__ import print_function, division, absolute_import
4import sys
5
6
7__all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO',
8		'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
9		'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error',
10		'SimpleNamespace', 'zip', 'RecursionError']
11
12
13class Py23Error(NotImplementedError):
14	pass
15
16
17PY3 = sys.version_info[0] == 3
18PY2 = sys.version_info[0] == 2
19
20
21try:
22	basestring = basestring
23except NameError:
24	basestring = str
25
26try:
27	unicode = unicode
28except NameError:
29	unicode = str
30
31try:
32	unichr = unichr
33
34	if sys.maxunicode < 0x10FFFF:
35		# workarounds for Python 2 "narrow" builds with UCS2-only support.
36
37		_narrow_unichr = unichr
38
39		def unichr(i):
40			"""
41			Return the unicode character whose Unicode code is the integer 'i'.
42			The valid range is 0 to 0x10FFFF inclusive.
43
44			>>> _narrow_unichr(0xFFFF + 1)
45			Traceback (most recent call last):
46			  File "<stdin>", line 1, in ?
47			ValueError: unichr() arg not in range(0x10000) (narrow Python build)
48			>>> unichr(0xFFFF + 1) == u'\U00010000'
49			True
50			>>> unichr(1114111) == u'\U0010FFFF'
51			True
52			>>> unichr(0x10FFFF + 1)
53			Traceback (most recent call last):
54			  File "<stdin>", line 1, in ?
55			ValueError: unichr() arg not in range(0x110000)
56			"""
57			try:
58				return _narrow_unichr(i)
59			except ValueError:
60				try:
61					padded_hex_str = hex(i)[2:].zfill(8)
62					escape_str = "\\U" + padded_hex_str
63					return escape_str.decode("unicode-escape")
64				except UnicodeDecodeError:
65					raise ValueError('unichr() arg not in range(0x110000)')
66
67		import re
68		_unicode_escape_RE = re.compile(r'\\U[A-Fa-f0-9]{8}')
69
70		def byteord(c):
71			"""
72			Given a 8-bit or unicode character, return an integer representing the
73			Unicode code point of the character. If a unicode argument is given, the
74			character's code point must be in the range 0 to 0x10FFFF inclusive.
75
76			>>> ord(u'\U00010000')
77			Traceback (most recent call last):
78			  File "<stdin>", line 1, in ?
79			TypeError: ord() expected a character, but string of length 2 found
80			>>> byteord(u'\U00010000') == 0xFFFF + 1
81			True
82			>>> byteord(u'\U0010FFFF') == 1114111
83			True
84			"""
85			try:
86				return ord(c)
87			except TypeError as e:
88				try:
89					escape_str = c.encode('unicode-escape')
90					if not _unicode_escape_RE.match(escape_str):
91						raise
92					hex_str = escape_str[3:]
93					return int(hex_str, 16)
94				except:
95					raise TypeError(e)
96
97	else:
98		byteord = ord
99	bytechr = chr
100
101except NameError:
102	unichr = chr
103	def bytechr(n):
104		return bytes([n])
105	def byteord(c):
106		return c if isinstance(c, int) else ord(c)
107
108
109# the 'io' module provides the same I/O interface on both 2 and 3.
110# here we define an alias of io.StringIO to disambiguate it eternally...
111from io import BytesIO
112from io import StringIO as UnicodeIO
113try:
114	# in python 2, by 'StringIO' we still mean a stream of *byte* strings
115	from StringIO import StringIO
116except ImportError:
117	# in Python 3, we mean instead a stream of *unicode* strings
118	StringIO = UnicodeIO
119
120
121def strjoin(iterable, joiner=''):
122	return tostr(joiner).join(iterable)
123
124def tobytes(s, encoding='ascii', errors='strict'):
125	if not isinstance(s, bytes):
126		return s.encode(encoding, errors)
127	else:
128		return s
129def tounicode(s, encoding='ascii', errors='strict'):
130	if not isinstance(s, unicode):
131		return s.decode(encoding, errors)
132	else:
133		return s
134
135if str == bytes:
136	class Tag(str):
137		def tobytes(self):
138			if isinstance(self, bytes):
139				return self
140			else:
141				return self.encode('latin1')
142
143	tostr = tobytes
144
145	bytesjoin = strjoin
146else:
147	class Tag(str):
148
149		@staticmethod
150		def transcode(blob):
151			if isinstance(blob, bytes):
152				blob = blob.decode('latin-1')
153			return blob
154
155		def __new__(self, content):
156			return str.__new__(self, self.transcode(content))
157		def __ne__(self, other):
158			return not self.__eq__(other)
159		def __eq__(self, other):
160			return str.__eq__(self, self.transcode(other))
161
162		def __hash__(self):
163			return str.__hash__(self)
164
165		def tobytes(self):
166			return self.encode('latin-1')
167
168	tostr = tounicode
169
170	def bytesjoin(iterable, joiner=b''):
171		return tobytes(joiner).join(tobytes(item) for item in iterable)
172
173
174import os
175import io as _io
176
177try:
178	from msvcrt import setmode as _setmode
179except ImportError:
180	_setmode = None  # only available on the Windows platform
181
182
183def open(file, mode='r', buffering=-1, encoding=None, errors=None,
184		 newline=None, closefd=True, opener=None):
185	""" Wrapper around `io.open` that bridges the differences between Python 2
186	and Python 3's built-in `open` functions. In Python 2, `io.open` is a
187	backport of Python 3's `open`, whereas in Python 3, it is an alias of the
188	built-in `open` function.
189
190	One difference is that the 'opener' keyword argument is only supported in
191	Python 3. Here we pass the value of 'opener' only when it is not None.
192	This causes Python 2 to raise TypeError, complaining about the number of
193	expected arguments, so it must be avoided in py2 or py2-3 contexts.
194
195	Another difference between 2 and 3, this time on Windows, has to do with
196	opening files by name or by file descriptor.
197
198	On the Windows C runtime, the 'O_BINARY' flag is defined which disables
199	the newlines translation ('\r\n' <=> '\n') when reading/writing files.
200	On both Python 2 and 3 this flag is always set when opening files by name.
201	This way, the newlines translation at the MSVCRT level doesn't interfere
202	with the Python io module's own newlines translation.
203
204	However, when opening files via fd, on Python 2 the fd is simply copied,
205	regardless of whether it has the 'O_BINARY' flag set or not.
206	This becomes a problem in the case of stdout, stdin, and stderr, because on
207	Windows these are opened in text mode by default (ie. don't have the
208	O_BINARY flag set).
209
210	On Python 3, this issue has been fixed, and all fds are now opened in
211	binary mode on Windows, including standard streams. Similarly here, I use
212	the `_setmode` function to ensure that integer file descriptors are
213	O_BINARY'ed before I pass them on to io.open.
214
215	For more info, see: https://bugs.python.org/issue10841
216	"""
217	if isinstance(file, int):
218		# the 'file' argument is an integer file descriptor
219		fd = file
220		if fd < 0:
221			raise ValueError('negative file descriptor')
222		if _setmode:
223			# `_setmode` function sets the line-end translation and returns the
224			# value of the previous mode. AFAIK there's no `_getmode`, so to
225			# check if the previous mode already had the bit set, I fist need
226			# to duplicate the file descriptor, set the binary flag on the copy
227			# and check the returned value.
228			fdcopy = os.dup(fd)
229			current_mode = _setmode(fdcopy, os.O_BINARY)
230			if not (current_mode & os.O_BINARY):
231				# the binary mode was not set: use the file descriptor's copy
232				file = fdcopy
233				if closefd:
234					# close the original file descriptor
235					os.close(fd)
236				else:
237					# ensure the copy is closed when the file object is closed
238					closefd = True
239			else:
240				# original file descriptor already had binary flag, close copy
241				os.close(fdcopy)
242
243	if opener is not None:
244		# "opener" is not supported on Python 2, use it at your own risk!
245		return _io.open(
246			file, mode, buffering, encoding, errors, newline, closefd,
247			opener=opener)
248	else:
249		return _io.open(
250			file, mode, buffering, encoding, errors, newline, closefd)
251
252
253# always use iterator for 'range' and 'zip' on both py 2 and 3
254try:
255	range = xrange
256except NameError:
257	range = range
258
259def xrange(*args, **kwargs):
260	raise Py23Error("'xrange' is not defined. Use 'range' instead.")
261
262try:
263	from itertools import izip as zip
264except ImportError:
265	zip = zip
266
267
268import math as _math
269
270try:
271	isclose = _math.isclose
272except AttributeError:
273	# math.isclose() was only added in Python 3.5
274
275	_isinf = _math.isinf
276	_fabs = _math.fabs
277
278	def isclose(a, b, rel_tol=1e-09, abs_tol=0):
279		"""
280		Python 2 implementation of Python 3.5 math.isclose()
281		https://hg.python.org/cpython/file/v3.5.2/Modules/mathmodule.c#l1993
282		"""
283		# sanity check on the inputs
284		if rel_tol < 0 or abs_tol < 0:
285			raise ValueError("tolerances must be non-negative")
286		# short circuit exact equality -- needed to catch two infinities of
287		# the same sign. And perhaps speeds things up a bit sometimes.
288		if a == b:
289			return True
290		# This catches the case of two infinities of opposite sign, or
291		# one infinity and one finite number. Two infinities of opposite
292		# sign would otherwise have an infinite relative tolerance.
293		# Two infinities of the same sign are caught by the equality check
294		# above.
295		if _isinf(a) or _isinf(b):
296			return False
297		# Cast to float to allow decimal.Decimal arguments
298		if not isinstance(a, float):
299			a = float(a)
300		if not isinstance(b, float):
301			b = float(b)
302		# now do the regular computation
303		# this is essentially the "weak" test from the Boost library
304		diff = _fabs(b - a)
305		result = ((diff <= _fabs(rel_tol * a)) or
306				  (diff <= _fabs(rel_tol * b)) or
307				  (diff <= abs_tol))
308		return result
309
310
311try:
312	_isfinite = _math.isfinite  # Python >= 3.2
313except AttributeError:
314	_isfinite = None
315	_isnan = _math.isnan
316	_isinf = _math.isinf
317
318
319def isfinite(f):
320	"""
321	>>> isfinite(0.0)
322	True
323	>>> isfinite(-0.1)
324	True
325	>>> isfinite(1e10)
326	True
327	>>> isfinite(float("nan"))
328	False
329	>>> isfinite(float("+inf"))
330	False
331	>>> isfinite(float("-inf"))
332	False
333	"""
334	if _isfinite is not None:
335		return _isfinite(f)
336	else:
337		return not (_isnan(f) or _isinf(f))
338
339
340import decimal as _decimal
341
342if PY3:
343	def round2(number, ndigits=None):
344		"""
345		Implementation of Python 2 built-in round() function.
346
347		Rounds a number to a given precision in decimal digits (default
348		0 digits). The result is a floating point number. Values are rounded
349		to the closest multiple of 10 to the power minus ndigits; if two
350		multiples are equally close, rounding is done away from 0.
351
352		ndigits may be negative.
353
354		See Python 2 documentation:
355		https://docs.python.org/2/library/functions.html?highlight=round#round
356		"""
357		if ndigits is None:
358			ndigits = 0
359
360		if ndigits < 0:
361			exponent = 10 ** (-ndigits)
362			quotient, remainder = divmod(number, exponent)
363			if remainder >= exponent//2 and number >= 0:
364				quotient += 1
365			return float(quotient * exponent)
366		else:
367			exponent = _decimal.Decimal('10') ** (-ndigits)
368
369			d = _decimal.Decimal.from_float(number).quantize(
370				exponent, rounding=_decimal.ROUND_HALF_UP)
371
372			return float(d)
373
374	if sys.version_info[:2] >= (3, 6):
375		# in Python 3.6, 'round3' is an alias to the built-in 'round'
376		round = round3 = round
377	else:
378		# in Python3 < 3.6 we need work around the inconsistent behavior of
379		# built-in round(), whereby floats accept a second None argument,
380		# while integers raise TypeError. See https://bugs.python.org/issue27936
381		_round = round
382
383		def round3(number, ndigits=None):
384			return _round(number) if ndigits is None else _round(number, ndigits)
385
386		round = round3
387
388else:
389	# in Python 2, 'round2' is an alias to the built-in 'round' and
390	# 'round' is shadowed by 'round3'
391	round2 = round
392
393	def round3(number, ndigits=None):
394		"""
395		Implementation of Python 3 built-in round() function.
396
397		Rounds a number to a given precision in decimal digits (default
398		0 digits). This returns an int when ndigits is omitted or is None,
399		otherwise the same type as the number.
400
401		Values are rounded to the closest multiple of 10 to the power minus
402		ndigits; if two multiples are equally close, rounding is done toward
403		the even choice (aka "Banker's Rounding"). For example, both round(0.5)
404		and round(-0.5) are 0, and round(1.5) is 2.
405
406		ndigits may be negative.
407
408		See Python 3 documentation:
409		https://docs.python.org/3/library/functions.html?highlight=round#round
410
411		Derived from python-future:
412		https://github.com/PythonCharmers/python-future/blob/master/src/future/builtins/newround.py
413		"""
414		if ndigits is None:
415			ndigits = 0
416			# return an int when called with one argument
417			totype = int
418			# shortcut if already an integer, or a float with no decimal digits
419			inumber = totype(number)
420			if inumber == number:
421				return inumber
422		else:
423			# return the same type as the number, when called with two arguments
424			totype = type(number)
425
426		m = number * (10 ** ndigits)
427		# if number is half-way between two multiples, and the mutliple that is
428		# closer to zero is even, we use the (slow) pure-Python implementation
429		if isclose(m % 1, .5) and int(m) % 2 == 0:
430			if ndigits < 0:
431				exponent = 10 ** (-ndigits)
432				quotient, remainder = divmod(number, exponent)
433				half = exponent//2
434				if remainder > half or (remainder == half and quotient % 2 != 0):
435					quotient += 1
436				d = quotient * exponent
437			else:
438				exponent = _decimal.Decimal('10') ** (-ndigits) if ndigits != 0 else 1
439
440				d = _decimal.Decimal.from_float(number).quantize(
441					exponent, rounding=_decimal.ROUND_HALF_EVEN)
442		else:
443			# else we use the built-in round() as it produces the same results
444			d = round2(number, ndigits)
445
446		return totype(d)
447
448	round = round3
449
450
451try:
452	from types import SimpleNamespace
453except ImportError:
454	class SimpleNamespace(object):
455		"""
456		A backport of Python 3.3's ``types.SimpleNamespace``.
457		"""
458		def __init__(self, **kwargs):
459			self.__dict__.update(kwargs)
460
461		def __repr__(self):
462			keys = sorted(self.__dict__)
463			items = ("{0}={1!r}".format(k, self.__dict__[k]) for k in keys)
464			return "{0}({1})".format(type(self).__name__, ", ".join(items))
465
466		def __eq__(self, other):
467			return self.__dict__ == other.__dict__
468
469
470if sys.version_info[:2] > (3, 4):
471	from contextlib import redirect_stdout, redirect_stderr
472else:
473	# `redirect_stdout` was added with python3.4, while `redirect_stderr`
474	# with python3.5. For simplicity, I redefine both for any versions
475	# less than or equal to 3.4.
476	# The code below is copied from:
477	# https://github.com/python/cpython/blob/57161aa/Lib/contextlib.py
478
479	class _RedirectStream(object):
480
481		_stream = None
482
483		def __init__(self, new_target):
484			self._new_target = new_target
485			# We use a list of old targets to make this CM re-entrant
486			self._old_targets = []
487
488		def __enter__(self):
489			self._old_targets.append(getattr(sys, self._stream))
490			setattr(sys, self._stream, self._new_target)
491			return self._new_target
492
493		def __exit__(self, exctype, excinst, exctb):
494			setattr(sys, self._stream, self._old_targets.pop())
495
496
497	class redirect_stdout(_RedirectStream):
498		"""Context manager for temporarily redirecting stdout to another file.
499			# How to send help() to stderr
500			with redirect_stdout(sys.stderr):
501				help(dir)
502			# How to write help() to a file
503			with open('help.txt', 'w') as f:
504				with redirect_stdout(f):
505					help(pow)
506		"""
507
508		_stream = "stdout"
509
510
511	class redirect_stderr(_RedirectStream):
512		"""Context manager for temporarily redirecting stderr to another file."""
513
514		_stream = "stderr"
515
516
517try:
518	RecursionError = RecursionError
519except NameError:
520	RecursionError = RuntimeError
521
522
523if __name__ == "__main__":
524	import doctest, sys
525	sys.exit(doctest.testmod().failed)
526