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:
|
||||
value = function(value, element)
|
||||
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