I have this example code, trying to demonstrate using a callback function:
def callback(a, b):
print('Sum = {0}'.format(a+b))
def main(callback=None):
print('Add any two digits.')
if callback != None:
callback
main(callback(1, 2))
I get this result:
Sum = 3
Add any two digits.
It seems that the callback function executes before the logic in main. Why? How can I make it so that the callback is not called until it is used within main?
See also: Python Argument Binders
In this code
if callback != None:
callback
callback on its own doesn't do anything; it accepts parameters - def callback(a, b):
The fact that you did callback(1, 2) first will call that function, thereby printing Sum = 3, and then main() gets called with the result of the callback function, which is printing the second line
Since callback returns no explicit value, it is returned as None.
Thus, your code is equivalent to
callback(1, 2)
main()
Solution
You could try not calling the function at first and just passing its handle.
def callback(n):
print("Sum = {}".format(n))
def main(a, b, _callback = None):
print("adding {} + {}".format(a, b))
if _callback:
_callback(a+b)
main(1, 2, callback)
Here's what you wanted to do :
def callback(a, b):
print('Sum = {0}'.format(a+b))
def main(a,b,f=None):
print('Add any two digits.')
if f is not None:
f(a,b)
main(1, 2, callback)
The problem is that you're evaluating the callback before you pass it as a callable. One flexible way to solve the problem would be this:
def callback1(a, b):
print('Sum = {0}'.format(a+b))
def callback2(a):
print('Square = {0}'.format(a**2))
def callback3():
print('Hello, world!')
def main(callback=None, cargs=()):
print('Calling callback.')
if callback is not None:
callback(*cargs)
main(callback1, cargs=(1, 2))
main(callback2, cargs=(2,))
main(callback3)
Optionally you may want to include a way to support keyword arguments.
As mentioned in the comments, your callback is called whenever it's suffixed with open and close parens; thus it's called when you pass it.
You might want to use a lambda and pass in the values.
#!/usr/bin/env python3
def main(callback=None, x=None, y=None):
print('Add any two digits.')
if callback != None and x != None and y != None:
print("Result of callback is {0}".format(callback(x,y)))
else:
print("Missing values...")
if __name__ == "__main__":
main(lambda x, y: x+y, 1, 2)
Your code is executed as follows:
main(callback(1, 2))
callback function is called with (1, 2) and it returns None (Without return statement, your function prints Sum = 3 and returns None)
main function is called with None as argument (So callback != None will always be False)
This is an old post, but perhaps the following may be additional clarification on writing and using a callback function, especially if you wonder where it gets its arguments from and whether you can access its return values (if there is no way to get it from the function that takes the callback function).
The following code defines a class CallBack that has two callback methods (functions) my_callback_sum and my_callback_multiply. The callback methods are fed into the method foo.
# understanding callback
class CallBack:
#classmethod
def my_callback_sum(cls, c_value1, c_value2):
value = c_value1 + c_value2
print(f'in my_callback_sum --> {c_value1} + {c_value2} = {value}')
cls.operator = '+'
return cls.operator, value
#classmethod
def my_callback_multiply(cls, c_value1, c_value2):
value = c_value1 * c_value2
print(f'in my_callback_multiply --> {c_value1} * {c_value2} = {value}')
cls.operator = '*'
return cls.operator, value
#staticmethod
def foo(foo_value, callback):
_, value = callback(10, foo_value)
# note foo only returns the value not the operator from callback!
return value
if __name__ == '__main__':
cb = CallBack()
value = cb.foo(20, cb.my_callback_sum)
print(f'in main --> {value} and the operator is {cb.operator}')
value = cb.foo(20, cb.my_callback_multiply)
print(f'in main --> {value} and the operator is {cb.operator}')
result:
in my_callback_sum --> 10 + 20 = 30
in main --> 30 and the operator is +
in my_callback_multiply --> 10 * 20 = 200
in main --> 200 and the operator is *
As you can see one value for the callback function c_value2 it gets from argument foo_value in foo and given in main the value 20, while c_value1 it gets internally from foo in this case the value 10 (and may be not clearly visible if foo is some method of a third party imported module, like pyaudio).
The return value of the callback function functions can be retrieved by adding it to the namespace of the class CallBack, in this case cls.operator
You can use anonymous functions
def callback(a, b):
print('Sum = {0}'.format(a+b))
def main(callback=None):
print('Add any two digits.')
if callback is not None:
callback()
tmp_func = lambda: main(lambda: callback(2,3))
tmp_func()
#OR
tmp_func = lambda x,y: main(lambda: callback(x,y))
tmp_func(2,4)
Related
def MainCount(f):
def progFirst(*args,**kwargs):
progFirst.calls+=1
return f(*args,**kwargs)
progFirst.calls=0
return progFirst
#MainCount
def progSecond(i):
return i+1
#MainCount
def Count(i=0,j=1):
return i*j+1
print(progSecond.calls)
for n in range(5):
progSecond(n)
Count(j=0,i=1)
print(Count.calls)
Output :0
1
As per my understanding MainCount(probSecond) but I am not understant then how probSecond.calls equal to zero same in Count.calls also
As You Can See in MainCount function probFirst.Calls is attribute of function .When MainCount(probSecond) Now probSecond.calls is also attribute of MainCount function.
# A Python example to demonstrate that
# decorators can be useful attach data
# A decorator function to attach
# data to func
def attach_data(func):
func.data = 3
return func
#attach_data
def add (x, y):
return x + y
# Driver code
# This call is equivalent to attach_data()
# with add() as parameter
print(add(2, 3))
print(add.data)
I have tree types of sub-functions:
one without any parameters (arguments),
second with one parameter
third with multiple parameters (tuple)
I am trying to pass that functions and its arguments to another function which sum results of all sub-functions and return the sum value.
Parameters in that function should be: names of each sub-function as position arguments (*args) and arguments of each subfunction as key-value arguments (*kvargs).
Example:
def no_arg()
def one_arg(a)
def multiple_args(a, b, c, e, f)
# execution of function_results_sum:
function_results_sum(
no_arg, one_arg, multiple_args,
one_arg=23,
multiple_args=(1, 2, 3, 4, 5))
What i have done so far:
def no_arg():
return 5
def ident(x):
return x
def mult(x, y):
return x * y
def function_results_sum(*args, **kwargs):
return no_arg() + ident(kwargs[ident.__name__]) + mult(*kwargs[mult.__name__])
The code above is passing arguments to each sub-function, but sub-function names are hardcoded. I would like to modify the current code to be able to get function names from *args. Below I wrote a pseudocode expressing more less what i am trying to achieve:
def function_results_sum(*args, **kwargs):
for functionName in args:
result = sum(funcionName(kwargs))
return result
I have already spent all day struggling with that problem, so please don't write me that "using google doesn't hurt" ;)
Something like this would work:
def no_arg():
return 5
def one_arg(x):
return x
def multiple_args(x, y):
return x * y
def function_results_sum(*args, **kwargs):
result = 0
for func in args:
result += func(*kwargs[func.__name__])
return result
Output:
function_results_sum(
no_arg, one_arg, multiple_args,
no_arg=(),
one_arg=(23, ),
multiple_args=(1,5))
33
The only difference between what you are asking is that you have to put args in a tuple to then unpack as args to pass in later.
If you dont want to have to supply anything for no argument functions, you can double check if the func name is in kwargs:
def function_results_sum(*args, **kwargs):
result = 0
for func in args:
if func.__name__ i kwargs:
result += func(*kwargs[func.__name__])
else:
result += func()
return result
Post of R Nar is exactly what I tried to achieve. I added additional if statement to verify if kwarg is an integer or a tuple. Thanks that it is not neccessary to put all **kwargs in a tuple. Thank you guys for help!
def function_results_sum(*args, **kwargs):
result = 0
for func in args:
if func.__name__ in kwargs:
if type(kwargs[func.__name__]) == int:
result += func(kwargs[func.__name__])
elif type(kwargs[func.__name__]) == tuple:
result += func(*kwargs[func.__name__])
else:
result += func()
return result
result = function_results_sum(no_arg, ident, mult, ident=2, mult=(3, 4))
print(result)
by goolging 'python determine number of args for passed function' I found How can I find the number of arguments of a Python function?
I'm pretty sure you don't want the **kwars key, value syntax so I use a func_list regular arg and *args
from inspect import signature
def function_results_sum(func_list, *args):
arg_gen = (e for e in args)
return sum([func(*(next(arg_gen)
for _ in range(len(signature(func).parameters))))
for func in func_list])
function_results_sum([no_arg, ident, mult], 7,8,9)
84
the input can be made flatter by parsing *args for Functions and (presumed) arguments (anything not Type Function)
from inspect import signature
import types
def function_results_sum(*args):
func_gen = (e for e in args if isinstance(e, types.FunctionType))
arg_gen = (e for e in args if not isinstance(e, types.FunctionType))
return sum(func(*(next(arg_gen)
for _ in range(len(signature(func).parameters))))
for func in func_gen)
function_results_sum(no_arg, ident, mult, 10,6,90)
555
order of functions and order of args are important, but separately, can be interleaved:
function_results_sum(no_arg, 10, ident, 6, 90, mult)
Out[399]: 555
So here's an extension to this question: https://stackoverflow.com/a/37568895/2290820
on how to optionally Enable or Disable Decorator on a Function.
On those lines, I came up with something like this to make decorator get invoked on a recursive call:
def deco(f):
def fattr(attr):
f.attr = attr
def closure(*args):
f(*args)
f.unwrap = f
f.closure = closure
return f
return fattr
#deco
def printa(x):
if x > 1:
print x
return printa(x-1)
else:
print x
return
printa({1:1})(5)
# do the same call w/o deocorator
def finta(x):
if x > 1:
print x
return finta(x-1)
else:
print x
return
finta(5) # this works
to experiment with decorators on a recursive function. Clearly, printa recursive version is not behaving the way it should be.
I could do
g = printa({1:1})
g.closure(5)
to turn on the decorator option or not use that option. Anyway, regardless of good or bad design, How can I make decorator get invoked on a recursive call?
In your deco you have an assignment f.attr = attr that "eats" your argument after first recursive call. Your should modify your recursive call this way:
def deco(f):
def fattr(attr):
f.attr = attr
def closure(*args):
f(*args)
f.unwrap = f
f.closure = closure
return f
return fattr
#deco
def printa(x):
if x > 1:
print x
return printa(None)(x-1) # None will be assigned to f.attr
else:
print x
return
printa({1:1})(5)
5
4
3
2
1
Suppose I have a function like f(a, b, c=None). The aim is to call the function like f(*args, **kwargs), and then construct a new set of args and kwargs such that:
If the function had default values, I should be able to acquire their values. For example, if I call it like f(1, 2), I should be able to get the tuple (1, 2, None) and/or the dictionary {'c': None}.
If the value of any of the arguments was modified inside the function, get the new value. For example, if I call it like f(1, 100000, 3) and the function does if b > 500: b = 5 modifying the local variable, I should be able to get the the tuple (1, 5, 3).
The aim here is to create a a decorator that finishes the job of a function. The original function acts as a preamble setting up the data for the actual execution, and the decorator finishes the job.
Edit: I'm adding an example of what I'm trying to do. It's a module for making proxies for other classes.
class Spam(object):
"""A fictional class that we'll make a proxy for"""
def eggs(self, start, stop, step):
"""A fictional method"""
return range(start, stop, step)
class ProxyForSpam(clsproxy.Proxy):
proxy_for = Spam
#clsproxy.signature_preamble
def eggs(self, start, stop, step=1):
start = max(0, start)
stop = min(100, stop)
And then, we'll have that:
ProxyForSpam().eggs(-10, 200) -> Spam().eggs(0, 100, 1)
ProxyForSpam().eggs(3, 4) -> Spam().eggs(3, 4, 1)
There are two recipes available here, one which requires an external library and another that uses only the standard library. They don't quite do what you want, in that they actually modify the function being executed to obtain its locals() rather than obtain the locals() after function execution, which is impossible, since the local stack no longer exists after the function finishes execution.
Another option is to see what debuggers, such as WinPDB or even the pdb module do. I suspect they use the inspect module (possibly along with others), to get the frame inside which a function is executing and retrieve locals() that way.
EDIT: After reading some code in the standard library, the file you want to look at is probably bdb.py, which should be wherever the rest of your Python standard library is. Specifically, look at set_trace() and related functions. This will give you an idea of how the Python debugger breaks into the class. You might even be able to use it directly. To get the frame to pass to set_trace() look at the inspect module.
I've stumbled upon this very need today and wanted to share my solution.
import sys
def call_function_get_frame(func, *args, **kwargs):
"""
Calls the function *func* with the specified arguments and keyword
arguments and snatches its local frame before it actually executes.
"""
frame = None
trace = sys.gettrace()
def snatch_locals(_frame, name, arg):
nonlocal frame
if frame is None and name == 'call':
frame = _frame
sys.settrace(trace)
return trace
sys.settrace(snatch_locals)
try:
result = func(*args, **kwargs)
finally:
sys.settrace(trace)
return frame, result
The idea is to use sys.trace() to catch the frame of the next 'call'. Tested on CPython 3.6.
Example usage
import types
def namespace_decorator(func):
frame, result = call_function_get_frame(func)
try:
module = types.ModuleType(func.__name__)
module.__dict__.update(frame.f_locals)
return module
finally:
del frame
#namespace_decorator
def mynamespace():
eggs = 'spam'
class Bar:
def hello(self):
print("Hello, World!")
assert mynamespace.eggs == 'spam'
mynamespace.Bar().hello()
I don't see how you could do this non-intrusively -- after the function is done executing, it doesn't exist any more -- there's no way you can reach inside something that doesn't exist.
If you can control the functions that are being used, you can do an intrusive approach like
def fn(x, y, z, vars):
'''
vars is an empty dict that we use to pass things back to the caller
'''
x += 1
y -= 1
z *= 2
vars.update(locals())
>>> updated = {}
>>> fn(1, 2, 3, updated)
>>> print updated
{'y': 1, 'x': 2, 'z': 6, 'vars': {...}}
>>>
...or you can just require that those functions return locals() -- as #Thomas K asks above, what are you really trying to do here?
Witchcraft below read on your OWN danger(!)
I have no clue what you want to do with this, it's possible but it's an awful hack...
Anyways, I HAVE WARNED YOU(!), be lucky if such things don't work in your favorite language...
from inspect import getargspec, ismethod
import inspect
def main():
#get_modified_values
def foo(a, f, b):
print a, f, b
a = 10
if a == 2:
return a
f = 'Hello World'
b = 1223
e = 1
c = 2
foo(e, 1000, b = c)
# intercept a function and retrieve the modifed values
def get_modified_values(target):
def wrapper(*args, **kwargs):
# get the applied args
kargs = getcallargs(target, *args, **kwargs)
# get the source code
src = inspect.getsource(target)
lines = src.split('\n')
# oh noes string patching of the function
unindent = len(lines[0]) - len(lines[0].lstrip())
indent = lines[0][:len(lines[0]) - len(lines[0].lstrip())]
lines[0] = ''
lines[1] = indent + 'def _temp(_args, ' + lines[1].split('(')[1]
setter = []
for k in kargs.keys():
setter.append('_args["%s"] = %s' % (k, k))
i = 0
while i < len(lines):
indent = lines[i][:len(lines[i]) - len(lines[i].lstrip())]
if lines[i].find('return ') != -1 or lines[i].find('return\n') != -1:
for e in setter:
lines.insert(i, indent + e)
i += len(setter)
elif i == len(lines) - 2:
for e in setter:
lines.insert(i + 1, indent + e)
break
i += 1
for i in range(0, len(lines)):
lines[i] = lines[i][unindent:]
data = '\n'.join(lines) + "\n"
# setup variables
frame = inspect.currentframe()
loc = inspect.getouterframes(frame)[1][0].f_locals
glob = inspect.getouterframes(frame)[1][0].f_globals
loc['_temp'] = None
# compile patched function and call it
func = compile(data, '<witchstuff>', 'exec')
eval(func, glob, loc)
loc['_temp'](kargs, *args, **kwargs)
# there you go....
print kargs
# >> {'a': 10, 'b': 1223, 'f': 'Hello World'}
return wrapper
# from python 2.7 inspect module
def getcallargs(func, *positional, **named):
"""Get the mapping of arguments to values.
A dict is returned, with keys the function argument names (including the
names of the * and ** arguments, if any), and values the respective bound
values from 'positional' and 'named'."""
args, varargs, varkw, defaults = getargspec(func)
f_name = func.__name__
arg2value = {}
# The following closures are basically because of tuple parameter unpacking.
assigned_tuple_params = []
def assign(arg, value):
if isinstance(arg, str):
arg2value[arg] = value
else:
assigned_tuple_params.append(arg)
value = iter(value)
for i, subarg in enumerate(arg):
try:
subvalue = next(value)
except StopIteration:
raise ValueError('need more than %d %s to unpack' %
(i, 'values' if i > 1 else 'value'))
assign(subarg,subvalue)
try:
next(value)
except StopIteration:
pass
else:
raise ValueError('too many values to unpack')
def is_assigned(arg):
if isinstance(arg,str):
return arg in arg2value
return arg in assigned_tuple_params
if ismethod(func) and func.im_self is not None:
# implicit 'self' (or 'cls' for classmethods) argument
positional = (func.im_self,) + positional
num_pos = len(positional)
num_total = num_pos + len(named)
num_args = len(args)
num_defaults = len(defaults) if defaults else 0
for arg, value in zip(args, positional):
assign(arg, value)
if varargs:
if num_pos > num_args:
assign(varargs, positional[-(num_pos-num_args):])
else:
assign(varargs, ())
elif 0 < num_args < num_pos:
raise TypeError('%s() takes %s %d %s (%d given)' % (
f_name, 'at most' if defaults else 'exactly', num_args,
'arguments' if num_args > 1 else 'argument', num_total))
elif num_args == 0 and num_total:
raise TypeError('%s() takes no arguments (%d given)' %
(f_name, num_total))
for arg in args:
if isinstance(arg, str) and arg in named:
if is_assigned(arg):
raise TypeError("%s() got multiple values for keyword "
"argument '%s'" % (f_name, arg))
else:
assign(arg, named.pop(arg))
if defaults: # fill in any missing values with the defaults
for arg, value in zip(args[-num_defaults:], defaults):
if not is_assigned(arg):
assign(arg, value)
if varkw:
assign(varkw, named)
elif named:
unexpected = next(iter(named))
if isinstance(unexpected, unicode):
unexpected = unexpected.encode(sys.getdefaultencoding(), 'replace')
raise TypeError("%s() got an unexpected keyword argument '%s'" %
(f_name, unexpected))
unassigned = num_args - len([arg for arg in args if is_assigned(arg)])
if unassigned:
num_required = num_args - num_defaults
raise TypeError('%s() takes %s %d %s (%d given)' % (
f_name, 'at least' if defaults else 'exactly', num_required,
'arguments' if num_required > 1 else 'argument', num_total))
return arg2value
main()
Output:
1 1000 2
{'a': 10, 'b': 1223, 'f': 'Hello World'}
There you go... I'm not responsible for any small children that get eaten by demons or something the like (or if it breaks on complicated functions).
PS: The inspect module is the pure EVIL.
Since you are trying to manipulate variables in one function, and do some job based on those variables on another function, the cleanest way to do it is having these variables to be an object's attributes.
It could be a dictionary - that could be defined inside the decorator - therefore access to it inside the decorated function would be as a "nonlocal" variable. That cleans up the default parameter tuple of this dictionary, that #bgporter proposed.:
def eggs(self, a, b, c=None):
# nonlocal parms ## uncomment in Python 3
parms["a"] = a
...
To be even more clean, you probably should have all these parameters as attributes of the instance (self) - so that no "magical" variable has to be used inside the decorated function.
As for doing it "magically" without having the parameters set as attributes of certain object explicitly, nor having the decorated function to return the parameters themselves (which is also an option) - that is, to have it to work transparently with any decorated function - I can't think of a way that does not involve manipulating the bytecode of the function itself.
If you can think of a way to make the wrapped function raise an exception at return time, you could trap the exception and check the execution trace.
If it is so important to do it automatically that you consider altering the function bytecode an option, feel free to ask me further.
I am writing a small app that has to perform some 'sanity checks' before entering execution. (eg. of a sanity check: test if a certain path is readable / writable / exists)
The code:
import logging
import os
import shutil
import sys
from paths import PATH
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger('sf.core.sanity')
def sanity_access(path, mode):
ret = os.access(path, mode)
logfunc = log.debug if ret else log.warning
loginfo = (os.access.__name__, path, mode, ret)
logfunc('%s(\'%s\', %s)==%s' % loginfo)
return ret
def sanity_check(bool_func, true_func, false_func):
ret = bool_func()
(logfunc, execfunc) = (log.debug, true_func) if ret else \
(log.warning, false_func)
logfunc('exec: %s', execfunc.__name__)
execfunc()
def sanity_checks():
sanity_check(lambda: sanity_access(PATH['userhome'], os.F_OK), \
lambda: None, sys.exit)
My question is related to the sanity_check function.
This function takes 3 parameters (bool_func, true_func, false_func). If the bool_func (which is the test function, returning a boolean value) fails, true_func gets executed, else the false_func gets executed.
1) lambda: None is a little lame , because for example if the sanity_access returns True, lambda: None gets executed, and the output printed will be:
DEBUG:sf.core.sanity:access('/home/nomemory', 0)==True
DEBUG:sf.core.sanity:exec: <lambda>
So it won't be very clear in the logs what function got executed. The log will only contain <lambda> . Is there a default function that does nothing and can be passed as a parameter ? Is it a way to return the name of the first function that is being executed inside a lambda ?
Or a way not to log that "exec" if 'nothing' is sent as a paramter ?
What's the none / do-nothing equivalent for functions ?
sanity_check(lambda: sanity_access(PATH['userhome'], os.F_OK), \
<do nothing, but show something more useful than <lambda>>, sys.exit)
Additional question, why is lambda: pass instead of lambda: None not working ?
What's with all the lambdas that serve no purpose? Well, maybe optional arguments will help you a bit:
def sanity_check( test, name='undefined', ontrue=None, onfalse=None ):
if test:
log.debug(name)
if ontrue is not None:
ontrue()
else:
log.warn( name )
if onfalse is not None:
onfalse()
def sanity_checks():
sanity_check(sanity_access(PATH['userhome'], os.F_OK), 'test home',
onfalse=sys.exit)
But you are really overcomplicating things.
update
I would normally delete this post because THC4k saw through all the complexity and rewrote your function correctly. However in a different context, the K combinator trick might come in handy, so I'll leave it up.
There is no builtin that does what you want AFIK. I believe that you want the K combinator (the link came up on another question) which can be encoded as
def K_combinator(x, name):
def f():
return x
f.__name__ = name
return f
none_function = K_combinator(None, 'none_function')
print none_function()
of course if this is just a one off then you could just do
def none_function():
return None
But then you don't get to say "K combinator". Another advantage of the 'K_combinator' approach is that you can pass it to functions, for example,
foo(call_back1, K_combinator(None, 'name_for_logging'))
as for your second statement, only expressions are allowed in lambda. pass is a statement. Hence, lambda: pass fails.
You can slightly simplify your call to sanity check by removing the lambda around the first argument.
def sanity_check(b, true_func, false_func):
if b:
logfunc = log.debug
execfunc = true_func
else:
logfunc = log.warning
execfunc = false_func
logfunc('exec: %s', execfunc.__name__)
execfunc()
def sanity_checks():
sanity_check(sanity_access(PATH['userhome'], os.F_OK),
K_combinator(None, 'none_func'), sys.exit)
This is more readable (largely from expanding the ternary operator into an if). the boolfunc wasn't doing anything because sanity_check wasn't adding any arguments to the call. Might as well just call instead of wrapping it in a lambda.
You might want to rethink this.
class SanityCheck( object ):
def __call__( self ):
if self.check():
logger.debug(...)
self.ok()
else:
logger.warning(...)
self.not_ok()
def check( self ):
return True
def ok( self ):
pass
def not_ok( self ):
sys.exit(1)
class PathSanityCheck(SanityCheck):
path = "/path/to/resource"
def check( self ):
return os.access( path, os.F_OK )
class AnotherPathSanityCheck(SanityCheck):
path = "/another/path"
def startup():
checks = ( PathSanityCheck(), AnotherPathSanityCheck() )
for c in checks:
c()
Callable objects can simplify your life.
>>> import dis
>>> f = lambda: None
>>> dis.dis(f)
1 0 LOAD_CONST 0 (None)
3 RETURN_VALUE
>>> g = lambda: Pass
>>>
>>>
>>> dis.dis(g)
1 0 LOAD_GLOBAL 0 (Pass)
3 RETURN_VALUE
>>> g = lambda: pass
File "<stdin>", line 1
g = lambda: pass
^
SyntaxError: invalid syntax
Actually, what you want is a function which does nothing, but has a __name__ which is useful to the log. The lambda function is doing exactly what you want, but execfunc.__name__ is giving "<lambda>". Try one of these:
def nothing_func():
return
def ThisAppearsInTheLog():
return
You can also put your own attributes on functions:
def log_nothing():
return
log_nothing.log_info = "nothing interesting"
Then change execfunc.__name__ to getattr(execfunc,'log_info', '')