Skip to content

Commit

Permalink
functools: Add functools.total_ordering.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
winni2k committed Mar 19, 2024
1 parent 2242465 commit 13285a4
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 1 deletion.
6 changes: 6 additions & 0 deletions python-stdlib/functools/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM micropython/unix:v1.22

RUN micropython -m mip install unittest
RUN micropython -m mip install unittest-discover


28 changes: 28 additions & 0 deletions python-stdlib/functools/README.md
Original file line number Diff line number Diff line change
@@ -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.

3 changes: 3 additions & 0 deletions python-stdlib/functools/functools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .total_ordering import total_ordering

from .functools import *
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ def reduce(function, iterable, initializer=None):
for element in it:
value = function(value, element)
return value

133 changes: 133 additions & 0 deletions python-stdlib/functools/functools/total_ordering.py
Original file line number Diff line number Diff line change
@@ -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


2 changes: 1 addition & 1 deletion python-stdlib/functools/manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
metadata(version="0.0.7")
metadata(version="0.0.8")

module("functools.py")

0 comments on commit 13285a4

Please sign in to comment.