top | item 39917303

(no title)

neoncontrails | 1 year ago

I have such a love/hate relationship with dunders. On the one hand, I get warm fuzzies from simplifying a module's interface with `__call__`. More than once I've seized an opportunity to rewrite some ugly conditional logic with a partial ordering by leveraging `__gt__`, `__lte__` and friends. `__setattr__` remains an interesting alternative to using decorators to execute some pre- or post-call behavior to a class method. (IIRC it can also be more efficient than wrapping a method with a decorator, since the `__setattr__` override gets precompiled into Python bytecode along with the rest of the class def.) Probably some other sleights of hand along these lines that I'm forgetting. I'm sure they were fun to write and I probably felt clever doing it.

Unfortunately in almost every case, dunder methods made it harder for me to collaborate with other people on the same codebase. And I get it. Tinkering with `__setattr__` leads to recursion errors if you're not careful, `__call__` introduces state in an unexpected place (wait, foo is a class instance? But but it behaves just like a function...). It's one of the only "stupid Python tricks" I can think of that lacks a clear analog in other programming languages, so polyglots without a strong Python background tend to hate them. I've tried to make the case on two separate occasions that dunder methods represent a more object-oriented approach to dealing with systemic complexity than type dispatch, and I stand by this. But I concede that the benefits of making the codebase nicer and more ergonomic in some places invariably requires writing class definitions that look horrendous in other places. So it's not so much that dunder methods reduce that complexity, so much as try to contain it and hopefully prevent it from spilling out into the main execution logic.

P.S. - Trey, if you're reading this, hello from a former TA!

discuss

order

th|1 year ago

> Trey, if you're reading this, hello from a former TA!

Hi former TA! It's been a long time.

> Unfortunately in almost every case, dunder methods made it harder for me to collaborate with other people on the same codebase.

I see dunder methods as methods that should usually (with the exception of __init__, __repr__, and __eq__) be used much more often than they're read.

I typically use dunder methods when implementing a library or framework that's meant to be used by others who would be looking at its documentation but not necessarily its code.

For example the dramatic library I published over the weekend relies on __enter__, __exit__, and __del__ (which is a very uncommon one typically): https://pypi.org/project/dramatic/

Users of that code shouldn't need to look at the code.

For code that's modified by newer Python users often, using dunder methods might be more difficult to justify. Though you might be able to get away with making a library which that could could use.

parpfish|1 year ago

There’s also a risk that you could spend a long time making some nice behavior possible with dunders, but then people using your object don’t know about it because a lot of the new behaviors don’t get surfaced in typical IDEs.

packetlost|1 year ago

Python has a bunch of facilities for poking holes in the "normal" execution flow (most "dunder" methods), but true enlightenment comes from understanding that it's all just dictionaries, type descriptors, functions, and a particularly gnarly execution order all the way down. The CPython interpreter has some pretty interesting C code too.