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