Python has no standard way to check if a decorator already been applied to a callable, and some of the methods to check are a bit cumbersome. Additionally, decorator placement matters when descriptors come into play.
# Works: trace gets a plain function
@classmethod
@trace
def bar(cls): ...
# Breaks: trace gets a classmethod object
@trace
@classmethod
def bar(cls): ...
Ideas
__decorators__: a registry of decorator function references for easier tracking. functools.idempotent: a decorator helper (or helper decorator?) that makes wrappers idempotent and descriptor-aware.
I didn’t have time to really go through this proposal, but a couple thoughts:
It’s fairly common for function decoration to involve returning a function object with some additional attributes.
In my opinion, setting attributes on objects outside of __init__ is a code smell. One problem is that it nearly always fails type checking when you go to look for that attribute.
Therefore, I think the idea of having a dictionary on all functions to be beneficial. It would provide an obvious place to set and look for various things.
I’m not sure I like the idea of making this a dunder. Why not just make a catch-all for function attributes like function_attrs? Or something like that, and make it a dictionary dict[str, Any]. When coding a function decorator, you can invoke some other decorator to lift the function_attrs dict from the old function and augment it in the way you want.
So you’re saying both that stale IDs would be a problem and that stale IDs aren’t possible? I’m confused. What’s your point?
Did you get an LLM to generate this text?
Yeah, that’s the problem with AI-generated proposals. They’re incredibly long, and we have to spend more effort reading them than the OP put in creating them.
If I understand correctly the proposal to attach a frozenset after each decoration, it assumes that each decoration wraps the callable. But this is not always the case. Many decorators just register the callable and returns it unmodified like e.g. atexit.
The point is that storing references is safer than storing IDs in principle. If one module is reloaded and others aren’t, what happens to the objects that were in the reloaded module? Could IDs be reused if those object were garbage collected?
I would also like to know whether the original proposal was LLM-generated. You’re clearly interacting now, but the original post is very like LLM-generated content (and in particular is hard to read and understand), so at a minimum, I’d like you to rewrite the proposal (in your own words, if it was LLM-generated) to be more concise and to the point.
Even if you feel that questioning whether a proposal was LLM-generated is a “tangential accusation”, it is a legitimate question, and it’s extremely easy for you to answer with a minimal “yes”, or “no”. Why have you not done so? Are you concerned that people will attack you for using an LLM? Personally, I don’t care what tools you use to help you produce a proposal, but I do care that the version you post is easy to understand and discuss, and content that LLMs produce nearly always isn’t, without significant human editing. I’d find it a lot easier to know how to advise you on improving your proposal if I knew whether what I was seeing was your writing style, or an LLM’s. (At least, if I were to offer anything more concrete than "don’t write your proposal in a way that looks like an LLM ).
… A very simple piece of advice - start by posting your idea as if you were talking casually to someone. When you’re initially exploring an idea, a formal proposal with headings and bullet points is generally offputting, rather than helpful. Formalising the proposal comes later, when you have something worth writing up as a PEP. Until then, keep things informal.
I also want to point out that the proposal contains critical flaws in it’s idea that a human would have caught - making me believe that it’s mostly AI generated with little oversight. Most notably, this statement is just false:
Same convergence for staticmethod, and for multiple idempotent-wrapped decorators stacked in any order.
It just doesn’t and can’t do the latter. (it doesn’t even try)
If you want to make a proposal, at the very least make sure that it actually does what you say it does.
Cool. If those are “tangential accusations”, then I take it you’re admitting that it WAS generated by an LLM, and your proposal has no merit. Good to know.
So you actually don’t understand the text you posted.
The section I quoted is under “Order Independence”, that is the property being discussed. No, the code does not guaranteed order independence. Yes, it does guarantee only-once execution if all decorates in the chain are cooperative.
By order independence I was referring to the set of recorded decorators, not the execution order of the wrappers. That’s probably an important distinction to make.
Thanks (although you might have been better re-posting the proposal - editing the original post is generally considered bad etiquette, because people who read this site via the email interface won’t see the changes and will be confused because comments won’t refer to the text they think was posted).
Anyway, the one glaring problem here is that you’ve offered no examples at all of why anyone would care if a decorator has been applied. There’s no point adding this mechanism to Python unless it’s useful and you’ve given no indication that it would be. Just because there isn’t a way to check, doesn’t mean there should be - you have to demonstrate the need.
Also, how would __decorators__ be maintained? Just by your idempotent function? Because if so, you can write that now and don’t need a language change. On the other hand, if the language runtime is supposed to maintain it, how do you address the fact that by design decorator syntax is defined to just be application of the decorator to the function object? So, for example:
@deco
def fn(x):
...
is by definition equivalent to
def fn(x):
...
fn = deco(fn)
You can’t have an arbitrary assignment suddenly try to look at __decorators__, so anything that made the decorator syntax do so would be a breaking change, removing the existing equivalence.
Maybe I’m more interested in why descriptors were implemented as decorators, since they don’t play nicely with programmatic application of other decorators. I’ll have to go dig up the PEP to figure out the rationale.
What if you want to decorate every public callable on a class, but one of those methods already has that decorator applied? Should you have to manually remove the decorator from that method? My argument is no, since applying the same decorator to an object that already has it is almost always an error.
I also don’t think that one should have to unwrap descriptors to apply decorators to methods with those descriptors. Nor should one have to worry about the order of decorators and descriptors. The fact that things break when decorators are applied over descriptors is certainly surprising.
To be fair you have also mistaken people with English as a second language as just one example. This is a frequent point of yours not a once off, and it is perfectly reasonable to push back with the frequency you deploy it.