Skip to content

Commit

Permalink
improve cmp function, add unittest
Browse files Browse the repository at this point in the history
  • Loading branch information
rtaycher committed Aug 31, 2020
1 parent 28b3361 commit 0d95a40
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 1 deletion.
63 changes: 62 additions & 1 deletion src/past/builtins/misc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import unicode_literals

import inspect
import math
import numbers

from future.utils import PY2, PY3, exec_

Expand Down Expand Up @@ -29,8 +31,67 @@ def cmp(x, y):
cmp(x, y) -> integer
Return negative if x<y, zero if x==y, positive if x>y.
Python2 had looser comparison allowing cmp None and non Numerical types and collections.
Try to match the old behavior
"""
return (x > y) - (x < y)
if isinstance(x, set) and isinstance(y, set):
raise TypeError('cannot compare sets using cmp()',)
try:
if isinstance(x, numbers.Number) and math.isnan(x):
if not isinstance(y, numbers.Number):
raise TypeError(f'cannot compare float("nan"), {type(y)} with cmp')
if isinstance(y, int):
return 1
else:
return -1
if isinstance(y, numbers.Number) and math.isnan(y):
if not isinstance(x, numbers.Number):
raise TypeError(f'cannot compare {type(x)}, float("nan") with cmp')
if isinstance(x, int):
return -1
else:
return 1
return (x > y) - (x < y)
except TypeError:
if x == y:
return 0
type_order = [
type(None),
numbers.Number,
dict, list,
set,
(str, bytes),
]
x_type_index = y_type_index = None
for i, type_match in enumerate(type_order):
if isinstance(x, type_match):
x_type_index = i
if isinstance(y, type_match):
y_type_index = i
if cmp(x_type_index, y_type_index) == 0:
if isinstance(x, bytes) and isinstance(y, str):
return cmp(x.decode('ascii'), y)
if isinstance(y, bytes) and isinstance(x, str):
return cmp(x, y.decode('ascii'))
elif isinstance(x, list):
# if both arguments are lists take the comparison of the first non equal value
for x_elem, y_elem in zip(x, y):
elem_cmp_val = cmp(x_elem, y_elem)
if elem_cmp_val != 0:
return elem_cmp_val
# if all elements are equal, return equal/0
return 0
elif isinstance(x, dict):
if len(x) != len(y):
return cmp(len(x), len(y))
else:
x_key = min(a for a in x if a not in y or x[a] != y[a])
y_key = min(b for b in y if b not in x or x[b] != y[b])
if x_key != y_key:
return cmp(x_key, y_key)
else:
return cmp(x[x_key], y[y_key])
return cmp(x_type_index, y_type_index)

from sys import intern

Expand Down
39 changes: 39 additions & 0 deletions tests/test_past/test_misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
"""
Tests for the resurrected Py2-like cmp funtion
"""

from __future__ import absolute_import, unicode_literals, print_function

import os.path
import sys
import traceback

from future.tests.base import unittest
from past.builtins import cmp

_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(_dir)
import test_values


class TestCmp(unittest.TestCase):
def test_cmp(self):
for x, y, cmp_python2_value in test_values.cmp_python2_value:
with self.subTest(x=x, y=y):
try:
past_cmp_value = cmp(x, y)
except Exception as ex:
past_cmp_value = traceback.format_exc().strip().split('\n')[-1]

self.assertEqual(cmp_python2_value, past_cmp_value,
"expected result matching python2 __builtins__.cmp({x!r},{y!r}) "
"== {cmp_python2_value} "
"got past.builtins.cmp({x!r},{y!r}) "
"== {past_cmp_value} "
"".format(x=x, y=y, past_cmp_value=past_cmp_value,
cmp_python2_value=cmp_python2_value))


if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit 0d95a40

Please sign in to comment.