Mmm. Changing control flow based on introspection of the call stack is reasonably nasty.
I recall we found a neat use for call stack introspection in one project - not to change control flow but to improve logging. Inside an open_db_connection utility function, to auto-generate an informational name describing which part of the codebase had opened the connection:
E.g. some_backend_process: main -> ... > grandparent -> parent -> open_db_connection
We'd use information from the call stack generate a string "some_backend_process grandparent.parent" describing the call site which could be passed to postgres as the application_name [1] when establishing a connection. Then from the database side, if we had problematic queries at runtime, we had a lot more clues to figure out which part of the backend codebase was responsible [2].
There'd be a way to do something equivalent without peeking at the call stack, by requiring the caller to explicitly pass a descriptive & unique name, but that makes things a bit more error-prone, especially since some devs would be prone to copy-pasting chunks of existing code & reusing the same name everywhere.
Funny, my logging module for Python does the same (if requested with an optional keyword argument). It fetches the call stack, removes it's own functions, replaces Python library function chains with a single [...] and prepends it to the log output (together with usual date and time).
ariebovenberg 2 days ago [-]
Author here. I ran into this while comparing Pendulum with `whenever`, the datetime library I maintain. I sent a patch that removes the hack, but the post is about why the underlying design can't be patched the same way. Happy to answer questions.
LoganDark 2 hours ago [-]
Did you use LLM to write parts of the article?
> The trouble with guessing who’s calling is that you can’t anticipate every caller. Pendulum’s hard-coded check is fragile: it covers only one name, and relies on an exact call stack that Pendulum doesn’t control.
Edit: it looks like the later parts of the article show a lot of LLM tells, but not the beginning. I've been seeing this pop up more and more lately...
seki285 1 hours ago [-]
This is impressive, because I had no idea and now I feel angry at having my time wasted! If this type of stuff increases in frequency and I can't easily tell if an article is AI or not, I will stop reading them.
awildfivreld 1 hours ago [-]
Up until "Each guess may look reasonable on its own. But guessing itself has four problems." I read the occasional tell, but not much.
By the end of that paragraph thereafter I started skimming as the rest turned blatantly LLM.
I should have caught the emdash in the first code block in "Who else is calling" though...
throwaway89201 2 hours ago [-]
> but not the beginning
You didn't stop reading after "The hack itself is on its way out, but the design behind it still matters for your code"? I did.
LoganDark 45 minutes ago [-]
I meant the beginning didn't show a lot! I don't have exactly zero tolerance because sometimes there's enough signal in the slop, my point was mainly that the signal seems to sharply drop off after a certain point in this article. (I've complimented authors before for managing to keep an article relatively information-dense despite having a lot of clear LLM tells. That shows care, and so I don't object to it.)
I have a suspicion that in these cases, either people are purposefully copyediting only the very beginning, or the very beginning has most of the prompt and the rest is pure extension.
This is used when cross compiling binary wheels (e.g., building a macos wheel on a linux build host or vice versa).
jonathaneunice 2 hours ago [-]
OMGeeeee!
I too have plumbed the call stack for hidden-maybe-forbidden knowledge from the caller, and it is always fraught. No matter how good one's intentions. Forget dynamic type testing. Forget Perl's `wantarray` dynamic introspection of "what does the caller want?" Those are small potatoes. This is full on "who's calling me and what do I want to send back as a result?" The people who will never forgive you for type testing are going to straight-up excommunicate whoever wrote this! And yet...it makes complete sense. Based on the promises made, you have to do something... and you're already in "what is the least of the evils?" territory, because you're going to have to do something that someone—maybe a lot of someones—will call evil.
My admiration for Pendulum just increased. This is sin-eating.
leni536 2 hours ago [-]
It's the promises that don't make sense, although that might be obvious in hindsight.
aeturnum 1 hours ago [-]
I can't provide any code samples, but this only strikes me as medium-cursed as far as python code goes. That stuff can get nasty! Once a team realizes they can modify anything at runtime things can go bad quite quickly.
Nice hack for maintaining old behavior tho!
Philpax 2 hours ago [-]
must admit that my first association was with the drum and bass band
groestl 1 hours ago [-]
Same here, and I thought it was about their visuals or something
david2ndaccount 2 hours ago [-]
"In the face of ambiguity, refuse the temptation to guess."
btown 1 hours ago [-]
Another cursed approach here that avoids stack walking, but is almost as bad:
Say you want to rely on a library superclass's foo() implementation, and add some pre/post-processing logic... but in turn that superclass calls another self.bar() (or recurses into self.foo()) and you want to route that to the superclass rather than to self.
One possibility: copy in the superclass implementation and change the call sites. But now you lose the ability to get new features from the superclass if the library makes updates/bugfixes.
Another possibility, as the author suggests: make a new object that's just the superclass, have it run its logic, and bring in the state. But maybe the state itself is massive, or otherwise not something you want to copy (say, there's some kind of RAII).
Another possibility: fork the library, put your fork on a package manager, and have an AI agent maintain your fork for you, forever and ever and ever.
The cursed/blursed possibility: set a flag in a threading.local() that we're currently processing foo(). Superclass ignores this. But when we get to our other logic, we check self._mybrand_processing_locals and route accordingly.
At the end of the day, the stack state is just a threading.local(). Why not cut out the middleman? /s
I recall we found a neat use for call stack introspection in one project - not to change control flow but to improve logging. Inside an open_db_connection utility function, to auto-generate an informational name describing which part of the codebase had opened the connection:
E.g. some_backend_process: main -> ... > grandparent -> parent -> open_db_connection
We'd use information from the call stack generate a string "some_backend_process grandparent.parent" describing the call site which could be passed to postgres as the application_name [1] when establishing a connection. Then from the database side, if we had problematic queries at runtime, we had a lot more clues to figure out which part of the backend codebase was responsible [2].
There'd be a way to do something equivalent without peeking at the call stack, by requiring the caller to explicitly pass a descriptive & unique name, but that makes things a bit more error-prone, especially since some devs would be prone to copy-pasting chunks of existing code & reusing the same name everywhere.
[1] https://www.postgresql.org/docs/current/libpq-connect.html#L... [2] https://www.postgresql.org/docs/current/monitoring-stats.htm...
> The trouble with guessing who’s calling is that you can’t anticipate every caller. Pendulum’s hard-coded check is fragile: it covers only one name, and relies on an exact call stack that Pendulum doesn’t control.
Edit: it looks like the later parts of the article show a lot of LLM tells, but not the beginning. I've been seeing this pop up more and more lately...
By the end of that paragraph thereafter I started skimming as the rest turned blatantly LLM.
I should have caught the emdash in the first code block in "Who else is calling" though...
You didn't stop reading after "The hack itself is on its way out, but the design behind it still matters for your code"? I did.
I have a suspicion that in these cases, either people are purposefully copyediting only the very beginning, or the very beginning has most of the prompt and the rest is pure extension.
This is used when cross compiling binary wheels (e.g., building a macos wheel on a linux build host or vice versa).
I too have plumbed the call stack for hidden-maybe-forbidden knowledge from the caller, and it is always fraught. No matter how good one's intentions. Forget dynamic type testing. Forget Perl's `wantarray` dynamic introspection of "what does the caller want?" Those are small potatoes. This is full on "who's calling me and what do I want to send back as a result?" The people who will never forgive you for type testing are going to straight-up excommunicate whoever wrote this! And yet...it makes complete sense. Based on the promises made, you have to do something... and you're already in "what is the least of the evils?" territory, because you're going to have to do something that someone—maybe a lot of someones—will call evil.
My admiration for Pendulum just increased. This is sin-eating.
Nice hack for maintaining old behavior tho!
Say you want to rely on a library superclass's foo() implementation, and add some pre/post-processing logic... but in turn that superclass calls another self.bar() (or recurses into self.foo()) and you want to route that to the superclass rather than to self.
One possibility: copy in the superclass implementation and change the call sites. But now you lose the ability to get new features from the superclass if the library makes updates/bugfixes.
Another possibility, as the author suggests: make a new object that's just the superclass, have it run its logic, and bring in the state. But maybe the state itself is massive, or otherwise not something you want to copy (say, there's some kind of RAII).
Another possibility: fork the library, put your fork on a package manager, and have an AI agent maintain your fork for you, forever and ever and ever.
The cursed/blursed possibility: set a flag in a threading.local() that we're currently processing foo(). Superclass ignores this. But when we get to our other logic, we check self._mybrand_processing_locals and route accordingly.
At the end of the day, the stack state is just a threading.local(). Why not cut out the middleman? /s