kopia lustrzana https://github.com/micropython/micropython-lib
functools: Add functools.total_ordering.
This commit is the result of copying the total_ordering code and tests over from CPython v3.7.17. One test is disabled because it expects builtin objects to have attributes (__lt__, __gt__, etc.). Another test for compatibility with pickle is also disabled because pickle compatibility is currently broken. Bumped package version to 0.0.8. The functools code in CPython has the following credits: Written by Nick Coghlan <ncoghlan at gmail.com>, Raymond Hettinger <python at rcn.com>, and Łukasz Langa <lukasz at langa.pl>. Copyright (C) 2006-2013 Python Software Foundation. See C source code for _functools credits/copyright This work was donated by W Winfried Kretzschmar. Signed-off-by: W Winfried Kretzschmar <winni@warrenwk.com>pull/818/head
rodzic
224246531e
commit
61efc92e26
|
@ -0,0 +1,6 @@
|
||||||
|
FROM micropython/unix:v1.22
|
||||||
|
|
||||||
|
RUN micropython -m mip install unittest
|
||||||
|
RUN micropython -m mip install unittest-discover
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# functools
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
## Install docker
|
||||||
|
|
||||||
|
See https://docs.docker.com/engine/install/
|
||||||
|
|
||||||
|
## Build test environment
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t micropython-unittest .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run tests
|
||||||
|
|
||||||
|
All test files are designed to execute their own tests either as
|
||||||
|
assert statements or as a call to unittest.
|
||||||
|
|
||||||
|
```
|
||||||
|
for i in test_*.py; do docker run -v .:/code -ti --rm micropython-unittest micropython $i; done
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Some files are distributed under the Python Software Foundation license.
|
||||||
|
These files reference the Python Software Foundation license at the top of the file.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .total_ordering import total_ordering
|
||||||
|
|
||||||
|
from .functools import *
|
|
@ -26,3 +26,4 @@ def reduce(function, iterable, initializer=None):
|
||||||
for element in it:
|
for element in it:
|
||||||
value = function(value, element)
|
value = function(value, element)
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
"""total_ordering
|
||||||
|
|
||||||
|
This code was extracted from the CPython v3.7.17 implementation of Lib/functools.py
|
||||||
|
|
||||||
|
Written by Nick Coghlan <ncoghlan at gmail.com>,
|
||||||
|
Raymond Hettinger <python at rcn.com>,
|
||||||
|
and Łukasz Langa <lukasz at langa.pl>.
|
||||||
|
Copyright (C) 2006-2013 Python Software Foundation.
|
||||||
|
See C source code for _functools credits/copyright
|
||||||
|
|
||||||
|
This code is distributed under the Python Software License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
### total_ordering class decorator
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# The total ordering functions all invoke the root magic method directly
|
||||||
|
# rather than using the corresponding operator. This avoids possible
|
||||||
|
# infinite recursion that could occur when the operator dispatch logic
|
||||||
|
# detects a NotImplemented result and then calls a reflected method.
|
||||||
|
|
||||||
|
def _gt_from_lt(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
|
||||||
|
op_result = self.__lt__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result and self != other
|
||||||
|
|
||||||
|
def _le_from_lt(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
|
||||||
|
op_result = self.__lt__(other)
|
||||||
|
return op_result or self == other
|
||||||
|
|
||||||
|
def _ge_from_lt(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a >= b. Computed by @total_ordering from (not a < b).'
|
||||||
|
op_result = self.__lt__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result
|
||||||
|
|
||||||
|
def _ge_from_le(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
|
||||||
|
op_result = self.__le__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result or self == other
|
||||||
|
|
||||||
|
def _lt_from_le(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
|
||||||
|
op_result = self.__le__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return op_result and self != other
|
||||||
|
|
||||||
|
def _gt_from_le(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a > b. Computed by @total_ordering from (not a <= b).'
|
||||||
|
op_result = self.__le__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result
|
||||||
|
|
||||||
|
def _lt_from_gt(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
|
||||||
|
op_result = self.__gt__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result and self != other
|
||||||
|
|
||||||
|
def _ge_from_gt(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
|
||||||
|
op_result = self.__gt__(other)
|
||||||
|
return op_result or self == other
|
||||||
|
|
||||||
|
def _le_from_gt(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a <= b. Computed by @total_ordering from (not a > b).'
|
||||||
|
op_result = self.__gt__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result
|
||||||
|
|
||||||
|
def _le_from_ge(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
|
||||||
|
op_result = self.__ge__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result or self == other
|
||||||
|
|
||||||
|
def _gt_from_ge(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
|
||||||
|
op_result = self.__ge__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return op_result and self != other
|
||||||
|
|
||||||
|
def _lt_from_ge(self, other, NotImplemented=NotImplemented):
|
||||||
|
'Return a < b. Computed by @total_ordering from (not a >= b).'
|
||||||
|
op_result = self.__ge__(other)
|
||||||
|
if op_result is NotImplemented:
|
||||||
|
return op_result
|
||||||
|
return not op_result
|
||||||
|
|
||||||
|
_convert = {
|
||||||
|
'__lt__': [('__gt__', _gt_from_lt),
|
||||||
|
('__le__', _le_from_lt),
|
||||||
|
('__ge__', _ge_from_lt)],
|
||||||
|
'__le__': [('__ge__', _ge_from_le),
|
||||||
|
('__lt__', _lt_from_le),
|
||||||
|
('__gt__', _gt_from_le)],
|
||||||
|
'__gt__': [('__lt__', _lt_from_gt),
|
||||||
|
('__ge__', _ge_from_gt),
|
||||||
|
('__le__', _le_from_gt)],
|
||||||
|
'__ge__': [('__le__', _le_from_ge),
|
||||||
|
('__gt__', _gt_from_ge),
|
||||||
|
('__lt__', _lt_from_ge)]
|
||||||
|
}
|
||||||
|
|
||||||
|
def total_ordering(cls):
|
||||||
|
"""Class decorator that fills in missing ordering methods"""
|
||||||
|
# Find user-defined comparisons (not those inherited from object).
|
||||||
|
roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
|
||||||
|
if not roots:
|
||||||
|
raise ValueError('must define at least one ordering operation: < > <= >=')
|
||||||
|
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
|
||||||
|
for opname, opfunc in _convert[root]:
|
||||||
|
if opname not in roots:
|
||||||
|
# function objects have no attributes in micropython
|
||||||
|
# opfunc.__name__ = opname
|
||||||
|
setattr(cls, opname, opfunc)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
metadata(version="0.0.7")
|
metadata(version="0.0.8")
|
||||||
|
|
||||||
module("functools.py")
|
package("functools")
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
""" Test code for functools.total_ordering
|
||||||
|
|
||||||
|
Copyright © 2001-2023 Python Software Foundation. All rights reserved.
|
||||||
|
|
||||||
|
This code was extracted from CPython v3.7.17 Lib/test/test_functools.py
|
||||||
|
|
||||||
|
This code is distributed under the Python Software License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
class TestTotalOrdering(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_total_ordering_lt(self):
|
||||||
|
@functools.total_ordering
|
||||||
|
class A:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.value < other.value
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other.value
|
||||||
|
self.assertTrue(A(1) < A(2))
|
||||||
|
self.assertTrue(A(2) > A(1))
|
||||||
|
self.assertTrue(A(1) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(1))
|
||||||
|
self.assertTrue(A(2) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(2))
|
||||||
|
self.assertFalse(A(1) > A(2))
|
||||||
|
|
||||||
|
def test_total_ordering_le(self):
|
||||||
|
@functools.total_ordering
|
||||||
|
class A:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.value <= other.value
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other.value
|
||||||
|
self.assertTrue(A(1) < A(2))
|
||||||
|
self.assertTrue(A(2) > A(1))
|
||||||
|
self.assertTrue(A(1) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(1))
|
||||||
|
self.assertTrue(A(2) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(2))
|
||||||
|
self.assertFalse(A(1) >= A(2))
|
||||||
|
|
||||||
|
def test_total_ordering_gt(self):
|
||||||
|
@functools.total_ordering
|
||||||
|
class A:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other.value
|
||||||
|
self.assertTrue(A(1) < A(2))
|
||||||
|
self.assertTrue(A(2) > A(1))
|
||||||
|
self.assertTrue(A(1) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(1))
|
||||||
|
self.assertTrue(A(2) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(2))
|
||||||
|
self.assertFalse(A(2) < A(1))
|
||||||
|
|
||||||
|
def test_total_ordering_ge(self):
|
||||||
|
@functools.total_ordering
|
||||||
|
class A:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.value >= other.value
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other.value
|
||||||
|
self.assertTrue(A(1) < A(2))
|
||||||
|
self.assertTrue(A(2) > A(1))
|
||||||
|
self.assertTrue(A(1) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(1))
|
||||||
|
self.assertTrue(A(2) <= A(2))
|
||||||
|
self.assertTrue(A(2) >= A(2))
|
||||||
|
self.assertFalse(A(2) <= A(1))
|
||||||
|
|
||||||
|
# This test does not appear to work due to the lack of attributes on builtin types.
|
||||||
|
# This appears to lead to the comparison operators not being inherited by default.
|
||||||
|
# def test_total_ordering_no_overwrite(self):
|
||||||
|
# # new methods should not overwrite existing
|
||||||
|
# import sys
|
||||||
|
# @functools.total_ordering
|
||||||
|
# class A(int):
|
||||||
|
# pass
|
||||||
|
# self.assertTrue(A(1) < A(2))
|
||||||
|
# self.assertTrue(A(2) > A(1))
|
||||||
|
# self.assertTrue(A(1) <= A(2))
|
||||||
|
# self.assertTrue(A(2) >= A(1))
|
||||||
|
# self.assertTrue(A(2) <= A(2))
|
||||||
|
# self.assertTrue(A(2) >= A(2))
|
||||||
|
|
||||||
|
def test_no_operations_defined(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
@functools.total_ordering
|
||||||
|
class A:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_type_error_when_not_implemented(self):
|
||||||
|
# bug 10042; ensure stack overflow does not occur
|
||||||
|
# when decorated types return NotImplemented
|
||||||
|
@functools.total_ordering
|
||||||
|
class ImplementsLessThan:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ImplementsLessThan):
|
||||||
|
return self.value == other.value
|
||||||
|
return False
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, ImplementsLessThan):
|
||||||
|
return self.value < other.value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
@functools.total_ordering
|
||||||
|
class ImplementsGreaterThan:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ImplementsGreaterThan):
|
||||||
|
return self.value == other.value
|
||||||
|
return False
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, ImplementsGreaterThan):
|
||||||
|
return self.value > other.value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
@functools.total_ordering
|
||||||
|
class ImplementsLessThanEqualTo:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ImplementsLessThanEqualTo):
|
||||||
|
return self.value == other.value
|
||||||
|
return False
|
||||||
|
def __le__(self, other):
|
||||||
|
if isinstance(other, ImplementsLessThanEqualTo):
|
||||||
|
return self.value <= other.value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
@functools.total_ordering
|
||||||
|
class ImplementsGreaterThanEqualTo:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ImplementsGreaterThanEqualTo):
|
||||||
|
return self.value == other.value
|
||||||
|
return False
|
||||||
|
def __ge__(self, other):
|
||||||
|
if isinstance(other, ImplementsGreaterThanEqualTo):
|
||||||
|
return self.value >= other.value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
@functools.total_ordering
|
||||||
|
class ComparatorNotImplemented:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ComparatorNotImplemented):
|
||||||
|
return self.value == other.value
|
||||||
|
return False
|
||||||
|
def __lt__(self, other):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
with self.subTest("LT < 1"), self.assertRaises(TypeError):
|
||||||
|
ImplementsLessThan(-1) < 1
|
||||||
|
|
||||||
|
with self.subTest("LT < LE"), self.assertRaises(TypeError):
|
||||||
|
ImplementsLessThan(0) < ImplementsLessThanEqualTo(0)
|
||||||
|
|
||||||
|
with self.subTest("LT < GT"), self.assertRaises(TypeError):
|
||||||
|
ImplementsLessThan(1) < ImplementsGreaterThan(1)
|
||||||
|
|
||||||
|
with self.subTest("LE <= LT"), self.assertRaises(TypeError):
|
||||||
|
ImplementsLessThanEqualTo(2) <= ImplementsLessThan(2)
|
||||||
|
|
||||||
|
with self.subTest("LE <= GE"), self.assertRaises(TypeError):
|
||||||
|
ImplementsLessThanEqualTo(3) <= ImplementsGreaterThanEqualTo(3)
|
||||||
|
|
||||||
|
with self.subTest("GT > GE"), self.assertRaises(TypeError):
|
||||||
|
ImplementsGreaterThan(4) > ImplementsGreaterThanEqualTo(4)
|
||||||
|
|
||||||
|
with self.subTest("GT > LT"), self.assertRaises(TypeError):
|
||||||
|
ImplementsGreaterThan(5) > ImplementsLessThan(5)
|
||||||
|
|
||||||
|
with self.subTest("GE >= GT"), self.assertRaises(TypeError):
|
||||||
|
ImplementsGreaterThanEqualTo(6) >= ImplementsGreaterThan(6)
|
||||||
|
|
||||||
|
with self.subTest("GE >= LE"), self.assertRaises(TypeError):
|
||||||
|
ImplementsGreaterThanEqualTo(7) >= ImplementsLessThanEqualTo(7)
|
||||||
|
|
||||||
|
with self.subTest("GE when equal"):
|
||||||
|
a = ComparatorNotImplemented(8)
|
||||||
|
b = ComparatorNotImplemented(8)
|
||||||
|
self.assertEqual(a, b)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
a >= b
|
||||||
|
|
||||||
|
with self.subTest("LE when equal"):
|
||||||
|
a = ComparatorNotImplemented(9)
|
||||||
|
b = ComparatorNotImplemented(9)
|
||||||
|
self.assertEqual(a, b)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
a <= b
|
||||||
|
|
||||||
|
# Leaving pickle support for a later date
|
||||||
|
# def test_pickle(self):
|
||||||
|
# for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||||
|
# for name in '__lt__', '__gt__', '__le__', '__ge__':
|
||||||
|
# with self.subTest(method=name, proto=proto):
|
||||||
|
# method = getattr(Orderable_LT, name)
|
||||||
|
# method_copy = pickle.loads(pickle.dumps(method, proto))
|
||||||
|
# self.assertIs(method_copy, method)
|
||||||
|
|
||||||
|
# @functools.total_ordering
|
||||||
|
# class Orderable_LT:
|
||||||
|
# def __init__(self, value):
|
||||||
|
# self.value = value
|
||||||
|
# def __lt__(self, other):
|
||||||
|
# return self.value < other.value
|
||||||
|
# def __eq__(self, other):
|
||||||
|
# return self.value == other.value
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
Ładowanie…
Reference in New Issue