I feel jumping right into __init_subclass__ without explaining why metaclasses exist and what problems they typically solve excludes the majority of devs on HN. Thereby limiting any discussion only to the advanced Python developer echo chamber, while discussion across very different devs is usually far more interesting.
Yeah this is great, __init_subclass__ comes along to make dynamic injection of methods and attributes on class creation easier, just as the entire practice is fast becoming fully obsolete because type checkers like pylance and mypy report these attributes and methods as errors.
I'm at a point in my career where I see "magic" and wince hard.<p>This is so hard to reason about and fix at scale. If you let other engineers run wild with this and build features with it, the time will eventually come to decom your service and move functionality elsewhere. Having to chase down these rabbits, duplicate magic, and search large code bases without the help of an AST assisted search is slow, painful, and error prone.<p>I spent several years undoing magic method dispatch in Ruby codebases. Tracing through nearly a thousand endpoints to detect blast radiuses of making schema changes impacted by CRUD ops on lazily dispatched "clever code".<p>I'm sure there are valid use cases for this, but be exceedingly careful. Boring code is often all you need. It doesn't leave a mess for your maintainers you'll never meet.<p>Python users tend not to behave this way, but seeing posts like this requires me to urge caution.
“Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).”<p>— Tim Peters
URL is to a "Things I Learned" by Simon W, but the title comes from tweet by David Beazley:<p><a href="https://twitter.com/dabeaz/status/1466731368956809219" rel="nofollow">https://twitter.com/dabeaz/status/1466731368956809219</a><p>Which is a warning that people shouldn't buy his new book if they want a deep dive on Metaprogramming:<p><a href="https://www.amazon.com/dp/0134173279/" rel="nofollow">https://www.amazon.com/dp/0134173279/</a><p>Simon does a nice job demonstrating how "the __init_subclass__ class method is called when the class itself is being constructed."
There are bugs in this code and I'm glad that I'm not the only one that has done it!<p><pre><code> graph = {
key: {
p
for p in inspect.signature(method).parameters.keys()
if p != "self" and not p.startswith("_")
}
for key, method in cls._registry.items()
}
</code></pre>
The first parameter to a bound method does not have to be called `self`. It's conventional, but not required. Is there a better way in the inspect module to filter these parameters? This comes up more often with classmethods where the naming convention `cls` is most common but I see `klass` somewhat frequently as well.
I wish there was a 'Modern Python' tutorial that would walk me through all the new python stuff like this or the type declarations or the new libs, etc.
I'm working on a project right now that uses __init_subclass__ and it works great, but takes some tricks (like having classvars with types set) in order to get mypy to cooperate. But it's way easier to reason about than metaclasses.
For those curious, here is a great explanation of what metaclasses are in python: <a href="https://stackoverflow.com/a/6581949" rel="nofollow">https://stackoverflow.com/a/6581949</a>
I've only once used metaclasses but I think it was pretty nifty. The use case was with Python and Gtk+ (both version 2), if you had your UI in a glade file you could define a corresponding class, and it would automatically create members for all your named widgets. It made it a bit like coding in VB:<p><pre><code> class MainWindow(gtk.Window):
__metaclass__ = MetaDescribedUI("MainWindow.ui")
def __init__(self):
# now you can use e.g. self.lvFiles or self.btnOK
pass
</code></pre>
Although writing it now I could probably do it differently without metaclasses.
(Shameless self-plug incoming…)<p>For those curious about what metaclasses actually are, I wrote up an article with some motivating examples about this a little while back. Might be worth a read if you’re interested!<p><a href="https://www.joe-bergeron.com/posts/Interfaces%20and%20Metaclasses%20in%20Python/" rel="nofollow">https://www.joe-bergeron.com/posts/Interfaces%20and%20Metacl...</a>
Isn't it ironic that one of the most readable procedural languages today has almost unreadable OOP syntax?<p>People unironically created interfaces and various overloaded operations using dunder methods. Not only do you need to keep a list of them under your pillow, but you must also keep notes on how to construct the interface for each one.<p>And they look ugly as hell. In a language that prides itself on readability and user friendlines.<p>My suggestion is to ditch the OOP syntax completely and make a built-in analogue to a C struct. Take inspiration from dataclasses syntax. OOP in a dynamic glue language is a silly idea anyway. Of course, you would have to bump the version to 4.0.
I recently encountered __init_subclass__ for the first time in Home Assistant's custom integrations. Configuration handlers were registerd by "just declaring the class" like so and I couldn't grok how it was registered by the runtime<p><pre><code> class MyCustomOptionsFlow(OptionsFlow, DOMAIN="MYPLUGIN"):...
</code></pre>
A bit of digging showed that __init__subclass_ was being used effectively as a registration callback, adding the newly defined class/DOMAIN to the global CustomOptions handler at class definition.<p>Very neat, not immediately obvious however :/
Evidence, if any was needed, that OOP, or at least the Python variant, was never designed for metaprogramming. Give me Clojure macros any day of the week over these ugly contortions.
I love that Python is unique and strange enough that merely seeing "__init_subclass__" in this headline is enough to know exactly what language it is talking about ;)
Official docs for that feature:<p><a href="https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__" rel="nofollow">https://docs.python.org/3/reference/datamodel.html#object.__...</a>
A recent anegdote with regards to typing in Python.<p>Let's say you have this code:<p>class Creature:<p><pre><code> def __init__(self, name: str, hp: int, attack: int):
...
def attack(self, other: Creature):
...
</code></pre>
Guess what? Since Creature is not yet defined, your very smart type-checker can't see the "other" parameter. Happened to me in VS Code.<p>That is the problem with features bolted on to a language where they perhaps don't belong. Now, I know that typing helps when you have simple, functional code. So, this code will work:<p>from dataclasses import dataclass<p>@dataclass<p>class Creature:<p><pre><code> name: str
hp: int
attack: int
</code></pre>
def combat_between(attacker: Creature, defender: Creature):
...<p>I was really surprised with this. We need better review in regards to adding additional features that may be broken on certain setups.
For all of Python's magic hackery that it lets you inject it feels like there are still some obvious 'holes'.<p>The one that bites me a lot is: I want to easily be able to write a decorator that accesses variables on a particular _instance_ of a class (such as a cached return value), and I want to take a Lock when I do it.<p>But since decorators are defined on the class method, not the instance, it has to do a bunch of work when the function runs: look at the instance, figure out if the lock is defined, if not define it (using a global lock?), take the lock, then do whatever internal processing it wants. It feels like decorators should have a step that runs at `__init__` time to do per-instance setup but instead I have to figure it out myself.
What's the advantage of this over class decorators?<p><pre><code> @decorator
class SomeClass:
...
</code></pre>
To me, class decorators seem to be much easier to reason about.
Python is quickly taking the crown for low-barrier to entry, slow, buggy code supported primarily by stackoverflow copy pastes from a never-ending supply of “data scientists”