_cmp.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. # SPDX-License-Identifier: MIT
  2. from __future__ import absolute_import, division, print_function
  3. import functools
  4. from ._compat import new_class
  5. from ._make import _make_ne
  6. _operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}
  7. def cmp_using(
  8. eq=None,
  9. lt=None,
  10. le=None,
  11. gt=None,
  12. ge=None,
  13. require_same_type=True,
  14. class_name="Comparable",
  15. ):
  16. """
  17. Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and
  18. ``cmp`` arguments to customize field comparison.
  19. The resulting class will have a full set of ordering methods if
  20. at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided.
  21. :param Optional[callable] eq: `callable` used to evaluate equality
  22. of two objects.
  23. :param Optional[callable] lt: `callable` used to evaluate whether
  24. one object is less than another object.
  25. :param Optional[callable] le: `callable` used to evaluate whether
  26. one object is less than or equal to another object.
  27. :param Optional[callable] gt: `callable` used to evaluate whether
  28. one object is greater than another object.
  29. :param Optional[callable] ge: `callable` used to evaluate whether
  30. one object is greater than or equal to another object.
  31. :param bool require_same_type: When `True`, equality and ordering methods
  32. will return `NotImplemented` if objects are not of the same type.
  33. :param Optional[str] class_name: Name of class. Defaults to 'Comparable'.
  34. See `comparison` for more details.
  35. .. versionadded:: 21.1.0
  36. """
  37. body = {
  38. "__slots__": ["value"],
  39. "__init__": _make_init(),
  40. "_requirements": [],
  41. "_is_comparable_to": _is_comparable_to,
  42. }
  43. # Add operations.
  44. num_order_functions = 0
  45. has_eq_function = False
  46. if eq is not None:
  47. has_eq_function = True
  48. body["__eq__"] = _make_operator("eq", eq)
  49. body["__ne__"] = _make_ne()
  50. if lt is not None:
  51. num_order_functions += 1
  52. body["__lt__"] = _make_operator("lt", lt)
  53. if le is not None:
  54. num_order_functions += 1
  55. body["__le__"] = _make_operator("le", le)
  56. if gt is not None:
  57. num_order_functions += 1
  58. body["__gt__"] = _make_operator("gt", gt)
  59. if ge is not None:
  60. num_order_functions += 1
  61. body["__ge__"] = _make_operator("ge", ge)
  62. type_ = new_class(class_name, (object,), {}, lambda ns: ns.update(body))
  63. # Add same type requirement.
  64. if require_same_type:
  65. type_._requirements.append(_check_same_type)
  66. # Add total ordering if at least one operation was defined.
  67. if 0 < num_order_functions < 4:
  68. if not has_eq_function:
  69. # functools.total_ordering requires __eq__ to be defined,
  70. # so raise early error here to keep a nice stack.
  71. raise ValueError(
  72. "eq must be define is order to complete ordering from "
  73. "lt, le, gt, ge."
  74. )
  75. type_ = functools.total_ordering(type_)
  76. return type_
  77. def _make_init():
  78. """
  79. Create __init__ method.
  80. """
  81. def __init__(self, value):
  82. """
  83. Initialize object with *value*.
  84. """
  85. self.value = value
  86. return __init__
  87. def _make_operator(name, func):
  88. """
  89. Create operator method.
  90. """
  91. def method(self, other):
  92. if not self._is_comparable_to(other):
  93. return NotImplemented
  94. result = func(self.value, other.value)
  95. if result is NotImplemented:
  96. return NotImplemented
  97. return result
  98. method.__name__ = "__%s__" % (name,)
  99. method.__doc__ = "Return a %s b. Computed by attrs." % (
  100. _operation_names[name],
  101. )
  102. return method
  103. def _is_comparable_to(self, other):
  104. """
  105. Check whether `other` is comparable to `self`.
  106. """
  107. for func in self._requirements:
  108. if not func(self, other):
  109. return False
  110. return True
  111. def _check_same_type(self, other):
  112. """
  113. Return True if *self* and *other* are of the same type, False otherwise.
  114. """
  115. return other.value.__class__ is self.value.__class__