“[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, Inventor of the timsort algorithm and prolific Python contributor
https://www.oreilly.com/library/view/fluent-python/978149194...
https://en.m.wikipedia.org/wiki/Tim_Peters_(software_enginee...
I would then also concur with the other comment that if you “know” you need metaclasses, 99% of the time actually you only need __subclass_init__.
A lot of online literature about Python meta programming misses out __subclass_init__ as it was only added to Python 3.6 in 2015 via PEP 487.
I was only around for one of them while he did it but it seemed pretty clear that he was insecure and trying to prove that he was a more experienced programmer by using the most advanced features.
I call it "the advanced beginner metaclass trap".
In one case I think the author did believe he needed them, or was going to. He didn't, but I think he believed it was the best way to go about the inheritance he was doing.
The other case was exactly what you say. The person wanted to show how advanced their knowledge of Python was. The funny part about the second case (which included every advanced or new Python feature under the sun, including the walrus operator shoehorned in for good measure) the code simply didn't work at all. It ran but was wrong. The author was so busy demoing his advanced knowledge he forgot to make it work. And was unable to debug it in a timely fashion due to his overuse of abstractions and features. So less "advanced" me was brought in to make it work. Which I did.
We do lots of Python metaprogramming at Mito [1], but generally avoid all of this fancy Python fluff to get it done. Specifically, we avoid metaclasses, invisible decorators, etc. Instead, we take a much simpler approach of having Python code that literally populates as template .py file, and then writes it to the correct location in our codebase.
As a concrete example: we’re a spreadsheet, so we let our users transform data in a bunch of different ways - adding a column, writing a formula, deleting some rows. Anytime I want to add a new transform (say, encoding a column), I tell the metaprogramming package “python -m metaprogramming step —name “Encoding A Column”. It will ask me some questions about the parameters and types of those parameters, and then write most of the 4-6 boilerplate Python and Typescript files I need automatically! You can see it here [2].
This is still metaprogramming (it’s certainly code that writes code). But the code you end up with at the end of the day is very simple Python code that is extremely easy to understand / maintain long-term.
I’ll pass on the fancy stuff for now. Thanks though!
[2] https://github.com/mito-ds/monorepo/blob/dev/mitosheet/dev/c...
Like let's say you want to add a new required property to every type of something... like you want every encoding to have an accepts_tainted_value property. You don't want a default because you want to be forced to think through every case.
Can you regenerate the code? If so, are you keeping the "real" object specification in some other structure? Or do you just change a superclass to make the property required then fix the generated source based on the errors you get?
https://docs.python.org/3/reference/datamodel.html#object.__...
And metaclasses can be any callable, not just class. It's usually better to use a simple function.
And with the recent addition of new syntax, I would hardly call Python easy.
Most python programmer use 10% of the language, the same 10% that is described in most tutorial, because that's enough, and they don't even know the rest exist.
This makes for a very smooth, but long learning curve which allow you to enjoy python in the early years, yet keep getting a kick out of it after 15 years.
I hear a lot of people raising the same kind of concern you do in comments, but in the field, I never do with people actually using the language.
Once you get thrown in a big project that makes heavy use of type hinting (plus the whole environment of it, mypy et all), Object Oriented design and all those hidden things...you realize how much you really don't know.
And that 10% is way too small. To be productive in Python, you need to know at least 50%, or you will be reinventing a lot of existing things. Poorly.
But that's exactly what that expression means?
A car with an automatic transmission is "easy to learn and use", but an automatic transmission is way more complicated to construct than a manual one.
The comparison makes no sense.
class A():
@property
def a(self):
raise Exception('go away')
a = A()
then hasattr(a, 'a') will return False in Python 2.7 (and throw as expected in Python3)A true WTF moment, the tests and the API have been slightly broken for years, without anyone noticing.
You can do these things in the extremely rare cases that you must, but other than that you shouldn’t.
It's so easy to learn & use that children manage to be productive with it (the Roblox community).
It's still powerful though. With few enough primitives to remain simple & understandable, it has just enough primitives to build anything. People have written web frameworks, window managers and video games in Lua.
It's got less pitfalls, "voodoo magic" and advanced weirdness than any other language I know. You can learn 90% of the language from its Wikipedia page. Every time I encounter some of Python's internal weirdness, I wish Lua had been the scripting language of the 2000s instead. Unfortunately Python has a vast collection of libraries available that keep us using it instead.
*and by easy there I mean streamlined and more intuitive on most expected "common" tasks than the alternatives
But even for that, it's not a toy language as clearly evident by this example.
>>> class SomeClass:
... pass
...
>>> type(SomeClass)
<class 'type'>(Mine didn't have it, and I've been ruing it, and compensating for it, ever since.)
We define the instance as: someobject = SomeClass()
but then we refer to it as someobj and some_object
Python is powerful and flexible enough that you don't need metaprogramming.
(I mean this quite literally: I sincerely doubt that there any code in Python using metaclasses etc., that wouldn't be more clear and maintainable if rewritten in "plain old" Python without them.)
(With the caveat that I'm not including "art" projects, I'm talking about working production code.)
(In case it's not clear, this is one of those "Prove Me Wrong" scenarios... If you think you have a counter-example to my claim, please don't keep it to yourself, "Shout it out so the whole theatre can hear you!")
Django using metaclasses is exactly the problem I'm warning of...
You don't need metaclasses for data models nor for HTML forms. Nothing Django does requires metaclasses, and it's actively beginner-unfriendly to use them.
Metaprogramming done wrong, no matter the mechanism, is very painful. So, I'm not sure which way is better.
I do not see a single new keyword being defined. I do not see anything changing the order of evaluation. Only to give 2 examples.
Recently I implemented a new kind of "define" in a Scheme, which allows for specifying contracts for a function. I don't think such a thing is possible using the tools shown in the article. As such, the things shown in the article are not really that meta, but rather parts of the Python language already. It is not like it is adding anything to Python, which was not there. It is just usage of some concepts Python already has. This is conceptually different from transforming source code to other source code, which then creates a new concept in the language itself. It is not like I can mold Python into whatever I want, but rather stay in the corset of the language facilities. Which of course is not as nice as lispy languages with good macro system, when it gets to creating DSLs and other conveniences.
So overall I am not so blown away by the article.
Python recently got a form of structural pattern matching.
Can that be removed from Python, and then reimplemented only in Python? I mean other than by writing a .py file to .py file text filter?
Can it be backported to a prior version of Python?
Python's release history is full of "can't use this syntax if you're not on at least x.y.z version"; it continuously proves that it has it doesn't have metaprogramming on a level that could be used to develop the language. When the purveyors of Python decide to add some new syntax and semantics, they eschew Python metaprogramming and dive straight into C.
https://github.com/irtnog/salt-states/blob/production/_modul...
The SaltStack module starts out empty except for a function that runs at module load time. The initialization function queries PowerShell for all AD FS-related cmdlets and creates Python wrapper functions for them. It even copies the cmdlet's help to the function's docstring.
You can use some metaprogramming to create very clean interface points in python! I always wondered how django did so much with very clean readable implementations for end users.