4 ms·
i'm curious about that __eq__. it generates code like (self.x, self.y) == (other.x, other.y) but i think this would be more efficient: self.x == other.x
by uryga 6y ago
i'm curious about that __eq__. it generates code like
(self.x, self.y) == (other.x, other.y)
but i think this would be more efficient:
self.x == other.x and self.y == other.y
because you avoid creating a tuple (possibly heap-allocated) and if `.x` differs, you avoid a dictionary lookup¹ for `.y` thanks to short-circuiting. i think i benchmarked it at some point, but that was a while ago (on CPython 3.5)
---
btw, i went with a similar approach (i.e everything is codegened) in my sum type library: http://github.com/lubieowoce/sumtype http://github.com/lubieowoce/sumtype
i never got around to generating the methods lazily, maybe i should!
for correctness reasons i switched from interpolating raw strings to <array of (line, indent-level)> and a bunch of wrappers – generating if-elif-else chains via raw strings gets scary. but they work well enough here
---
1. iirc even though it's using slots, it still has to look up the descriptors for `.x` and `.y` in the class dictionary. another possible optimization would be to trade off memory (and extensibility) for time and "cache" those. something in the vein of
def generate(...):
...
get_x = MyClass.x.__get__
get_y = MyClass.y.__get__
...
def eq(self, other):
return (get_x(self), get_y(self)) == (get_x(other), get_y(other))
MyClass.__eq__ = eq
here, `get_x` and `get_y` would be closed-over in `eq`, so they shouldn't incur a dictionary lookup.
which of course adds significant complexity, but that may be a sensible trade-off