Package: executing
executingThis mini-package lets you get information about what a frame is currently doing, particularly the AST node being executed.UsageGetting the AST nodeGetting the source code of the nodeGetting the__qualname__of the current functionThe Source classInstallationHow does it work?Is it reliable?Which nodes can it identify?Libraries that use thisUsageGetting the AST nodeimportexecutingnode=executing.Source.executing(frame).nodeThennodewill be an AST node (from theaststandard library module) or None if the node couldn't be identified (which may happen often and should always be checked).nodewill always be the same instance for multiple calls with frames at the same point of execution.If you have a traceback object, pass it directly toSource.executing()rather than thetb_frameattribute to get the correct node.Getting the source code of the nodeFor this you will need to separately install theasttokenslibrary, then obtain anASTTokensobject:executing.Source.executing(frame).source.asttokens()or:executing.Source.for_frame(frame).asttokens()or use one of the convenience methods:executing.Source.executing(frame).text()executing.Source.executing(frame).text_range()Getting the__qualname__of the current functionexecuting.Source.executing(frame).code_qualname()or:executing.Source.for_frame(frame).code_qualname(frame.f_code)TheSourceclassEverything goes through theSourceclass. Only one instance of the class is created for each filename. Subclassing it to add more attributes on creation or methods is recommended. The classmethods such asexecutingwill respect this. See the source code and docstrings for more detail.Installationpip install executingIf you don't like that you can just copy the fileexecuting.py, there are no dependencies (but of course you won't get updates).How does it work?Suppose the frame is executing this line:self.foo(bar.x)and in particular it's currently obtaining the attributeself.foo. Looking at the bytecode, specificallyframe.f_code.co_code[frame.f_lasti], we can tell that it's loading an attribute, but it's not obvious which one. We can narrow down the statement being executed usingframe.f_linenoand find the twoast.Attributenodes representingself.fooandbar.x. How do we find out which one it is, without recreating the entire compiler in Python?The trick is to modify the AST slightly for each candidate expression and observe the changes in the bytecode instructions. We change the AST to this:(self.foo**'longuniqueconstant')(bar.x)and compile it, and the bytecode will be almost the same but there will be two new instructions:LOAD_CONST 'longuniqueconstant'
BINARY_POWERand just before that will be aLOAD_ATTRinstruction corresponding toself.foo. Seeing that it's in the same position as the original instruction lets us know we've found our match.Is it reliable?Yes - if it identifies a node, you can trust that it's identified the correct one. The tests are very thorough - in addition to unit tests which check various situations directly, there are property tests against a large number of files (see the filenames printed inthis build) with real code. Specifically, for each file, the tests:Identify as many nodes as possible from all the bytecode instructions in the file, and assert that they are all distinctFind all the nodes that should be identifiable, and assert that they were indeed identified somewhereIn other words, it shows that there is a one-to-one mapping between the nodes and the instructions that can be handled. This leaves very little room for a bug to creep in.Furthermore,executingchecks that the instructions compiled from the modified AST exactly match the original code save for a few small known exceptions. This accounts for all the quirks and optimisations in the interpreter.Which nodes can it identify?Currently it works in almost all cases for the followingastnodes:Call, e.g.self.foo(bar)Attribute, e.g.point.xSubscript, e.g.lst[1]BinOp, e.g.x + y(doesn't includeandandor)UnaryOp, e.g.-n(includesnotbut only works sometimes)Comparee.g.a < b(not for chains such as0 < p < 1)The plan is to extend to more operations in the future.Projects that use thisMy Projectsstack_data: Extracts data from stack frames and tracebacks, particularly to display more useful tracebacks than the default. Also uses another related library of mine:pure_eval.futurecoder: Highlights the executing node in tracebacks usingexecutingviastack_data, and provides debugging withsnoop.snoop: A feature-rich and convenient debugging library. Usesexecutingto show the operation which caused an exception and to allow theppfunction to display the source of its arguments.heartrate: A simple real time visualisation of the execution of a Python program. Usesexecutingto highlight currently executing operations, particularly in each frame of the stack trace.sorcery: Dark magic delights in Python. Usesexecutingto let special callables called spells know where they're being called from.Projects I've contributed toIPython: Highlights the executing node in tracebacks usingexecutingviastack_data.icecream: 🍦 Sweet and creamy print debugging. Usesexecutingto identify whereicis called and print its arguments.friendly_traceback: Usesstack_dataandexecutingto pinpoint the cause of errors and provide helpful explanations.python-devtools: Usesexecutingfor print debugging similar toicecream.sentry_sdk: Add the integrationsentry_sdk.integrations.executingExecutingIntegration()to show the function__qualname__in each frame in sentry events.varname: Dark magics about variable names in python. Usesexecutingto find where its various magical functions likevarnameandnameofare called from.