Printing the name of the function and who called it?

In the process of converting a lot of spaghetti code to organized script modules and multiple functions. One drawback I’ve noticed to this is that sometimes the error messages are truncated and not as helpful or specific as if the script was on the button onAction script. I will just get errors that show a Key I tried access in a dictionary that doesn’t exist, or a type error, but no line number or even what function this occured in. I am trying to mediate that.

What I am looking for is some function I can call in the beginning of other functions so that it will print out the current function and what function called it if possible.

I tried doing it with using the inspect module per this and doing this in a function called from another function

import inspect
print "running " + str(inspect.stack()[1][3]) + " called by " + str(inspect.stack()[2][3])

which just throws me a error like so
image

I assume this has to do with the odd runtime environment Ignition is, java calling python scripts etc and so I’m not sure if there is a python stack to really access like there would be in a traditional all python application.

Is there another way to do this? I would really like a function I could call as the first line of other functions that will just print “Running function (function name), called by (function name)”

You might find inspiration in the PythonAsJavaException class in my later.py script module.

How I would in practice use this?

You’re talking a bout this snippet right ?

#------------
# Java wrapper for Jython exceptions to preserve the python
# stack trace.
#
class PythonAsJavaException(java.lang.Exception):

    @staticmethod
    def fullClassName(cls):
        if cls.__bases__:
            return fullClassName(cls.__bases__[0])+"."+cls.__name__
        return cls.__name__

    def __init__(self, pyexc, tb=None):
        super(PythonAsJavaException, self).__init__(str(pyexc), None, True, True)
        traceElements = []
        if tb is None:
            tb = sys.exc_info()[2]
        while tb:
            code = tb.tb_frame.f_code
            vnames = code.co_varnames
            if vnames and vnames[0] in ('cls', 'self'):
                ref = tb.tb_frame.f_locals[vnames[0]]
                if vnames[0] == 'self':
                    className = fullClassName(ref.__class__)
                else:
                    className = fullClassName(ref)
            else:
                className = '<global>'
            traceElements.append(StackTraceElement(className, code.co_name, code.co_filename, tb.tb_lineno))
            tb = tb.tb_next
        self.setStackTrace(traceElements)

It’s a bit over my head - how could I use implement say in a situation like this for a module lets call barcodes?

def create(data):
    import barcodes
    # something here to print the module.create is running, called by GUI
    barcodes.foo()

def foo():
    # something here to print barcode.foo is runnig, called by create
    x = 1

Now say I ran barcode.create(data) from my GUI, how would I use your class to have printed to the console

running barcode.create called from ? 
running barcode.foo called from barcode.create

You wouldn’t use my class. I said inspiration. I would use python/jython’s exception machinery to generate a traceback. With raise inside a try block, if not directly. The traceback has the information you want. That class is an example of traversing it.

1 Like

I’m also in need of something like this…
@bkarabinchak.psi Did you ever get it working?

You can use sys._getframe(1).f_code.co_name to get the caller's name.
I'd make a decorator for this, something like

import sys

def get_caller(func):
	def wrapper(*args, **kwargs):
		print("{} called by {}".format(func.__name__, sys._getframe(1).f_code.co_name))
		return func(*args, **kwargs)
	return wrapper
@get_caller
def foo():
	bar()

@get_caller
def bar():
	pass

foo()

foo called by <module>
bar called by foo

2 Likes

OK, thank you for that…
But I probably don’t understand all this … sh*t… :innocent:

If I put this in the button actionPerformed script, I get

foo called by <module>
bar called by foo

But from where is foo called…? What does <module> means?
I’d expect to see something like … button’s name.actionPerformed…instead of <module>?

There might be ways to make it more accurate (get the module’s name, things like that…) but I have no idea how all this interacts with Ignition’s components. You might need to call upon Java for this, I don’t know.

OK, maybe uncle @pturmel can share some… samples…? :pray:

No, I gave up and just hand traced how the code was being run, now I just judiciously use a logger where needed, logger.trace(“Running function x”) is the first line of any function I care to know and use logger.info()'s at important junctions. I also restructured my code so it’s not really a mystery who’s calling what anymore so I didn’t need this anymore. Sorry can’t be more helpful

I imagine <module> refers to the scripting module you have your functions in but I am not an expert here.

1 Like

If you create a java exception instance you can get the java call-chain that ran the jython script, but you won’t get a specific component instance. It does generally reveal a component type, which can narrow down your search. Most of Ignition’s event machinery yields <module> when the source is an anonymous script.

You have to address this in your design pattern:

  • events should have single-line scripts that delegate to a project library function,
  • event or event.source should always be an argument passed in,
  • the library function should wrap the entire body in a try catch catch block–java and jython flavors,
  • and the handler should create a new exception that identifies the component, including the original exception as the cause.

There is no other magic.

4 Likes

Phil,

Are there any specific reasons to do this other than organization, code reuse, exception handling etc...? I'm rolling through a large library of components and moving the logic as much as I can into a project library function for basically just organization. I was just curious if there were other benefits on the java/jython/Ignition side of things that we might see as a side benefit.

The biggest reason is to avoid legacy scoping rules. If you search the forum you can find tons of examples of the different ways in which legacy scoping creates issues.

To sum things up succinctly:

3 Likes

Thanks! That makes sense.

We’ve never run into that exact issue so we’ve been running along w/o issues on event scripts. We’ve been going through fixing up our standards and just wanted to have a bit more of an explanation for future users.

I've had reason to dig into this as of late for similar reasons to the OP. had started using logger.trace(traceback.format_stack(limit=1)) and was wondering what better options might exist. My research led me to these ways of programmatically obtaining the current function name and the name of the function that called it. I

# Options to get name of current function
fname = traceback.extract_stack(limit=1)[0][2]
fname = sys._getframe(0).f_code.co_name
fname = inspect.stack()[0][3]
fname = inspect.currentframe().f_code.co_name

# Get name of calling function
fname = traceback.extract_stack(limit=2)[0][2]
fname = sys._getframe(1).f_code.co_name
fname = inspect.stack()[1][3]
fname = inspect.currentframe().f_back.f_code.co_name

I attempted to benchmark the performance of the different options (my first time trying this), these results were representative.

Method Loops Milliseconds
traceback.extract_stack(limit=1)[0][2] 100 1.846
sys._getframe(0).f_code.co_name 100 0.082
inspect.currentframe().f_code.co_name 100 0.053
inspect.stack()[0][3] 100 8,355.998

I removed inspect.stack()[0][3] from further testing as it was sooo slow.

Method Loops Milliseconds
traceback.extract_stack(limit=1)[0][2] 100,000 637.639
sys._getframe(0).f_code.co_name 100,000 23.272
inspect.currentframe().f_code.co_name 100,000 17.674
Method Loops Milliseconds
traceback.extract_stack(limit=1)[0][2] 1,000,000 6,399.593
sys._getframe(0).f_code.co_name 1,000,000 268.101
inspect.currentframe().f_code.co_name 1,000,000 226.340

inspect.currentframe().f_code.co_name does appear to be fastest for getting current function name. It does require importing inspect for each module where I want to use it and when toying with that in script console the import does appear to add a notable delay.

An argument against using sys._getframe(0).f_code.co_name is that the leading underscore in the function name indicates that it is private, intended for internal use and should not be directly called by users outside the module or class.

I might rewrite the OPs code as

import inspect
LOGGER = system.util.getLogger("path.to.module")
...
def myfunc():
	LOGGER.tracef("running %s %s called by %s %s " , 
	  inspect.currentframe().f_code.co_name
	, inspect.currentframe().f_code.co_filename
	, inspect.currentframe().f_back.f_code.co_name
	, inspect.currentframe().f_back.f_code.co_filename
	)
...

But that is so bulky I might just stick with:

import traceback
LOGGER = system.util.getLogger("path.to.module")
...
def myfunc():
	LOGGER.trace(traceback.format_stack(limit=1))
	...

Any drawbacks to using inspect or 'traceback' and more-or-less importing it in all my modules so I can use it in trace/debug log entries?

1 Like

If speed is important to you, you should use system.reflect.asThrowable() to convert jython exceptions into java-compatible exceptions. Then you can use the logger method variants that take a throwable object and expand it into the logs for you, efficiently.

If you cannot use my Integration Toolkit, similar functionality is available in later.py.

I'm late to this post, but I like this approach from an efficiency standpoint. I can put all my expensive imports at top level and only do them once, as opposed to something like a custom method or an event handler that would have to do them every time they run. Also, there's the obvious benefit of being able to use the functions in multiple places while only having to maintain them in one place.

I can illustrate the efficiency with a simple test script that imports Color and returns red. If I do this in a library script with the top level import, it runs more than 5 times faster than if I execute the same script from a custom method:

Example:

'''
Compare library script efficiency vs custom method efficiency
Loop each approach a million time to magnify the efficiency differences
'''
# Each script simply imports java.awt.Color and returns Color.RED

# Run a function that requires imports from the script library
startTime = system.date.now()
for index in xrange(1000000): 
	red = testScript.getRed()	
print 'Library Script:', '{}ms'.format(system.date.millisBetween(startTime, system.date.now()))

# Run a script that requires imports from a custom method on the source component
startTime = system.date.now()
for index in xrange(1000000):
	red = event.source.getRed()
	
print 'Custom Method:', '{}ms'.format(system.date.millisBetween(startTime, system.date.now()))

Result:

Library Script: 960ms
Custom Method: 5222ms
3 Likes