Skip to content

Commit 0d95a40

Browse files
committed
improve cmp function, add unittest
1 parent 28b3361 commit 0d95a40

File tree

3 files changed

+302
-1
lines changed

3 files changed

+302
-1
lines changed

Diff for: src/past/builtins/misc.py

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import unicode_literals
22

33
import inspect
4+
import math
5+
import numbers
46

57
from future.utils import PY2, PY3, exec_
68

@@ -29,8 +31,67 @@ def cmp(x, y):
2931
cmp(x, y) -> integer
3032
3133
Return negative if x<y, zero if x==y, positive if x>y.
34+
Python2 had looser comparison allowing cmp None and non Numerical types and collections.
35+
Try to match the old behavior
3236
"""
33-
return (x > y) - (x < y)
37+
if isinstance(x, set) and isinstance(y, set):
38+
raise TypeError('cannot compare sets using cmp()',)
39+
try:
40+
if isinstance(x, numbers.Number) and math.isnan(x):
41+
if not isinstance(y, numbers.Number):
42+
raise TypeError(f'cannot compare float("nan"), {type(y)} with cmp')
43+
if isinstance(y, int):
44+
return 1
45+
else:
46+
return -1
47+
if isinstance(y, numbers.Number) and math.isnan(y):
48+
if not isinstance(x, numbers.Number):
49+
raise TypeError(f'cannot compare {type(x)}, float("nan") with cmp')
50+
if isinstance(x, int):
51+
return -1
52+
else:
53+
return 1
54+
return (x > y) - (x < y)
55+
except TypeError:
56+
if x == y:
57+
return 0
58+
type_order = [
59+
type(None),
60+
numbers.Number,
61+
dict, list,
62+
set,
63+
(str, bytes),
64+
]
65+
x_type_index = y_type_index = None
66+
for i, type_match in enumerate(type_order):
67+
if isinstance(x, type_match):
68+
x_type_index = i
69+
if isinstance(y, type_match):
70+
y_type_index = i
71+
if cmp(x_type_index, y_type_index) == 0:
72+
if isinstance(x, bytes) and isinstance(y, str):
73+
return cmp(x.decode('ascii'), y)
74+
if isinstance(y, bytes) and isinstance(x, str):
75+
return cmp(x, y.decode('ascii'))
76+
elif isinstance(x, list):
77+
# if both arguments are lists take the comparison of the first non equal value
78+
for x_elem, y_elem in zip(x, y):
79+
elem_cmp_val = cmp(x_elem, y_elem)
80+
if elem_cmp_val != 0:
81+
return elem_cmp_val
82+
# if all elements are equal, return equal/0
83+
return 0
84+
elif isinstance(x, dict):
85+
if len(x) != len(y):
86+
return cmp(len(x), len(y))
87+
else:
88+
x_key = min(a for a in x if a not in y or x[a] != y[a])
89+
y_key = min(b for b in y if b not in x or x[b] != y[b])
90+
if x_key != y_key:
91+
return cmp(x_key, y_key)
92+
else:
93+
return cmp(x[x_key], y[y_key])
94+
return cmp(x_type_index, y_type_index)
3495

3596
from sys import intern
3697

Diff for: tests/test_past/test_misc.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Tests for the resurrected Py2-like cmp funtion
4+
"""
5+
6+
from __future__ import absolute_import, unicode_literals, print_function
7+
8+
import os.path
9+
import sys
10+
import traceback
11+
12+
from future.tests.base import unittest
13+
from past.builtins import cmp
14+
15+
_dir = os.path.dirname(os.path.abspath(__file__))
16+
sys.path.append(_dir)
17+
import test_values
18+
19+
20+
class TestCmp(unittest.TestCase):
21+
def test_cmp(self):
22+
for x, y, cmp_python2_value in test_values.cmp_python2_value:
23+
with self.subTest(x=x, y=y):
24+
try:
25+
past_cmp_value = cmp(x, y)
26+
except Exception as ex:
27+
past_cmp_value = traceback.format_exc().strip().split('\n')[-1]
28+
29+
self.assertEqual(cmp_python2_value, past_cmp_value,
30+
"expected result matching python2 __builtins__.cmp({x!r},{y!r}) "
31+
"== {cmp_python2_value} "
32+
"got past.builtins.cmp({x!r},{y!r}) "
33+
"== {past_cmp_value} "
34+
"".format(x=x, y=y, past_cmp_value=past_cmp_value,
35+
cmp_python2_value=cmp_python2_value))
36+
37+
38+
if __name__ == '__main__':
39+
unittest.main()

0 commit comments

Comments
 (0)