Reputation: 4416
As pointed out in __key__ parameter for classes in Python, Python3.x has functools.total_ordering
, but it still requires manually implementing __lt__
, __hash__
and __eq__
manually.
In many cases what I need thus boils down to the still repetitive pattern
@functools.total_ordering
class X:
...
_key = lambda self: (self.field1, self.field2)
__lt__ = lambda self, other: self._key() < other._key()
__eq__ = lambda self, other: self._key == other._key
__hash__ = lambda self: hash(self._key())
The motivation for the _key
function is the key
parameter used in utility funcitons like sorted
, which behaves roughly like this -- calculate a tuple from the object, and then compare the tuples.
Is there some feature of python, that allows defining a class-level function, from which all comparison function and a __hash__
function are derived, similarly to what I do manually?
There is __key__ parameter for classes in Python, but it considers only comparison operators. The __hash__
function is never considered. As a result the question looks similar, but treats only a subset of my question.
Upvotes: 0
Views: 205
Reputation: 14233
Look at dataclasses module from Standard Library:
This module provides a decorator and functions for automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes. It was originally described in PEP 557.
this include also options for generating comparison and hash special methods.
from dataclasses import dataclass
@dataclass(order=True)
class Foo:
field1: int
field2: int
spam = Foo(1, 2)
eggs = Foo(2, 3)
foo = Foo(2, 1)
bar = Foo(2, 1)
print(spam > eggs)
print(eggs == foo)
print(bar < eggs)
print(dir(Foo))
output
False
False
True
['__annotations__', '__class__', '__dataclass_fields__',
'__dataclass_params__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__']
We don't have full information about your class, so it's difficult to tell if it will solve your problem.
Upvotes: 2
Reputation: 50116
You can implement your own class-decorator to automatically add __hash__
, __eq__
and __lt__
in terms of a __key__
(or other known method name).
def key_hash(self):
return hash(self.__key__())
def key_lt(self, other):
return self.__key__() < other.__key__()
def key_eq(self, other):
return self.__key__() == other.__key__()
def keyed(cls):
for op_name, op_func in (("__hash__", key_hash), ("__lt__", key_lt), ("__eq__", key_eq)):
setattr(cls, op_name, op_func)
return cls
This can be applied just like total_ordering
and also combined with it:
@functools.total_ordering
@keyed
class X:
def __init__(self, field1, field2):
self.field1, self.field2 = field1, field2
def __key__(self): return self.field1, self.field2
If you always want keyed
and functools.total_ordering
, they can be merged into one decorator as well.
def total_keyed(cls):
for op_name, op_func in (("__hash__", key_hash), ("__lt__", key_lt), ("__eq__", key_eq)):
setattr(cls, op_name, op_func)
return functools.total_ordering(cls)
Upvotes: 1