2013-11-27 14:36:57 -05:00
|
|
|
"""Python 2/3 compat layer."""
|
|
|
|
|
2014-01-14 15:07:50 +08:00
|
|
|
from __future__ import print_function, division, absolute_import
|
2015-05-08 19:28:42 +01:00
|
|
|
import sys
|
2013-11-27 17:27:45 -05:00
|
|
|
|
2016-04-07 09:21:05 +01:00
|
|
|
|
|
|
|
__all__ = ['basestring', 'unicode', 'unichr', 'byteord', 'bytechr', 'BytesIO',
|
|
|
|
'StringIO', 'UnicodeIO', 'strjoin', 'bytesjoin', 'tobytes', 'tostr',
|
2018-01-22 17:44:59 -08:00
|
|
|
'tounicode', 'Tag', 'open', 'range', 'xrange', 'round', 'Py23Error',
|
|
|
|
'SimpleNamespace']
|
2016-05-04 18:31:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Py23Error(NotImplementedError):
|
|
|
|
pass
|
2016-04-07 09:21:05 +01:00
|
|
|
|
|
|
|
|
2016-09-13 07:12:44 +01:00
|
|
|
PY3 = sys.version_info[0] == 3
|
|
|
|
PY2 = sys.version_info[0] == 2
|
|
|
|
|
|
|
|
|
2013-11-27 14:36:57 -05:00
|
|
|
try:
|
[py23] fix ImportError when trying to import `unichr`, `basestring` or `unicode` when already defined
When one does `from fontTools.misc.py23 import *`, everything seems to work fine.
However, linters will complain when one uses the asterisk to import all names from a module, since they can't detect when names are left undefined -- asterisks are greedy and will eat all names.
If one avoids the asterik and attempts to import explicitly, like in `from fontTools.misc.py23 import basestring`, the problem then is that, if `py23` does not re-define the name -- e.g. under python2 `basestring` or `unicode` are built-ins -- then the import statement raises `ImportError`.
The same happens for the `unichr` function on a "wide" Python 2 build (in which `sys.maxunicode == 0x10FFFF`).
Now, to work around this, we need to re-assign those built-ins to their very same names. This may look silly, but at least it works.
2015-11-23 12:02:12 +00:00
|
|
|
basestring = basestring
|
2013-11-27 14:36:57 -05:00
|
|
|
except NameError:
|
|
|
|
basestring = str
|
|
|
|
|
|
|
|
try:
|
[py23] fix ImportError when trying to import `unichr`, `basestring` or `unicode` when already defined
When one does `from fontTools.misc.py23 import *`, everything seems to work fine.
However, linters will complain when one uses the asterisk to import all names from a module, since they can't detect when names are left undefined -- asterisks are greedy and will eat all names.
If one avoids the asterik and attempts to import explicitly, like in `from fontTools.misc.py23 import basestring`, the problem then is that, if `py23` does not re-define the name -- e.g. under python2 `basestring` or `unicode` are built-ins -- then the import statement raises `ImportError`.
The same happens for the `unichr` function on a "wide" Python 2 build (in which `sys.maxunicode == 0x10FFFF`).
Now, to work around this, we need to re-assign those built-ins to their very same names. This may look silly, but at least it works.
2015-11-23 12:02:12 +00:00
|
|
|
unicode = unicode
|
2013-11-27 14:36:57 -05:00
|
|
|
except NameError:
|
|
|
|
unicode = str
|
|
|
|
|
|
|
|
try:
|
[py23] fix ImportError when trying to import `unichr`, `basestring` or `unicode` when already defined
When one does `from fontTools.misc.py23 import *`, everything seems to work fine.
However, linters will complain when one uses the asterisk to import all names from a module, since they can't detect when names are left undefined -- asterisks are greedy and will eat all names.
If one avoids the asterik and attempts to import explicitly, like in `from fontTools.misc.py23 import basestring`, the problem then is that, if `py23` does not re-define the name -- e.g. under python2 `basestring` or `unicode` are built-ins -- then the import statement raises `ImportError`.
The same happens for the `unichr` function on a "wide" Python 2 build (in which `sys.maxunicode == 0x10FFFF`).
Now, to work around this, we need to re-assign those built-ins to their very same names. This may look silly, but at least it works.
2015-11-23 12:02:12 +00:00
|
|
|
unichr = unichr
|
2015-05-08 19:28:42 +01:00
|
|
|
|
|
|
|
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
|
2013-11-27 14:36:57 -05:00
|
|
|
bytechr = chr
|
2015-05-08 19:28:42 +01:00
|
|
|
|
|
|
|
except NameError:
|
2013-11-27 14:36:57 -05:00
|
|
|
unichr = chr
|
|
|
|
def bytechr(n):
|
|
|
|
return bytes([n])
|
2013-11-27 18:13:48 -05:00
|
|
|
def byteord(c):
|
2013-11-27 21:13:05 -05:00
|
|
|
return c if isinstance(c, int) else ord(c)
|
2013-11-27 14:36:57 -05:00
|
|
|
|
2015-08-07 15:44:58 +01:00
|
|
|
|
|
|
|
# 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
|
2013-11-27 14:36:57 -05:00
|
|
|
try:
|
2015-08-07 15:44:58 +01:00
|
|
|
# in python 2, by 'StringIO' we still mean a stream of *byte* strings
|
2013-12-04 04:11:06 -05:00
|
|
|
from StringIO import StringIO
|
2013-11-27 14:36:57 -05:00
|
|
|
except ImportError:
|
2015-08-07 15:44:58 +01:00
|
|
|
# in Python 3, we mean instead a stream of *unicode* strings
|
|
|
|
StringIO = UnicodeIO
|
|
|
|
|
2013-11-27 16:44:53 -05:00
|
|
|
|
2015-04-14 19:07:34 -07:00
|
|
|
def strjoin(iterable, joiner=''):
|
|
|
|
return tostr(joiner).join(iterable)
|
2014-07-21 13:19:53 -04:00
|
|
|
|
2015-04-16 17:09:49 -07:00
|
|
|
def tobytes(s, encoding='ascii', errors='strict'):
|
2014-07-21 13:19:53 -04:00
|
|
|
if not isinstance(s, bytes):
|
2015-04-16 17:09:49 -07:00
|
|
|
return s.encode(encoding, errors)
|
2014-07-21 13:19:53 -04:00
|
|
|
else:
|
|
|
|
return s
|
2015-04-16 17:09:49 -07:00
|
|
|
def tounicode(s, encoding='ascii', errors='strict'):
|
2014-07-21 13:19:53 -04:00
|
|
|
if not isinstance(s, unicode):
|
2015-04-16 17:09:49 -07:00
|
|
|
return s.decode(encoding, errors)
|
2014-07-21 13:19:53 -04:00
|
|
|
else:
|
|
|
|
return s
|
|
|
|
|
2013-11-27 16:44:53 -05:00
|
|
|
if str == bytes:
|
|
|
|
class Tag(str):
|
|
|
|
def tobytes(self):
|
|
|
|
if isinstance(self, bytes):
|
|
|
|
return self
|
|
|
|
else:
|
2013-11-28 06:46:59 -05:00
|
|
|
return self.encode('latin1')
|
2013-11-27 19:51:59 -05:00
|
|
|
|
2014-07-21 13:19:53 -04:00
|
|
|
tostr = tobytes
|
2013-11-27 21:09:03 -05:00
|
|
|
|
2013-11-27 21:17:35 -05:00
|
|
|
bytesjoin = strjoin
|
2013-11-27 16:44:53 -05:00
|
|
|
else:
|
|
|
|
class Tag(str):
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def transcode(blob):
|
2018-04-03 15:40:54 -04:00
|
|
|
if isinstance(blob, bytes):
|
2013-11-27 16:44:53 -05:00
|
|
|
blob = blob.decode('latin-1')
|
|
|
|
return blob
|
|
|
|
|
|
|
|
def __new__(self, content):
|
|
|
|
return str.__new__(self, self.transcode(content))
|
2013-12-06 22:25:48 -05:00
|
|
|
def __ne__(self, other):
|
|
|
|
return not self.__eq__(other)
|
2013-11-27 16:44:53 -05:00
|
|
|
def __eq__(self, other):
|
2018-04-03 15:40:54 -04:00
|
|
|
return str.__eq__(self, self.transcode(other))
|
2013-11-27 16:44:53 -05:00
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return str.__hash__(self)
|
|
|
|
|
|
|
|
def tobytes(self):
|
|
|
|
return self.encode('latin-1')
|
2013-11-27 19:51:59 -05:00
|
|
|
|
2014-07-21 13:19:53 -04:00
|
|
|
tostr = tounicode
|
2013-11-27 21:09:03 -05:00
|
|
|
|
2015-04-14 19:07:34 -07:00
|
|
|
def bytesjoin(iterable, joiner=b''):
|
|
|
|
return tobytes(joiner).join(tobytes(item) for item in iterable)
|
2015-05-08 19:28:42 +01:00
|
|
|
|
|
|
|
|
2015-12-11 17:21:35 +00:00
|
|
|
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.
|
2016-12-14 10:03:49 +00:00
|
|
|
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.
|
2015-12-11 17:21:35 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2016-05-04 18:31:13 +01:00
|
|
|
# always use iterator for 'range' on both py 2 and 3
|
2016-05-04 17:11:54 +01:00
|
|
|
try:
|
|
|
|
range = xrange
|
|
|
|
except NameError:
|
|
|
|
range = range
|
|
|
|
|
2016-05-04 18:31:13 +01:00
|
|
|
def xrange(*args, **kwargs):
|
|
|
|
raise Py23Error("'xrange' is not defined. Use 'range' instead.")
|
|
|
|
|
2016-05-04 17:11:54 +01:00
|
|
|
|
2016-12-01 17:12:20 +00:00
|
|
|
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
|
|
|
|
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-12-01 17:43:35 +00:00
|
|
|
import decimal as _decimal
|
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
if PY3:
|
|
|
|
def round2(number, ndigits=None):
|
|
|
|
"""
|
|
|
|
Implementation of Python 2 built-in round() function.
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
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.
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
ndigits may be negative.
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
See Python 2 documentation:
|
|
|
|
https://docs.python.org/2/library/functions.html?highlight=round#round
|
|
|
|
"""
|
|
|
|
if ndigits is None:
|
|
|
|
ndigits = 0
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
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)
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
d = _decimal.Decimal.from_float(number).quantize(
|
|
|
|
exponent, rounding=_decimal.ROUND_HALF_UP)
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
return float(d)
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-12-02 11:58:43 +00:00
|
|
|
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
|
2016-09-13 00:22:14 +01:00
|
|
|
|
2016-09-13 07:12:44 +01:00
|
|
|
else:
|
2016-12-01 17:12:20 +00:00
|
|
|
# in Python 2, 'round2' is an alias to the built-in 'round' and
|
|
|
|
# 'round' is shadowed by 'round3'
|
|
|
|
round2 = round
|
|
|
|
|
2016-11-30 12:43:07 +00:00
|
|
|
def round3(number, ndigits=None):
|
|
|
|
"""
|
|
|
|
Implementation of Python 3 built-in round() function.
|
|
|
|
|
|
|
|
Rounds a number to a given precision in decimal digits (default
|
2016-12-02 12:06:25 +00:00
|
|
|
0 digits). This returns an int when ndigits is omitted or is None,
|
2016-11-30 12:43:07 +00:00
|
|
|
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)
|
|
|
|
|
2016-12-01 17:12:20 +00:00
|
|
|
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
|
2016-11-30 12:43:07 +00:00
|
|
|
|
2016-12-01 17:12:20 +00:00
|
|
|
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)
|
2016-11-30 12:43:07 +00:00
|
|
|
|
|
|
|
return totype(d)
|
|
|
|
|
|
|
|
round = round3
|
2016-09-13 07:12:44 +01:00
|
|
|
|
|
|
|
|
2016-02-01 13:10:42 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
class _Logger(logging.Logger):
|
|
|
|
""" Add support for 'lastResort' handler introduced in Python 3.2. """
|
|
|
|
|
|
|
|
def callHandlers(self, record):
|
|
|
|
# this is the same as Python 3.5's logging.Logger.callHandlers
|
|
|
|
c = self
|
|
|
|
found = 0
|
|
|
|
while c:
|
|
|
|
for hdlr in c.handlers:
|
|
|
|
found = found + 1
|
|
|
|
if record.levelno >= hdlr.level:
|
|
|
|
hdlr.handle(record)
|
|
|
|
if not c.propagate:
|
|
|
|
c = None # break out
|
|
|
|
else:
|
|
|
|
c = c.parent
|
|
|
|
if (found == 0):
|
|
|
|
if logging.lastResort:
|
|
|
|
if record.levelno >= logging.lastResort.level:
|
|
|
|
logging.lastResort.handle(record)
|
|
|
|
elif logging.raiseExceptions and not self.manager.emittedNoHandlerWarning:
|
|
|
|
sys.stderr.write("No handlers could be found for logger"
|
|
|
|
" \"%s\"\n" % self.name)
|
|
|
|
self.manager.emittedNoHandlerWarning = True
|
|
|
|
|
|
|
|
|
|
|
|
class _StderrHandler(logging.StreamHandler):
|
|
|
|
""" This class is like a StreamHandler using sys.stderr, but always uses
|
|
|
|
whatever sys.stderr is currently set to rather than the value of
|
|
|
|
sys.stderr at handler construction time.
|
|
|
|
"""
|
|
|
|
def __init__(self, level=logging.NOTSET):
|
|
|
|
"""
|
|
|
|
Initialize the handler.
|
|
|
|
"""
|
|
|
|
logging.Handler.__init__(self, level)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def stream(self):
|
2017-01-28 14:18:29 +00:00
|
|
|
# the try/execept avoids failures during interpreter shutdown, when
|
|
|
|
# globals are set to None
|
|
|
|
try:
|
|
|
|
return sys.stderr
|
|
|
|
except AttributeError:
|
|
|
|
return __import__('sys').stderr
|
2016-02-01 13:10:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
if not hasattr(logging, 'lastResort'):
|
|
|
|
# for Python pre-3.2, we need to define the "last resort" handler used when
|
|
|
|
# clients don't explicitly configure logging (in Python 3.2 and above this is
|
|
|
|
# already defined). The handler prints the bare message to sys.stderr, only
|
|
|
|
# for events of severity WARNING or greater.
|
|
|
|
# To obtain the pre-3.2 behaviour, you can set logging.lastResort to None.
|
|
|
|
# https://docs.python.org/3.5/howto/logging.html#what-happens-if-no-configuration-is-provided
|
|
|
|
logging.lastResort = _StderrHandler(logging.WARNING)
|
|
|
|
# Also, we need to set the Logger class to one which supports the last resort
|
|
|
|
# handler. All new loggers instantiated after this call will use the custom
|
|
|
|
# logger class (the already existing ones, like the 'root' logger, will not)
|
|
|
|
logging.setLoggerClass(_Logger)
|
|
|
|
|
|
|
|
|
2017-04-10 15:03:51 +01:00
|
|
|
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__
|
|
|
|
|
|
|
|
|
2017-11-07 12:08:04 +00:00
|
|
|
if sys.version_info[:2] > (3, 4):
|
2017-11-07 11:54:28 +00:00
|
|
|
from contextlib import redirect_stdout, redirect_stderr
|
|
|
|
else:
|
2017-11-07 12:08:04 +00:00
|
|
|
# `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.
|
2017-11-07 11:54:28 +00:00
|
|
|
# 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"
|
|
|
|
|
|
|
|
|
2015-05-08 19:28:42 +01:00
|
|
|
if __name__ == "__main__":
|
|
|
|
import doctest, sys
|
|
|
|
sys.exit(doctest.testmod().failed)
|