The first line of the code in the function, 'func', below is a print statement. It prints/generates the following sort of cross between a tuple and a dictionary, (to the right of the first equals sign) containing all func’s parameters along with the default values for those parameters:
str(inspect.signature(func)) = (a='my', b='dog', c='chews', d='bones')
I want to modify the print statement on the first line of 'func' (as distinguished from print Statement #2 far below):
- So that each time 'func' is called, the print statement prints the names of the parameters along with: [i] the supplied values where they are different from the parameters’ default values, and [ii] the default values if no other values are supplied; and
- By replacing the string 'func' with a code variable that represents the function object, where the function object is the function in which the code/variable is located (in this case, 'func'), as an object. To illustrate, assume X = PSEUDO_CODE_REPRESENTING_THIS_FUNCTION_OBJECT, and that the following line is the first line of code in the function:
print('str(inspect.signature(X) = ' + str(inspect.signature(X))
Here is the function, 'func' (containing a print statement on the first line of 'func') and, following the definition of 'func', a second print statement (on the last line of the code) supplying non-default values to 'func':
def func(a='my', b='dog', c='chews', d='bones'):
print('str(inspect.signature(func)) = ' + str(inspect.signature(func)))
return a + ' ' + b + ' ' + c + ' ' + d
print('Statement #2: ' + func(a='her', b='cat', c='drinks', d='milk'))
Any suggestions would be much appreciated, preferably with a link or some explanation.
What you should do
In a normal context, you should absolutely do this with a decorator, because what you're trying to do is a really bad idea and requires messing with the grittier parts of Python, whilst also being the pinnacle of spaghetti code.
Here's a decorator that does the above:
However, that's no fun!
What you shouldn't do
Python uses frames to implement the call stack, and provides a convenient library for fiddling with them, which you've already come across - the
inspectlibrary.inspect.currentframereturns aFrameInfoobject, which will give us the frame in which that was called - in this case, the frame representing our function.We then rip out the
f_codeattribute of that frame, which is a code object - the meat behind every function. However, a code object isn't a function yet, and you asked for one. Irritatingly, there's no way to pull the default arguments out of a frame.We get around this by just jumping one up the call stack, and using the name we get from
f_code.co_nameto access thef_localsof the caller. This gets us our function.Here's an implementation:
That's pretty long-winded though, and you wanted an expression.
Problem is, that's completely unreadable. We could make a helper function!
That's probably the best solution, even if it's still a total crime against Python. In code, just use the decorator above.
As a final note, you don't actually need
inspectto get the current frame - you can do shenanigans with raising an error and using the traceback to get the frame. If we were doing other manipulations thaninspect.signature, it would save us an import, which would be nice. It's also unreadable, so don't do this. Here it is anyway, for educational purposes.Be careful that you clear your frames, also - if you don't, it can create reference cycles, and memory leaks are bad juju.
All of this is CPython specific, and will break when the API is updated, which is regularly.
Just use the decorator.