mirror of https://github.com/python/cpython
Reverted the addition of a NORMALIZE_NUMBERS option, per Tim Peter's
request. Tim says that "correct 'fuzzy' comparison of floats cannot be automated." (The motivation behind adding the new option was verifying interactive examples in Python's latex documentation; several such examples use numbers that don't print consistently on different platforms.)
This commit is contained in:
parent
4cda01e260
commit
7d88a58e85
|
@ -581,17 +581,6 @@ TypeError: object doesn't support item assignment
|
|||
|
||||
\end{datadesc}
|
||||
|
||||
\begin{datadesc}{NORMALIZE_NUMBERS}
|
||||
When specified, number literals in the expected output will match
|
||||
corresponding number literals in the actual output if their values
|
||||
are equal (to ten digits of precision). For example, \code{1.1}
|
||||
will match \code{1.1000000000000001}; and \code{1L} will match
|
||||
\code{1} and \code{1.0}. Currently, \constant{NORMALIZE_NUMBERS}
|
||||
can fail to normalize numbers when used in conjunction with
|
||||
ellipsis. In particular, if an ellipsis marker matches one or
|
||||
more numbers, then number normalization is not supported.
|
||||
\end{datadesc}
|
||||
|
||||
\begin{datadesc}{COMPARISON_FLAGS}
|
||||
A bitmask or'ing together all the comparison flags above.
|
||||
\end{datadesc}
|
||||
|
@ -713,7 +702,7 @@ can be useful.
|
|||
|
||||
\versionchanged[Constants \constant{DONT_ACCEPT_BLANKLINE},
|
||||
\constant{NORMALIZE_WHITESPACE}, \constant{ELLIPSIS},
|
||||
\constant{IGNORE_EXCEPTION_DETAIL}, \constant{NORMALIZE_NUMBERS},
|
||||
\constant{IGNORE_EXCEPTION_DETAIL},
|
||||
\constant{REPORT_UDIFF}, \constant{REPORT_CDIFF},
|
||||
\constant{REPORT_NDIFF}, \constant{REPORT_ONLY_FIRST_FAILURE},
|
||||
\constant{COMPARISON_FLAGS} and \constant{REPORTING_FLAGS}
|
||||
|
@ -751,7 +740,6 @@ in any particular order, so a test like
|
|||
|
||||
% Hey! What happened to Monty Python examples?
|
||||
% Tim: ask Guido -- it's his example!
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> foo()
|
||||
{"Hermione": "hippogryph", "Harry": "broomstick"}
|
||||
|
@ -759,7 +747,6 @@ in any particular order, so a test like
|
|||
|
||||
is vulnerable! One workaround is to do
|
||||
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> foo() == {"Hermione": "hippogryph", "Harry": "broomstick"}
|
||||
True
|
||||
|
@ -767,7 +754,6 @@ True
|
|||
|
||||
instead. Another is to do
|
||||
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> d = foo().items()
|
||||
>>> d.sort()
|
||||
|
@ -779,7 +765,6 @@ There are others, but you get the idea.
|
|||
|
||||
Another bad idea is to print things that embed an object address, like
|
||||
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> id(1.0) # certain to fail some of the time
|
||||
7948648
|
||||
|
@ -791,7 +776,6 @@ Another bad idea is to print things that embed an object address, like
|
|||
The \constant{ELLIPSIS} directive gives a nice approach for the last
|
||||
example:
|
||||
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> C() #doctest: +ELLIPSIS
|
||||
<__main__.C instance at 0x...>
|
||||
|
@ -801,7 +785,6 @@ Floating-point numbers are also subject to small output variations across
|
|||
platforms, because Python defers to the platform C library for float
|
||||
formatting, and C libraries vary widely in quality here.
|
||||
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> 1./7 # risky
|
||||
0.14285714285714285
|
||||
|
@ -1635,7 +1618,6 @@ Doctest provides several mechanisms for debugging doctest examples:
|
|||
|
||||
Then an interactive Python session may look like this:
|
||||
|
||||
% doctest: ignore
|
||||
\begin{verbatim}
|
||||
>>> import a, doctest
|
||||
>>> doctest.testmod(a)
|
||||
|
|
|
@ -55,7 +55,6 @@ __all__ = [
|
|||
'NORMALIZE_WHITESPACE',
|
||||
'ELLIPSIS',
|
||||
'IGNORE_EXCEPTION_DETAIL',
|
||||
'NORMALIZE_NUMBERS',
|
||||
'COMPARISON_FLAGS',
|
||||
'REPORT_UDIFF',
|
||||
'REPORT_CDIFF',
|
||||
|
@ -140,14 +139,12 @@ DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE')
|
|||
NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE')
|
||||
ELLIPSIS = register_optionflag('ELLIPSIS')
|
||||
IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL')
|
||||
NORMALIZE_NUMBERS = register_optionflag('NORMALIZE_NUMBERS')
|
||||
|
||||
COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 |
|
||||
DONT_ACCEPT_BLANKLINE |
|
||||
NORMALIZE_WHITESPACE |
|
||||
ELLIPSIS |
|
||||
IGNORE_EXCEPTION_DETAIL |
|
||||
NORMALIZE_NUMBERS)
|
||||
IGNORE_EXCEPTION_DETAIL)
|
||||
|
||||
REPORT_UDIFF = register_optionflag('REPORT_UDIFF')
|
||||
REPORT_CDIFF = register_optionflag('REPORT_CDIFF')
|
||||
|
@ -280,72 +277,6 @@ class _SpoofOut(StringIO):
|
|||
if hasattr(self, "softspace"):
|
||||
del self.softspace
|
||||
|
||||
# The number of digits of precision that must be equal for
|
||||
# NORMALIZE_NUMBERS to consider two numbers equal.
|
||||
_NORMALIZE_NUMBERS_PRECISION_THRESHOLD = 10
|
||||
|
||||
# A regular expression that matches Python number literals. This is
|
||||
# used by _normalize_numbers to look for numbers that should be
|
||||
# normalized.
|
||||
_NUMBER_LITERAL = re.compile(r'''
|
||||
(\d+[.]\d*(?:[eE][-+]?\d+)?[jJ]? | # float (w/ digits left of ".")
|
||||
[.]\d+(?:[eE][-+]?\d+)?[jJ]? | # float (no digits left of ".")
|
||||
\d+ (?:[eE][-+]?\d+) [jJ]? | # float (no ".", exponent only)
|
||||
\d [jJ] | # float (no ".", imaginary only)
|
||||
0[xX]\d+[lL]? | # hexint
|
||||
0[0-7]*[lL]? | # octint or zero
|
||||
\d+[lL]? ) # decint
|
||||
''', re.VERBOSE)
|
||||
|
||||
def _normalize_numbers(want, got):
|
||||
"""
|
||||
If all the numbers in `want` and `got` match (one-for-one), then
|
||||
return a new version of `got` with the exact number strings from
|
||||
`want` spliced in. Two numbers match if `str` of their float
|
||||
values are equal. (I.e., `x` matches `y` if
|
||||
`str(float(x))==str(float(y))`).
|
||||
"""
|
||||
want_pieces = _NUMBER_LITERAL.split(want)
|
||||
got_pieces = _NUMBER_LITERAL.split(got)
|
||||
|
||||
# If they don't have the same number of numbers, fail immediately.
|
||||
if len(want_pieces) != len(got_pieces):
|
||||
return got
|
||||
|
||||
# If any individual numbers don't match, then fail.
|
||||
for i in range(1, len(got_pieces), 2):
|
||||
w, g = eval(want_pieces[i]), eval(got_pieces[i])
|
||||
if not _numbers_match(w, g):
|
||||
return got
|
||||
|
||||
# Success; replace numbers in got w/ numbers from want.
|
||||
for i in range(1, len(got_pieces), 2):
|
||||
got_pieces[i] = want_pieces[i]
|
||||
return ''.join(got_pieces)
|
||||
|
||||
def _numbers_match(x, y):
|
||||
"""
|
||||
A helper function for _normalize_numbers, that returns true if the
|
||||
numbers `x` and `y` are close enough to match for NORMALIZE_NUMBERS.
|
||||
"""
|
||||
# Equal numbers match.
|
||||
if x == y:
|
||||
return True
|
||||
# Split up complex numbers into real & imag.
|
||||
if isinstance(x, complex):
|
||||
return (isinstance(y, complex) and
|
||||
_numbers_match(x.real, y.real) and
|
||||
_numbers_match(x.imag, y.imag))
|
||||
# If the signs are different, they don't match.
|
||||
if x*y < 0:
|
||||
return False
|
||||
# If one is zero and the other isn't, they don't match.
|
||||
if x==0 or y==0:
|
||||
return False
|
||||
# They're not exactly equal, but are they close enough?
|
||||
threshold = 10**-_NORMALIZE_NUMBERS_PRECISION_THRESHOLD
|
||||
return (abs(x-y) / min(abs(x), abs(y))) < threshold
|
||||
|
||||
# Worst-case linear-time ellipsis matching.
|
||||
def _ellipsis_match(want, got):
|
||||
"""
|
||||
|
@ -1572,13 +1503,6 @@ class OutputChecker:
|
|||
if got == want:
|
||||
return True
|
||||
|
||||
# This flag causes doctest to treat numbers that are within a
|
||||
# small threshold as if they are equal.
|
||||
if optionflags & NORMALIZE_NUMBERS:
|
||||
got = _normalize_numbers(want, got)
|
||||
if got == want:
|
||||
return True
|
||||
|
||||
# The ELLIPSIS flag says to let the sequence "..." in `want`
|
||||
# match any substring in `got`.
|
||||
if optionflags & ELLIPSIS:
|
||||
|
@ -1859,7 +1783,6 @@ def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None,
|
|||
NORMALIZE_WHITESPACE
|
||||
ELLIPSIS
|
||||
IGNORE_EXCEPTION_DETAIL
|
||||
NORMALIZE_NUMBERS
|
||||
REPORT_UDIFF
|
||||
REPORT_CDIFF
|
||||
REPORT_NDIFF
|
||||
|
@ -1982,7 +1905,6 @@ def testfile(filename, module_relative=True, name=None, package=None,
|
|||
NORMALIZE_WHITESPACE
|
||||
ELLIPSIS
|
||||
IGNORE_EXCEPTION_DETAIL
|
||||
NORMALIZE_NUMBERS
|
||||
REPORT_UDIFF
|
||||
REPORT_CDIFF
|
||||
REPORT_NDIFF
|
||||
|
|
|
@ -1032,107 +1032,6 @@ treated as equal:
|
|||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
|
||||
|
||||
The NORMALIZE_NUMBERS flag causes numbers that are equal (to
|
||||
approximately 10 decimal places) but formatted differently to match.
|
||||
|
||||
>>> def f(x): '''
|
||||
... Numbers will match if they are exactly equal:
|
||||
...
|
||||
... >>> print 1.1, 'intervening text', 1L # should match
|
||||
... 1.1 intervening text 1L
|
||||
... >>> print 1.0j, 22, 22.0, 1, 1e1 # should match
|
||||
... 1j 22.0 22 1 10.0
|
||||
...
|
||||
... Numbers will match if they are equal to 14 digits of
|
||||
... precision:
|
||||
...
|
||||
... >>> 2.00000000001 # should match
|
||||
... 1.99999999999
|
||||
... >>> 2.000000001 # should not match
|
||||
... 1.999999999
|
||||
... >>> 2.00000000001e10 # should match
|
||||
... 1.99999999999e10
|
||||
... >>> 2.000000001e10 # should not match
|
||||
... 1.999999999e10
|
||||
... '''
|
||||
|
||||
>>> # Without the flag:
|
||||
>>> test = doctest.DocTestFinder().find(f)[0]
|
||||
>>> doctest.DocTestRunner(verbose=False).run(test)
|
||||
... # doctest: +ELLIPSIS
|
||||
**********************************************************************
|
||||
File ..., line 4, in f
|
||||
Failed example:
|
||||
print 1.1, 'intervening text', 1L # should match
|
||||
Expected:
|
||||
1.1 intervening text 1L
|
||||
Got:
|
||||
1.1 intervening text 1
|
||||
**********************************************************************
|
||||
File ..., line 6, in f
|
||||
Failed example:
|
||||
print 1.0j, 22, 22.0, 1, 1e1 # should match
|
||||
Expected:
|
||||
1j 22.0 22 1 10.0
|
||||
Got:
|
||||
1j 22 22.0 1 10.0
|
||||
**********************************************************************
|
||||
File ..., line 12, in f
|
||||
Failed example:
|
||||
2.00000000001 # should match
|
||||
Expected:
|
||||
1.99999999999
|
||||
Got:
|
||||
2.00000000001
|
||||
**********************************************************************
|
||||
File ..., line 14, in f
|
||||
Failed example:
|
||||
2.000000001 # should not match
|
||||
Expected:
|
||||
1.999999999
|
||||
Got:
|
||||
2.0000000010000001
|
||||
**********************************************************************
|
||||
File ..., line 16, in f
|
||||
Failed example:
|
||||
2.00000000001e10 # should match
|
||||
Expected:
|
||||
1.99999999999e10
|
||||
Got:
|
||||
20000000000.099998
|
||||
**********************************************************************
|
||||
File ..., line 18, in f
|
||||
Failed example:
|
||||
2.000000001e10 # should not match
|
||||
Expected:
|
||||
1.999999999e10
|
||||
Got:
|
||||
20000000010.0
|
||||
(6, 6)
|
||||
|
||||
>>> # With the flag:
|
||||
>>> test = doctest.DocTestFinder().find(f)[0]
|
||||
>>> flags = doctest.NORMALIZE_NUMBERS
|
||||
>>> doctest.DocTestRunner(verbose=False, optionflags=flags).run(test)
|
||||
... # doctest: +ELLIPSIS
|
||||
**********************************************************************
|
||||
File ..., line 14, in f
|
||||
Failed example:
|
||||
2.000000001 # should not match
|
||||
Expected:
|
||||
1.999999999
|
||||
Got:
|
||||
2.0000000010000001
|
||||
**********************************************************************
|
||||
File ..., line 18, in f
|
||||
Failed example:
|
||||
2.000000001e10 # should not match
|
||||
Expected:
|
||||
1.999999999e10
|
||||
Got:
|
||||
20000000010.0
|
||||
(2, 6)
|
||||
|
||||
The ELLIPSIS flag causes ellipsis marker ("...") in the expected
|
||||
output to match any substring in the actual output:
|
||||
|
||||
|
|
Loading…
Reference in New Issue