Context:
I'd like to use heapq (and anything else) on objects I didn't create, which don't themselves have a __lt__
operator. Can I? (without a wrapper class).
the class:
class Node:
def __init__(self, val):
self.val = val
Now, at runtime in the interpreter, I am handed some collection of objects. I want to iterate over them, adding a dunder method (in my case lt), eg:
n = Node(4)
m = Node(5)
def myLT(self, other):
return self.val < other.val
What I tried:
n.__lt__ = types.MethodType(myLT, n)
m.__lt__ = types.MethodType(myLT, m)
also
n.__lt__ = types.MethodType(myLT, n)
m.__lt__ = types.MethodType(myLT, n)
(on the off chance that binding the same functor-thing would improve matters)
>>> n < m
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Node' and 'Node'
even though:
>>> n.__lt__(m)
True
I can use a wrapper class, which is yucky in some ways (extra memory and traversal code gets uglier, but at least leaves the original objects untouched):
class NodeWrapper:
def __init__(self, n):
self.node = n
def __lt__(self):
return self.node.val
I'm just interested to know if I'm doing something wrong in adding the dunder method, or if this just doesn't work in python 3.x. I'm using 3.6.9 if that matters.
You can try monkeypatching the dunder by changing the __class__
property of the instance. As explained in by docs section Special method lookup:
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.
def patch_call(instance, func, memo={}):
if type(instance) not in memo:
class _(type(instance)):
def __lt__(self, *arg, **kwargs):
return func(self, *arg, **kwargs)
memo[type(instance)] = _
instance.__class__ = memo[type(instance)]
patch_call(m, myLT)
patch_call(n, myLT)
n < m
# True
Thanks to @juanpa.arrivilaga for recommending the classes be cached to improve performance.