| """Python 2/3 compat layer.""" |
| |
| from __future__ import print_function, division, absolute_import |
| import sys |
| |
| |
| __all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO', |
| 'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr', |
| 'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error', |
| 'SimpleNamespace', 'zip', 'RecursionError'] |
| |
| |
| class Py23Error(NotImplementedError): |
| pass |
| |
| |
| PY3 = sys.version_info[0] == 3 |
| PY2 = sys.version_info[0] == 2 |
| |
| |
| try: |
| basestring = basestring |
| except NameError: |
| basestring = str |
| |
| try: |
| unicode = unicode |
| except NameError: |
| unicode = str |
| |
| try: |
| unichr = unichr |
| |
| if sys.maxunicode < 0x10FFFF: |
| # workarounds for Python 2 "narrow" builds with UCS2-only support. |
| |
| _narrow_unichr = unichr |
| |
| def unichr(i): |
| """ |
| Return the unicode character whose Unicode code is the integer 'i'. |
| The valid range is 0 to 0x10FFFF inclusive. |
| |
| >>> _narrow_unichr(0xFFFF + 1) |
| Traceback (most recent call last): |
| File "<stdin>", line 1, in ? |
| ValueError: unichr() arg not in range(0x10000) (narrow Python build) |
| >>> unichr(0xFFFF + 1) == u'\U00010000' |
| True |
| >>> unichr(1114111) == u'\U0010FFFF' |
| True |
| >>> unichr(0x10FFFF + 1) |
| Traceback (most recent call last): |
| File "<stdin>", line 1, in ? |
| ValueError: unichr() arg not in range(0x110000) |
| """ |
| try: |
| return _narrow_unichr(i) |
| except ValueError: |
| try: |
| padded_hex_str = hex(i)[2:].zfill(8) |
| escape_str = "\\U" + padded_hex_str |
| return escape_str.decode("unicode-escape") |
| except UnicodeDecodeError: |
| raise ValueError('unichr() arg not in range(0x110000)') |
| |
| import re |
| _unicode_escape_RE = re.compile(r'\\U[A-Fa-f0-9]{8}') |
| |
| def byteord(c): |
| """ |
| Given a 8-bit or unicode character, return an integer representing the |
| Unicode code point of the character. If a unicode argument is given, the |
| character's code point must be in the range 0 to 0x10FFFF inclusive. |
| |
| >>> ord(u'\U00010000') |
| Traceback (most recent call last): |
| File "<stdin>", line 1, in ? |
| TypeError: ord() expected a character, but string of length 2 found |
| >>> byteord(u'\U00010000') == 0xFFFF + 1 |
| True |
| >>> byteord(u'\U0010FFFF') == 1114111 |
| True |
| """ |
| try: |
| return ord(c) |
| except TypeError as e: |
| try: |
| escape_str = c.encode('unicode-escape') |
| if not _unicode_escape_RE.match(escape_str): |
| raise |
| hex_str = escape_str[3:] |
| return int(hex_str, 16) |
| except: |
| raise TypeError(e) |
| |
| else: |
| byteord = ord |
| bytechr = chr |
| |
| except NameError: |
| unichr = chr |
| def bytechr(n): |
| return bytes([n]) |
| def byteord(c): |
| return c if isinstance(c, int) else ord(c) |
| |
| |
| # the 'io' module provides the same I/O interface on both 2 and 3. |
| # here we define an alias of io.StringIO to disambiguate it eternally... |
| from io import BytesIO |
| from io import StringIO as UnicodeIO |
| try: |
| # in python 2, by 'StringIO' we still mean a stream of *byte* strings |
| from StringIO import StringIO |
| except ImportError: |
| # in Python 3, we mean instead a stream of *unicode* strings |
| StringIO = UnicodeIO |
| |
| |
| def strjoin(iterable, joiner=''): |
| return tostr(joiner).join(iterable) |
| |
| def tobytes(s, encoding='ascii', errors='strict'): |
| if not isinstance(s, bytes): |
| return s.encode(encoding, errors) |
| else: |
| return s |
| def tounicode(s, encoding='ascii', errors='strict'): |
| if not isinstance(s, unicode): |
| return s.decode(encoding, errors) |
| else: |
| return s |
| |
| if str == bytes: |
| class Tag(str): |
| def tobytes(self): |
| if isinstance(self, bytes): |
| return self |
| else: |
| return self.encode('latin1') |
| |
| tostr = tobytes |
| |
| bytesjoin = strjoin |
| else: |
| class Tag(str): |
| |
| @staticmethod |
| def transcode(blob): |
| if isinstance(blob, bytes): |
| blob = blob.decode('latin-1') |
| return blob |
| |
| def __new__(self, content): |
| return str.__new__(self, self.transcode(content)) |
| def __ne__(self, other): |
| return not self.__eq__(other) |
| def __eq__(self, other): |
| return str.__eq__(self, self.transcode(other)) |
| |
| def __hash__(self): |
| return str.__hash__(self) |
| |
| def tobytes(self): |
| return self.encode('latin-1') |
| |
| tostr = tounicode |
| |
| def bytesjoin(iterable, joiner=b''): |
| return tobytes(joiner).join(tobytes(item) for item in iterable) |
| |
| |
| import os |
| import io as _io |
| |
| try: |
| from msvcrt import setmode as _setmode |
| except ImportError: |
| _setmode = None # only available on the Windows platform |
| |
| |
| def open(file, mode='r', buffering=-1, encoding=None, errors=None, |
| newline=None, closefd=True, opener=None): |
| """ Wrapper around `io.open` that bridges the differences between Python 2 |
| and Python 3's built-in `open` functions. In Python 2, `io.open` is a |
| backport of Python 3's `open`, whereas in Python 3, it is an alias of the |
| built-in `open` function. |
| |
| One difference is that the 'opener' keyword argument is only supported in |
| Python 3. Here we pass the value of 'opener' only when it is not None. |
| This causes Python 2 to raise TypeError, complaining about the number of |
| expected arguments, so it must be avoided in py2 or py2-3 contexts. |
| |
| Another difference between 2 and 3, this time on Windows, has to do with |
| opening files by name or by file descriptor. |
| |
| On the Windows C runtime, the 'O_BINARY' flag is defined which disables |
| the newlines translation ('\r\n' <=> '\n') when reading/writing files. |
| On both Python 2 and 3 this flag is always set when opening files by name. |
| This way, the newlines translation at the MSVCRT level doesn't interfere |
| with the Python io module's own newlines translation. |
| |
| However, when opening files via fd, on Python 2 the fd is simply copied, |
| regardless of whether it has the 'O_BINARY' flag set or not. |
| This becomes a problem in the case of stdout, stdin, and stderr, because on |
| Windows these are opened in text mode by default (ie. don't have the |
| O_BINARY flag set). |
| |
| On Python 3, this issue has been fixed, and all fds are now opened in |
| binary mode on Windows, including standard streams. Similarly here, I use |
| the `_setmode` function to ensure that integer file descriptors are |
| O_BINARY'ed before I pass them on to io.open. |
| |
| For more info, see: https://bugs.python.org/issue10841 |
| """ |
| if isinstance(file, int): |
| # the 'file' argument is an integer file descriptor |
| fd = file |
| if fd < 0: |
| raise ValueError('negative file descriptor') |
| if _setmode: |
| # `_setmode` function sets the line-end translation and returns the |
| # value of the previous mode. AFAIK there's no `_getmode`, so to |
| # check if the previous mode already had the bit set, I fist need |
| # to duplicate the file descriptor, set the binary flag on the copy |
| # and check the returned value. |
| fdcopy = os.dup(fd) |
| current_mode = _setmode(fdcopy, os.O_BINARY) |
| if not (current_mode & os.O_BINARY): |
| # the binary mode was not set: use the file descriptor's copy |
| file = fdcopy |
| if closefd: |
| # close the original file descriptor |
| os.close(fd) |
| else: |
| # ensure the copy is closed when the file object is closed |
| closefd = True |
| else: |
| # original file descriptor already had binary flag, close copy |
| os.close(fdcopy) |
| |
| if opener is not None: |
| # "opener" is not supported on Python 2, use it at your own risk! |
| return _io.open( |
| file, mode, buffering, encoding, errors, newline, closefd, |
| opener=opener) |
| else: |
| return _io.open( |
| file, mode, buffering, encoding, errors, newline, closefd) |
| |
| |
| # always use iterator for 'range' and 'zip' on both py 2 and 3 |
| try: |
| range = xrange |
| except NameError: |
| range = range |
| |
| def xrange(*args, **kwargs): |
| raise Py23Error("'xrange' is not defined. Use 'range' instead.") |
| |
| try: |
| from itertools import izip as zip |
| except ImportError: |
| zip = zip |
| |
| |
| import math as _math |
| |
| try: |
| isclose = _math.isclose |
| except AttributeError: |
| # math.isclose() was only added in Python 3.5 |
| |
| _isinf = _math.isinf |
| _fabs = _math.fabs |
| |
| def isclose(a, b, rel_tol=1e-09, abs_tol=0): |
| """ |
| Python 2 implementation of Python 3.5 math.isclose() |
| https://hg.python.org/cpython/file/v3.5.2/Modules/mathmodule.c#l1993 |
| """ |
| # sanity check on the inputs |
| if rel_tol < 0 or abs_tol < 0: |
| raise ValueError("tolerances must be non-negative") |
| # short circuit exact equality -- needed to catch two infinities of |
| # the same sign. And perhaps speeds things up a bit sometimes. |
| if a == b: |
| return True |
| # This catches the case of two infinities of opposite sign, or |
| # one infinity and one finite number. Two infinities of opposite |
| # sign would otherwise have an infinite relative tolerance. |
| # Two infinities of the same sign are caught by the equality check |
| # above. |
| if _isinf(a) or _isinf(b): |
| return False |
| # Cast to float to allow decimal.Decimal arguments |
| if not isinstance(a, float): |
| a = float(a) |
| if not isinstance(b, float): |
| b = float(b) |
| # now do the regular computation |
| # this is essentially the "weak" test from the Boost library |
| diff = _fabs(b - a) |
| result = ((diff <= _fabs(rel_tol * a)) or |
| (diff <= _fabs(rel_tol * b)) or |
| (diff <= abs_tol)) |
| return result |
| |
| |
| try: |
| _isfinite = _math.isfinite # Python >= 3.2 |
| except AttributeError: |
| _isfinite = None |
| _isnan = _math.isnan |
| _isinf = _math.isinf |
| |
| |
| def isfinite(f): |
| """ |
| >>> isfinite(0.0) |
| True |
| >>> isfinite(-0.1) |
| True |
| >>> isfinite(1e10) |
| True |
| >>> isfinite(float("nan")) |
| False |
| >>> isfinite(float("+inf")) |
| False |
| >>> isfinite(float("-inf")) |
| False |
| """ |
| if _isfinite is not None: |
| return _isfinite(f) |
| else: |
| return not (_isnan(f) or _isinf(f)) |
| |
| |
| import decimal as _decimal |
| |
| if PY3: |
| def round2(number, ndigits=None): |
| """ |
| Implementation of Python 2 built-in round() function. |
| |
| Rounds a number to a given precision in decimal digits (default |
| 0 digits). The result is a floating point number. Values are rounded |
| to the closest multiple of 10 to the power minus ndigits; if two |
| multiples are equally close, rounding is done away from 0. |
| |
| ndigits may be negative. |
| |
| See Python 2 documentation: |
| https://docs.python.org/2/library/functions.html?highlight=round#round |
| """ |
| if ndigits is None: |
| ndigits = 0 |
| |
| if ndigits < 0: |
| exponent = 10 ** (-ndigits) |
| quotient, remainder = divmod(number, exponent) |
| if remainder >= exponent//2 and number >= 0: |
| quotient += 1 |
| return float(quotient * exponent) |
| else: |
| exponent = _decimal.Decimal('10') ** (-ndigits) |
| |
| d = _decimal.Decimal.from_float(number).quantize( |
| exponent, rounding=_decimal.ROUND_HALF_UP) |
| |
| return float(d) |
| |
| if sys.version_info[:2] >= (3, 6): |
| # in Python 3.6, 'round3' is an alias to the built-in 'round' |
| round = round3 = round |
| else: |
| # in Python3 < 3.6 we need work around the inconsistent behavior of |
| # built-in round(), whereby floats accept a second None argument, |
| # while integers raise TypeError. See https://bugs.python.org/issue27936 |
| _round = round |
| |
| def round3(number, ndigits=None): |
| return _round(number) if ndigits is None else _round(number, ndigits) |
| |
| round = round3 |
| |
| else: |
| # in Python 2, 'round2' is an alias to the built-in 'round' and |
| # 'round' is shadowed by 'round3' |
| round2 = round |
| |
| def round3(number, ndigits=None): |
| """ |
| Implementation of Python 3 built-in round() function. |
| |
| Rounds a number to a given precision in decimal digits (default |
| 0 digits). This returns an int when ndigits is omitted or is None, |
| otherwise the same type as the number. |
| |
| Values are rounded to the closest multiple of 10 to the power minus |
| ndigits; if two multiples are equally close, rounding is done toward |
| the even choice (aka "Banker's Rounding"). For example, both round(0.5) |
| and round(-0.5) are 0, and round(1.5) is 2. |
| |
| ndigits may be negative. |
| |
| See Python 3 documentation: |
| https://docs.python.org/3/library/functions.html?highlight=round#round |
| |
| Derived from python-future: |
| https://github.com/PythonCharmers/python-future/blob/master/src/future/builtins/newround.py |
| """ |
| if ndigits is None: |
| ndigits = 0 |
| # return an int when called with one argument |
| totype = int |
| # shortcut if already an integer, or a float with no decimal digits |
| inumber = totype(number) |
| if inumber == number: |
| return inumber |
| else: |
| # return the same type as the number, when called with two arguments |
| totype = type(number) |
| |
| m = number * (10 ** ndigits) |
| # if number is half-way between two multiples, and the mutliple that is |
| # closer to zero is even, we use the (slow) pure-Python implementation |
| if isclose(m % 1, .5) and int(m) % 2 == 0: |
| if ndigits < 0: |
| exponent = 10 ** (-ndigits) |
| quotient, remainder = divmod(number, exponent) |
| half = exponent//2 |
| if remainder > half or (remainder == half and quotient % 2 != 0): |
| quotient += 1 |
| d = quotient * exponent |
| else: |
| exponent = _decimal.Decimal('10') ** (-ndigits) if ndigits != 0 else 1 |
| |
| d = _decimal.Decimal.from_float(number).quantize( |
| exponent, rounding=_decimal.ROUND_HALF_EVEN) |
| else: |
| # else we use the built-in round() as it produces the same results |
| d = round2(number, ndigits) |
| |
| return totype(d) |
| |
| round = round3 |
| |
| |
| try: |
| from types import SimpleNamespace |
| except ImportError: |
| class SimpleNamespace(object): |
| """ |
| A backport of Python 3.3's ``types.SimpleNamespace``. |
| """ |
| def __init__(self, **kwargs): |
| self.__dict__.update(kwargs) |
| |
| def __repr__(self): |
| keys = sorted(self.__dict__) |
| items = ("{0}={1!r}".format(k, self.__dict__[k]) for k in keys) |
| return "{0}({1})".format(type(self).__name__, ", ".join(items)) |
| |
| def __eq__(self, other): |
| return self.__dict__ == other.__dict__ |
| |
| |
| if sys.version_info[:2] > (3, 4): |
| from contextlib import redirect_stdout, redirect_stderr |
| else: |
| # `redirect_stdout` was added with python3.4, while `redirect_stderr` |
| # with python3.5. For simplicity, I redefine both for any versions |
| # less than or equal to 3.4. |
| # The code below is copied from: |
| # https://github.com/python/cpython/blob/57161aa/Lib/contextlib.py |
| |
| class _RedirectStream(object): |
| |
| _stream = None |
| |
| def __init__(self, new_target): |
| self._new_target = new_target |
| # We use a list of old targets to make this CM re-entrant |
| self._old_targets = [] |
| |
| def __enter__(self): |
| self._old_targets.append(getattr(sys, self._stream)) |
| setattr(sys, self._stream, self._new_target) |
| return self._new_target |
| |
| def __exit__(self, exctype, excinst, exctb): |
| setattr(sys, self._stream, self._old_targets.pop()) |
| |
| |
| class redirect_stdout(_RedirectStream): |
| """Context manager for temporarily redirecting stdout to another file. |
| # How to send help() to stderr |
| with redirect_stdout(sys.stderr): |
| help(dir) |
| # How to write help() to a file |
| with open('help.txt', 'w') as f: |
| with redirect_stdout(f): |
| help(pow) |
| """ |
| |
| _stream = "stdout" |
| |
| |
| class redirect_stderr(_RedirectStream): |
| """Context manager for temporarily redirecting stderr to another file.""" |
| |
| _stream = "stderr" |
| |
| |
| try: |
| RecursionError = RecursionError |
| except NameError: |
| RecursionError = RuntimeError |
| |
| |
| if __name__ == "__main__": |
| import doctest, sys |
| sys.exit(doctest.testmod().failed) |