[HN Gopher] Problems once solved by a metaclass can be solved by...
       ___________________________________________________________________
        
       Problems once solved by a metaclass can be solved by
       __init_subclass__
        
       Author : imaurer
       Score  : 153 points
       Date   : 2022-01-05 16:35 UTC (6 hours ago)
        
 (HTM) web link (til.simonwillison.net)
 (TXT) w3m dump (til.simonwillison.net)
        
       | imaurer wrote:
       | URL is to a "Things I Learned" by Simon W, but the title comes
       | from tweet by David Beazley:
       | 
       | https://twitter.com/dabeaz/status/1466731368956809219
       | 
       | Which is a warning that people shouldn't buy his new book if they
       | want a deep dive on Metaprogramming:
       | 
       | https://www.amazon.com/dp/0134173279/
       | 
       | Simon does a nice job demonstrating how "the __init_subclass__
       | class method is called when the class itself is being
       | constructed."
        
       | captainmuon wrote:
       | 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:
       | class MainWindow(gtk.Window):             __metaclass__ =
       | MetaDescribedUI("MainWindow.ui")                  def
       | __init__(self):                 # now you can use e.g.
       | self.lvFiles or self.btnOK                 pass
       | 
       | Although writing it now I could probably do it differently
       | without metaclasses.
        
       | chadykamar wrote:
       | "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)."
       | 
       | -- Tim Peters
        
         | tybug wrote:
         | I realize this quote isn't meant to be taken literally, and
         | it's a nice one-liner encapsulation of metaclasses, but it
         | always bothered me. The people who "actually need metaclasses"
         | were, at some point, learning about metaclasses for the first
         | time and were "wondering whether they need them".
         | 
         | This quote ignores the middle ground of users who have a valid
         | use case, but don't yet understand metaclasses. Not great
         | advice for people who are trying to learn metaclasses.
        
           | jcranmer wrote:
           | I do sympathize with this sentiment, but there are times when
           | "if you're wondering if you need it, you don't need it" is a
           | very true statement, and Python metaclasses are in that boat.
           | To see why, let me explain in more detail:
           | 
           | There's a "basic" [1] understanding of Python's object model,
           | one which understands that most of Python is actually
           | syntactic sugar for calling certain special methods. For
           | example, the expression a[b] is "really" just calling
           | a.__getitem__(b). Except that's not actually true; the "real"
           | object model involves yet more dispatching to get things to
           | work. Metaclasses allow you to muck with that "yet more
           | dispatching" step.
           | 
           | So when do you need metaclasses? When you need to do that
           | low-level mucking--the kind of mucking most won't know about
           | until actually needed. If all you know about metaclasses is
           | kind of what they are, then you very likely haven't learned
           | enough about them to use them to actually need them.
           | Conversely, if you've learned those details well enough to
           | need to muck with them, then you've also learned enough to
           | the point that you can answer the question as to whether or
           | not you need metaclasses.
           | 
           | [1] I suspect most Python users don't even have this level of
           | understanding, which is why I put it in scare quotes.
        
           | nas wrote:
           | I suspect Tim's meaning was that ordinary users don't need to
           | know how metaclasses work in detail. It is enough that people
           | know they exist (i.e. do something at time class is defined).
           | If you need them, you are doing deep enough magic that you
           | can take the time to learn how they work.
           | 
           | Maybe he was also suggesting that people should generally not
           | be using them. In my experience, it is exceedingly rare to
           | need them. In 29 years of writing Python code, I think I've
           | used them once or twice.
        
       | ajkjk wrote:
       | For all of Python's magic hackery that it lets you inject it
       | feels like there are still some obvious 'holes'.
       | 
       | 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.
       | 
       | 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.
        
         | Spivak wrote:
         | I'm so confused.                   from functools import wraps
         | from threading import Lock              def my_decorator(f):
         | # Do whatever you want to set up the lock.            lock =
         | Lock()                 @wraps(f)            def wrapper(self,
         | *args, **kwargs):                # Do whatever with the lock.
         | lock.acquire()                     # Do whatever you want with
         | the instance.                print(self.greeting)
         | return f(self, *args, **kwargs)            return wrapper
         | class MyClass:           def __init__(self):
         | self.greeting = "Hello World!"                @my_decorator
         | def my_method(self):             print("And all who inhabit
         | it.")              >>> MyClass().my_method()         Hello
         | World!         And all who inhabit it.
         | 
         | Edit: Wait do you want the decorator, before it ever runs, to
         | add a property self.lock? Are you sure you don't want this to
         | be a class decorator that messes with __init__? Because you can
         | 100% wrap init to do that kind of set up.
        
           | ajkjk wrote:
           | The problem is that `lock = Lock()` is shared across all
           | instances of the class, which creates contention if there are
           | many of them. To solve this you need to save a per-instance
           | lock, so yes to "Wait do you want the decorator, before it
           | ever runs, to add a property self.lock".
           | 
           | An example usecase is writing a @cached decorator that
           | behaves well in multithreaded programs and can be used on
           | classes that have many instances, with the caching happening
           | per-instance.
        
       | wheelerof4te wrote:
       | Python is slowly going in the direction of C++. Sad to see,
       | really.
        
         | ajkjk wrote:
         | I don't think it's nearly as bad. The magic nonsense is fairly
         | well-constrained inside the OOP system and there is, imo, a
         | certain coherent logic to it.
        
         | ChuckMcM wrote:
         | Not particularly slowly, as fast as it can manage :-).
        
       | sam0x17 wrote:
       | 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 ;)
        
       | Naac wrote:
       | For those curious, here is a great explanation of what
       | metaclasses are in python: https://stackoverflow.com/a/6581949
        
       | matsemann wrote:
       | How does this play with typing and other tools? I hate the use of
       | all the magic stuff in Python making it needlessly hard to use
       | lots of libraries.
        
         | sillysaurusx wrote:
         | It's fine. https://news.ycombinator.com/item?id=29812639
        
       | joebergeron wrote:
       | (Shameless self-plug incoming...)
       | 
       | 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!
       | 
       | https://www.joe-bergeron.com/posts/Interfaces%20and%20Metacl...
        
       | kkirsche wrote:
       | Even using Python regularly I haven't run into a need for meta
       | classes. I'm sure there is a valid reason, but what is it?
        
         | throwaway19937 wrote:
         | (https://www.linuxjournal.com/article/3882) has a dated (2000)
         | example of metaclass usage in Python 2.
        
         | enragedcacti wrote:
         | More generically, Metaclasses are primarily useful when using
         | (abusing?) the Python type system to represent other type
         | systems. The most common example of this is ORMs like
         | SQLAlchemy, where the Python type system is used to represent
         | tables in a relational database.
         | 
         | Another example is the builtin "Typing" module where the type
         | system is used to provide a language for representing itself.
         | "Union" itself is a type, and metaclasses allow you to set
         | "__getitem__" on the "Union" type such that Union[Int|Str] is
         | valid and represents a new type. Not that I don't know if
         | metaclasses are actually used to build this system internally,
         | but you could use it for many of the features implemented by
         | Typing.
         | 
         | There are alternative ways to go about doing that because
         | Python is so flexible, but IMO metaclasses (or
         | __init_subclass__ as seen in this post) are the least "magic"
         | of the magic ways to tackle problems like these.
        
         | dragonwriter wrote:
         | The need _isn 't_ common, but there are some real uses. The top
         | answer of this SO thread (I didn't read the rest, so there may
         | be more goodies) has some good discussion of use cases
         | including comparison to non-metaclass approaches to the same
         | problems.
         | 
         | https://stackoverflow.com/questions/392160/what-are-some-con...
        
         | fdomingues wrote:
         | Is useful to do code generation/transformation, for example in
         | this utility[1] it uses the information provided on the
         | declaration of the class to generate the methods required by
         | the base class.
         | 
         | The user of the utility write this:                   class
         | Book(objects.Object):             title: Optional[str]
         | 
         | And the generated class code is:                   class
         | Book(Base):             __slots__ = ('title', )
         | title: typing.Optional[str]                      def
         | __init__(self, title=None):                 self.title = title
         | @classmethod             def from_data(cls, data):
         | title = data.get('title', None)                 return
         | cls(title=title)                      def to_data(self):
         | data = {}                 if self.title is not None:
         | data['title'] = self.title                 return data
         | @classmethod             def from_oracle_object(cls, obj):
         | return cls(title=obj.title)                      def
         | to_oracle_object(self, connection):                 obj =
         | connection.gettype('Book').newobject()
         | obj.title = self.title                 return obj
         | 
         | [1]https://github.com/domingues/oracle-object-mapping
        
         | Sohcahtoa82 wrote:
         | I recently used them to create a framework for handling plugins
         | to an IRC bot.
         | 
         | By creating an IrcMessageHandler metaclass, I can easily
         | subclass it to encapsulate functionality into different classes
         | without having to add boilerplate to load each plugin. There's
         | a Plugins directory with a __init__.py that automatically loads
         | all .py files, and the main bot just has to do `from Plugins
         | import *` to load them all. Each plugin script merely needs a
         | `from ircbot import IrcMessageHandler`, then creates a
         | subclass. So there's a Calc class that adds a "!calc" command,
         | Seen class that adds "!seen", and more. Each of them merely
         | needs to include an "on_message" function that parses IRC
         | messages.
        
         | tybug wrote:
         | The most salient example I can think of is tracking all
         | subclasses of a class. This is how django tracks `Model`
         | subclasses to create a table for each model, for instance. I
         | wrote a blog post on this topic:
         | https://tybug.github.io/2021/11/06/tracking-all-subclasses-o...
         | 
         | I've also used metaclasses in the past to apply a decorator to
         | certain methods of all subclasses, without needing to specify
         | that decorator in the subclass declaration itself.
        
       | dreyfan wrote:
       | 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"
        
         | ransom1538 wrote:
         | I can write php in any language.
        
           | dreyfan wrote:
           | modern php and js/ts are both rather impressive how far
           | they've come compared to the jumble of crap we had a decade
           | ago.
        
             | sam0x17 wrote:
             | I miss it. On the javascript side there was just jquery and
             | a mountain of vanilla js that everyone could at least
             | understand (and no npm dependency issues, ES6+ weirdness,
             | etc) and on the PHP side, all built-in functions were in
             | the global scope (easy to CTRL+F on a single page at a time
             | when search endpoints were quite slow), there were
             | (virtually) no web frameworks (at least I didn't use any
             | until after 2009), and no package management to worry about
             | unless you were doing something weird and/or wrangling with
             | wordpress. As a tween and young teen I had plenty of small
             | web design clients.. local businesses, photographers, etc.,
             | and I could build sites 100% from scratch starting from a
             | photoshop mockup to custom PHP/HTML/CSS/JS, 100% no
             | packages or other people's code. Packages have done a ton
             | for us, for sure, but I truly miss those days and feel like
             | we've lost something we'll never get back.
             | 
             | Back then it was actually quite possible to build an entire
             | website or "app" as we call them now from first principles
             | just banging your head against HTML/CSS/JS and whatever
             | server side language you were using. Then the internet grew
             | up, security issues became more prevalent, packages, other
             | people's code, dependency hell, "futures", "promises",
             | server state and client state management, shadow dom,
             | reactivity, etc etc.
        
         | ww520 wrote:
         | VB6 of modern day.
        
         | robervin wrote:
         | Quickly seems like an overstatement; you've always been able to
         | write low-barrier to entry, slow, buggy code. Is there a low-
         | barrier language that doesn't end up with a lot of less-than-
         | ideal code samples?
        
         | shrimpx wrote:
         | There's a lot of nice Python code out there, mostly written by
         | disciplined and experienced systems people.
         | 
         | But I agree about the hordes of data scientists making copy and
         | paste spaghetti. This is evidenced by companies deploying
         | Jupyter notebooks in production, which essentially witnesses
         | the fact that they've given up on getting data people to write
         | quality production Python, and instead they treat their
         | prototype spaghetti as sandboxed blackboxes that can be tested
         | on inputs and deployed if they perform well enough.
        
         | reedf1 wrote:
         | If you can write code twice as fast then you'll write twice as
         | many bugs.
         | 
         | Having been a C++ and python developer I see just as many
         | footguns in both languages and have spent many many many hours
         | dealing with slow, buggy code in either.
        
         | ThePhysicist wrote:
         | Meh, Python really shines at I/O and gluing together
         | C/C++/Fortran code. It's reasonably fast for most tasks, and if
         | it isn't there are tools like Cython that make it pretty
         | trivial to write Python code that achieves C-like execution
         | speed (because it effectively compiles to C code).
        
           | bloblaw wrote:
           | I agree that Python is a great glue language (so are Perl and
           | Python).
           | 
           | However, Python really doesn't shine when compared with other
           | memory safe languages like Go, Rust, and Java.
           | 
           | Ultimately, Python is single-threaded by design. There are
           | some hacks to get around this design contraint (multi-
           | processing), but then you are adding an entire additional
           | Python runtime to your system's memory for every "thread" you
           | want to run. Even with asyncio, Python performs the worst
           | when it comes to I/O.
           | 
           | I love Python, but it's important to understand that due to
           | specific design considerations it is not and will likely
           | never be a performant language.
           | 
           | reference: https://medium.com/star-gazers/benchmarking-low-
           | level-i-o-c-...
        
       | zbyforgotpass wrote:
       | 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.
        
         | diarrhea wrote:
         | I learned about metaclasses in "Fluent Python". An excellent
         | book, but it predates typing, so that's not covered. Then
         | again, I believe a second edition is on its way.
        
       | cutler wrote:
       | 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.
        
       | wheelerof4te wrote:
       | A recent anegdote with regards to typing in Python.
       | 
       | Let's say you have this code:
       | 
       | class Creature:                   def __init__(self, name: str,
       | hp: int, attack: int):             ...              def
       | attack(self, other: Creature):             ...
       | 
       | 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.
       | 
       | 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:
       | 
       | from dataclasses import dataclass
       | 
       | @dataclass
       | 
       | class Creature:                   name: str         hp: int
       | attack: int
       | 
       | def combat_between(attacker: Creature, defender: Creature): ...
       | 
       | I was really surprised with this. We need better review in
       | regards to adding additional features that may be broken on
       | certain setups.
        
         | orf wrote:
         | Simply add quotes around "Creature" and it works.
         | 
         | > We need better review in regards to adding additional
         | features that may be broken on certain setups.
         | 
         | What does this even mean. The documentation explains that you
         | need to do this, and it's fairly obvious why you would.
         | 
         | Type hints are one of the best features added to Python.
        
           | wheelerof4te wrote:
           | Yeah, "simply add quotes", indeed.
           | 
           | Except, it is not that simple. It's not even intuitive.
           | Typing is a great feature on paper, but in practice, it is
           | just something we don't need in a scripting and glue
           | language.
        
             | orf wrote:
             | Except it is simple and it is fairly intuitive. It is also
             | pretty awesome. See fastapi, pyndantic, dataclasses, etc
             | etc.
        
         | tda wrote:
         | That's why you can add quotes to types:                   def
         | attack(self, other: 'Creature'):
        
           | wheelerof4te wrote:
           | Thanks, I did not know about this.
        
       | wheelerof4te wrote:
       | Isn't it ironic that one of the most readable procedural
       | languages today has almost unreadable OOP syntax?
       | 
       | 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.
       | 
       | And they look ugly as hell. In a language that prides itself on
       | readability and user friendlines.
       | 
       | 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.
        
       | amelius wrote:
       | __This__ __looks__ __very__ __interesting__!
        
       | pengwing wrote:
       | 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.
        
         | floober wrote:
         | Note that this doesn't appear to have been submitted by the
         | post's author, and HN may not have been the intended audience.
        
           | abdusco wrote:
           | Simon keeps his TIL (today I learned) blog as a way to take
           | notes mainly for himself, from what I understand.
           | 
           | I like the way he documents everything in Github issues[0]. I
           | learn a new thing or two with every release of Datasette,
           | because there's so much troubleshooting full of relevant
           | links and solutions. Same with his TIL blog.
           | 
           | [0]: https://github.com/simonw/datasette/issues/1576
        
             | DonaldPShimoda wrote:
             | I've not seen this before but it's so cool! I've lost track
             | of all the ways I've tried to keep track of things I've
             | learned. Maybe this would be a good way to do it.
             | 
             | Link to the GitHub repo for those interested:
             | https://github.com/simonw/til
        
         | godelski wrote:
         | Could you, or someone else, clue us in?
         | 
         | Edit: another user linked their blog
         | https://news.ycombinator.com/item?id=29813349
        
           | Macha wrote:
           | One use is for e.g. binding of database models for an ORM.
           | class FooModel(metaclass=Base):             x =
           | Field(type=int, primary_key=True)             y =
           | Field(type=reference(BarModel))
           | 
           | The Base metaclass will set it up to implement methods like
           | save() by inheriting from parent classes, but it would also
           | be nice for the library to have a list of all model types
           | without the library user having to call a method like
           | FooORM.register_type(FooModel). So the metaclass is being
           | used in these classes to build up a dictionary of models when
           | the class definition is encountered.
           | 
           | The metaclass is basically a class that itself builds
           | classes, which means it can be syntactically convoluted.
           | 
           | However, with __init_subclass__ you can write a thing that
           | looks like a regular class with regular parent methods, but
           | instead just gets a method called each time the interpreter
           | encounters a new subclass, which lets you do things like
           | build up that dictionary for your ORM.
        
           | diarrhea wrote:
           | What classes are to instances, metaclasses are to classes.
           | 
           | This is from someone who has only ever read about, never used
           | metaclasses, because they are widely regarded similar to git
           | submodules. If you cannot really assert that you need them,
           | you don't. They solve very specific problems, mostly found in
           | library, not user code. A library can then allow user classes
           | to be modified comprehensively. If you control the classes in
           | the first place (not library code), you probably can do
           | without metaclasses.
        
             | dragonwriter wrote:
             | > What classes are to instances, metaclasses are to
             | classes.
             | 
             | Given the popularity of this construct for analogies and
             | metaphorical comparisons, it should be noted that this is
             | _strictly literal_. In Python classes are objects, and the
             | classes whose instances are classes are called
             | "metaclasses" (and they are subclasses of the class
             | "type".)
        
               | tshaddox wrote:
               | Is this definition recursive? Meaning that classes whose
               | instances are metaclasses are also metaclasses?
        
               | [deleted]
        
               | xapata wrote:
               | Check the type of `type` in Python ;-)
        
               | dragonwriter wrote:
               | Because metaclasses are classes, yes, classes whose
               | instances are metaclasses are also classes whose
               | instances are classes and thus are also metaclasses. But
               | you could distinguish this subset of metaclasses as
               | "metametaclasses" if you wanted to, to distinguish them
               | from more general metaclasses just as metaclasses are
               | distinguished from more general classes.
               | 
               | But AFAIK no one has come up with a distinct application
               | for custom metametaclasses which would make having
               | terminology to discuss them necessary or useful other
               | than for entertainment.
        
       | fdgsdfogijq wrote:
       | I once heard a quote that described David Beazely as:
       | 
       | "The Jimi Hendrix of programming"
        
       | zzzeek wrote:
       | 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.
        
         | robertlagrant wrote:
         | Yes, this. This is where type checking becomes potentially
         | harmful: you have to start creating loads of explicit
         | subclasses instead of one dynamic class. And then you're
         | writing slow Java.
        
         | shrimpx wrote:
         | That's why I resist python types. A lot of the usefulness and
         | interestingness of Python is in its dynamic features which aid
         | in rapidly developing complex systems. Hard to imagine a
         | strictly typed future for Python, when you then have little to
         | trade off for its bad performance and lack of real concurrency.
         | JavaScript has seen success as a typed language because it has
         | being "the exclusive frontend language" going for it, and v8 is
         | high performance. And the dynamic capabilities of JavaScript
         | were never that interesting or powerful.
        
           | ryanianian wrote:
           | > Hard to imagine a strictly typed future for Python
           | 
           | On the contrary, the dynamic nature of python can make it
           | seem like inaccessible black-magic where you don't even know
           | where to look in the code to see what's even possible.
           | Stronger types will empower users to understand their
           | ecosystems.
           | 
           | I've spent countless hours in rails/django land trying to
           | figure out what's even possible with an object that the ORM
           | or whatever gives me. The human-flavored docs kinda outline
           | it, but if you're in IDE-land, context-switching to prose
           | isn't productive.
           | 
           | Types help humans develop and debug, but they make library
           | authors jump through acrobatics to express what their magic
           | does. This is a limitation of the type-checker
           | implementation(s) not having a strong syntax or notion to
           | capture the runtime-dynamic polymorphism, but it doesn't mean
           | that the concept of types is a flawed idea or not worth using
           | when appropriate.
           | 
           | Don't throw the baby out with the bath-water.
        
             | shrimpx wrote:
             | I like types, but I have a hard time seeing them in Python.
             | If I was forced to use only typed Python, I'd just use
             | golang and get a ton of performance and concurrency that I
             | could never get in typed Python.
             | 
             | Good point about Django though. Django has so much
             | historical stickiness that types probably look attractive
             | for entrenched corporate projects that are hard to port to
             | high performance natively typed languages.
        
         | dragonwriter wrote:
         | > 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.
         | 
         | The Python community and the set of people that treat mypy and
         | pylance as dictators rather than tools to be used where
         | appropriate and turned off where not are...not the same thing.
         | (And the latter very much depends on tools built by the rest of
         | the former that internally are wild and woolly, even if they
         | present a nice cleanly typed interface to the consumer.)
        
           | zzzeek wrote:
           | sure...im coming from the library producer perspective. it's
           | not very easy for us to say, "just turn off the type checker
           | when you use our library" :)
        
             | dragonwriter wrote:
             | > it's not very easy for us to say, "just turn off the type
             | checker when you use our library" :)
             | 
             | Sure, and that is a problem with exposing certain kinds of
             | dynamism when your customer base is the kind that is
             | inflexible about typing.
             | 
             | OTOH, a lot can still be done with dynamism on the inside
             | and a typed public interface, and also there's a
             | significant part of the community that accepts selective
             | disabling of typechecking as an acceptable when dynamism
             | provides adequate benefits.
        
         | j4mie wrote:
         | Python is the most popular language on the planet _because_
         | it's a dynamic language, not in spite of it. If the type
         | checker makes dynamic features difficult, ditch the type
         | checker, not the dynamic features. I can't wait for this
         | pendulum to swing back the other way.
        
           | Tanjreeve wrote:
           | Was there ever a point people were successfully building
           | "backbone" systems in dynamically typed languages? I thought
           | the pythons and Perl's of the world were always mainly doing
           | scripts/interface applications.
        
             | stevesimmons wrote:
             | See [1] for a PyData London talk I did on "Python at
             | Massive Scale", about JPMorgan's Athena trading and risk
             | management system, used for FX, Commodities, Credit,
             | Equities, etc trading. At that time, Athena had 4,500
             | Python developers doing 20,000 commits a week.
             | 
             | Also see one recent HN discussion on "An Oral History of
             | Bank Python" [2].
             | 
             | [1] https://www.youtube.com/watch?v=ZYD9yyMh9Hk
             | 
             | [2] https://news.ycombinator.com/item?id=29104047
        
         | ThePhysicist wrote:
         | That's my observation as well. If you write typed Python code
         | (or Typescript) you need to constrain yourself to a small
         | subset of what the dynamic language can do. In some cases it's
         | beneficial but often it can be quite stifling, so not sure I'm
         | a big fan of gradual typing anymore.
        
         | sillysaurusx wrote:
         | There are a few ways to deal with that. One is to declare the
         | properties directly in the subclass:                 class
         | Inject:         def __init_subclass__(cls):
         | cls.injected = 42            class Child(Inject):
         | injected: int            print(Child().injected) # 42
         | 
         | This technique isn't too useful in practice, though it has its
         | place (e.g. when creating ctypes.Structures). But there's a
         | clever way to simplify this. Remember that the subclass is a
         | subclass -- it inherits from Inject:                 import
         | typing as t            class Inject:         injected:
         | t.ClassVar[int]              def __init_subclass__(cls):
         | cls.injected = 42            class Child(Inject):         pass
         | print(Child().injected) # 42
         | 
         | (ClassVar[int] ensures that type checkers know it's a variable
         | attached to the class, not each instance.)
        
           | zzzeek wrote:
           | yes you can do that, but if you want to do say, what
           | dataclasses does, where it collects all the Field objects and
           | creates a typed `__init__` method, typing can't do that
           | without plugins. all the current typecheckers hardcode the
           | specific behavior of "dataclasses" without there being any
           | pep that allows other systems (like ORMs) to take advantage
           | of the same thing without writing mypy plugins or simply not
           | working for other type checkers that don't allow plugins.
        
             | sillysaurusx wrote:
             | Yeah, that's true. Dataclass support is severely lacking.
             | Is there a pep on the horizon for this?
        
               | zzzeek wrote:
               | im not really sure (highly doubtful, much simpler ideas
               | like being able to make your own Tuple subclass are still
               | not ready for primetime) but this would be a pretty tough
               | pep to allow the general "dataclass" concept to be
               | available to other libraries that want to use the same
               | pattern, but not use dataclasses.
        
       | VWWHFSfQ wrote:
       | There are bugs in this code and I'm glad that I'm not the only
       | one that has done it!                   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()         }
       | 
       | 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.
        
       | 3pt14159 wrote:
       | 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.
        
       | echelon wrote:
       | I'm at a point in my career where I see "magic" and wince hard.
       | 
       | 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.
       | 
       | 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".
       | 
       | 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.
       | 
       | Python users tend not to behave this way, but seeing posts like
       | this requires me to urge caution.
        
         | radicalbyte wrote:
         | ..and there was me thinking that it was only Java which
         | suffered from that problem.
         | 
         | That kind of code is great if:
         | 
         | (a) you wrote it
         | 
         | (b) you knew what you were doing when you wrote it and
         | 
         | (c) you also wrote an extensive set of tests to cover the
         | entire domain of whatever you were doing.
        
           | tentacleuno wrote:
           | (d) and you used comments!
           | 
           | Comments are underestimated. I'm not saying they should be
           | used as an excuse to use "magic" esoteric code, but sometimes
           | such solutions are needed and it's much better to document
           | them for future maintainers.
        
         | jhgb wrote:
         | Wasn't the idea behind this to make _most_ code boring, and
         | have only a small part of it exciting? Just like when
         | structured programming was created, or even local variables
         | introduced, etc. (Of course, the exciting part had to be moved
         | into the compiler in those cases.)
        
       | scottwitcher83 wrote:
        
       | tsujamin wrote:
       | 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                 class
       | MyCustomOptionsFlow(OptionsFlow, DOMAIN="MYPLUGIN"):...
       | 
       | 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.
       | 
       | Very neat, not immediately obvious however :/
        
       | lvass wrote:
       | Am I just bad at OOP hackery or does anyone else look at this and
       | think it'd be extremely hard to debug?
        
         | foolfoolz wrote:
         | i wouldn't let this past code review. it's magic and makes a
         | great blog post about some feature but is not maintainable code
        
         | draw_down wrote:
        
         | pmarreck wrote:
         | Yep. As a current functional-language guy, this merely confirms
         | my suspicion that those crazy functional people were onto
         | something.
         | 
         | As it turns out, since the human mind is the real limitation
         | here, anything that stops runaway complexity ("hiding"
         | functionality behind "magic" is actually worse because you're
         | just moving the complexity "away from your mental model" where
         | it can fester and grow out of sight, even if what you see looks
         | "simpler") results in fewer bugs, and more-debuggable bugs.
        
         | jreese wrote:
         | I'm not fond of the example given in the post, but I've very
         | happily used __init_subclass__ in a few places where I
         | previously would have needed a decorator. Eg, for "plugin-like"
         | things where I would have otherwise needed a decorator to keep
         | track, or iterated over subclasses, now can just "register"
         | subclasses without any extra work on the child classes. Things
         | that I normally wouldn't expect to change the behavior of the
         | subclass, but where having a no-effort hook at subclass
         | creation time can be extremely useful.
        
           | sillysaurusx wrote:
           | I used to think so, but cls.__subclasses__() makes it
           | possible to just walk the type tree at some later point. I
           | find the lack of state to be a feature. (Normally with a hook
           | like you're talking about, you'd add the class to some list
           | somewhere, which can get out of sync.)
        
         | qsort wrote:
         | Frankly I doubt you even need metaclasses in the first place
         | (at least in non-library code).
         | 
         | I get that it's a dynamic language but if I need metaclasses
         | for something it's more likely than not to be a hidden
         | technical debt generator.
        
           | vosper wrote:
           | I think SQLAlchemy makes good use of metaclasses. It's the
           | only thing I've seen that used them in a way that I thought
           | made sense and was well justified (but I have been out of the
           | Python world for a while).
           | 
           | In another language it would be done with code-gen, which
           | would also be fine.
           | 
           | But SQLAlchemy is library code.
           | 
           | I took the time to understand how to use metaclasses, and
           | concluded that I never would, especially not in code I
           | expected another engineer to understand - I expect 90% of
           | people who write Python haven't heard of metaclasses, and 99%
           | have never written one.
        
             | nomel wrote:
             | There was a time, early on in my python career, that I
             | tried all the different flavors of magic that python
             | provides. These days, I tend to write code for junior
             | developers. I don't use any magic at all, even when it
             | could make sense.
             | 
             | Once you start playing with magic, you have far fewer eyes
             | and hands that are interested.
        
               | nightski wrote:
               | In my opinion it's not about dealing with junior
               | developers or even having the ability to understand it.
               | I've been programming for 25 years and to me it's about
               | cognitive load. I despise tricks like this because they
               | put a much higher strain on the programmer. Software is
               | hard enough, let's not make it harder.
               | 
               | If I can look at a piece of code and it's quickly obvious
               | whether it is correct or not is my favorite type of
               | code...
        
         | formerly_proven wrote:
         | Metaclasses / __init_subclass__ are generally only used if you
         | try to define different / new behavior for how the body of a
         | class is interpreted. So if you'd want to create e.g. an
         | explicit interface system in Python, you might use them. If
         | you're creating an ORM or some other kind of declarative
         | property system, you might use them (but not necessarily, as
         | descriptors are a thing). I think the only use in the standard
         | library is in ABCs and probably the typing module.
         | 
         | Edit: Enums use them as well (makes sense).
        
         | klodolph wrote:
         | It's used very sparingly in the wild, for cases where you need
         | a bunch of classes that are set up similarly, and ordinary
         | subclassing still results in too much boilerplate.
         | 
         | For example... you might make a class for each HTML tag in an
         | HTML processor, or each drawing command in an SVG processor. My
         | experience is that it's not hard to debug, because all that
         | you're doing is getting rid of some boilerplate.
        
         | Traubenfuchs wrote:
         | I am baffled that people make fun of Java and Spring and then
         | use double underscore magic methods called __init_subclass__
         | with a straight face.
        
       ___________________________________________________________________
       (page generated 2022-01-05 23:00 UTC)