Skip to content

Instantly share code, notes, and snippets.

@josiahcarlson
Last active July 17, 2025 01:07
Show Gist options
  • Save josiahcarlson/39ed816e80108093d585df94f2dbc8b7 to your computer and use it in GitHub Desktop.
Save josiahcarlson/39ed816e80108093d585df94f2dbc8b7 to your computer and use it in GitHub Desktop.
No need for typing any more __dunder__ method names, solving 80-95% of hand-wringing: https://news.ycombinator.com/item?id=44579717
"""
Copyright 2025 Dr. Josiah Carlson, Phd <[email protected]>
Released under the "don't be a jerk" license - which requires that you
include this entire header, and that you not be a jerk. Can't do that?
You don't have a license. Want to be super cool? Buy me a pizza:
https://josiahcarlson.github.io/
Why was this created? There are a lot of complainers about Python's
syntax for magic methods. This module allows you to not need to use __add__
to override the + operation, you can instead use::
class MyClass(metaclass=ReDunder):
def add(self, other):
# your implementation here
And your add method (as well as any other matching methods / entries in the
class namespace) will be auto-renamed to having double-underscore prefixed
and suffixed names, aka dunder names.
If you find that we have missed a name, feel free to call _scan_namespace(obj)
to discover any other relevant names on the type with existing dunder names.
If you wish to limit renames::
class MyClass(metaclass=ReDunder):
re_dunder = {"add"}
# class MyClass(metaclass=ReDunder, re_dunder={"add"}): # alternative spellling to ^^^
def add(self, other):
# your implementation here
# method will be renamed to __add__
def sub(self):
# don't rename this one!
# method will remain sub.
Warning: you probably shouldn't use this in production code. For real. Stop.
"""
import types
import typing
# pulled from https://docs.python.org/3/reference/datamodel.html on July 16, 2025
dunder = set([
'__path__', '__name__', '__weakref__', '__qualname__', '__hash__', '__class_getitem__',
'__annotations__', '__module__', '__class__', '__aenter__', '__objclass__', '__loader__',
'__self__', '__dict__', '__file__', '__anext__', '__release_buffer__', '__traceback__',
'__func__', '__cached__', '__spec__', '__prepare__', '__buffer__', '__globals__',
'__firstlineno__', '__getattr__', '__classcell__', '__format__', '__package__',
'__future__', '__getitem__', '__length_hint__', '__doc__', '__init_subclass__',
'__set_name__', '__match_args__', '__code__', '__dir__', '__slots__', '__aexit__'
])
de_dunder = {x[2:-2] for x in dunder}
ld = len(de_dunder)
# clean up namespace
del dunder
def _filter_name(name: str) -> bool:
return name.startswith("__") and name.endswith("__") and len(name) > 4
def _scan_namespace(ns: object) -> None:
for it in dir(ns):
if _filter_name(it):
de_dunder.add(it[2:-2])
for name in dir(getattr(ns, it)):
if _filter_name(name):
de_dunder.add(name[2:-2])
def _init(ex_objects: typing.Iterable) -> None:
any(map(_scan_namespace, ex_objects))
# make sure we get some methods from real objects, not just docs
if len(de_dunder) == ld:
_init([1, 1.1, {}, [], de_dunder, "", b"", _init, types, typing])
assert len(de_dunder) > ld
# clean up namespace
del ld
class ReDunder(type):
"""
Setting::
re_dunder = {'add', 'sub'}
Or using::
class MyClass(metaclass=ReDunder, re_dunder={'add', 'sub'})
In your class will limit the remapping to only those methods listed.
"""
def __new__(cls: type, name: str, bases: list, namespace: typing.Mapping, **kwds: dict) -> type:
only_these = kwds.get("re_dunder") or namespace.get("re_dunder") or de_dunder
only_these = set(only_these) if not isinstance(only_these, set) else only_these
# rename <operation> -> __<operation>__
# O(len(kwds)) operation to find + rename everything
for it in set(namespace) & only_these:
namespace[f"__{it}__"] = namespace.pop(it)
return super().__new__(cls, name, bases, namespace)
# Ta da! That's it. <100 lines, including docs, and now you don't need to be
# shackled to the Tyranny of Python's __dunder__ methods. Just De-dunder your
# code and use our ReDunder metaclass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment