python late binding - dynamically put locals in scope - python

i have a function, m_chain, which refers to two functions bind and unit which are not defined. i want to wrap this function in some context which provides definitions for these functions - you can think of them as interfaces for which i want to dynamically provide an implementation.
def m_chain(*fns):
"""what this function does is not relevant to the question"""
def m_chain_link(chain_expr, step):
return lambda v: bind(chain_expr(v), step)
return reduce(m_chain_link, fns, unit)
In Clojure, this is done with macros. what are some elegant ways of doing this in python? i have considered:
polymorphism: turn m_chain into a method referring to self.bind and self.unit, whose implementations are provided by a subclass
implementing the with interface so i can modify the environment map and then clean up when i'm done
changing the signature of m_chain to accept unit and bind as arguments
requiring usage of m_chain be wrapped by a decorator which will do something or other - not sure if this even makes sense
ideally, i do not want to modify m_chain at all, i want to use the definition as is, and all of the above options require changing the definition. This is sort of important because there are other m_* functions which refer to additional functions to be provided at runtime.
How do i best structure this so i can nicely pass in implementations of bind and unit? its important that the final usage of m_chain be really easy to use, despite the complex implementation.
edit: here's another approach which works, which is ugly as all hell because it requires m_chain be curried to a function of no args. but this is a minimum working example.
def domonad(monad, cmf):
bind = monad['bind']; unit = monad['unit']
return cmf()
identity_m = {
'bind':lambda v,f:f(v),
'unit':lambda v:v
}
maybe_m = {
'bind':lambda v,f:f(v) if v else None,
'unit':lambda v:v
}
>>> domonad(identity_m, lambda: m_chain(lambda x: 2*x, lambda x:2*x)(2))
8
>>> domonad(maybe_m, lambda: m_chain(lambda x: None, lambda x:2*x)(2))
None

In Python, you can write all the code you want that refers to stuff that doesn't exist; to be specific, you can write code that refers to names that do not have values bound to them. And you can compile that code. The only problem will happen at run time, if the names still don't have values bound to them.
Here is a code example you can run, tested under Python 2 and Python 3.
def my_func(a, b):
return foo(a) + bar(b)
try:
my_func(1, 2)
except NameError:
print("didn't work") # name "foo" not bound
# bind name "foo" as a function
def foo(a):
return a**2
# bind name "bar" as a function
def bar(b):
return b * 3
print(my_func(1, 2)) # prints 7
If you don't want the names to be just bound in the local name space, but you want to be able to fine-tune them per function, I think the best practice in Python would be to use named arguments. You could always close over the function arguments and return a new function object like so:
def my_func_factory(foo, bar):
def my_func(a, b):
return foo(a) + bar(b)
return my_func
my_func0 = my_func_factory(lambda x: 2*x, lambda x:2*x)
print(my_func0(1, 2)) # prints 6
EDIT: Here is your example, modified using the above idea.
def domonad(monad, *cmf):
def m_chain(fns, bind=monad['bind'], unit=monad['unit']):
"""what this function does is not relevant to the question"""
def m_chain_link(chain_expr, step):
return lambda v: bind(chain_expr(v), step)
return reduce(m_chain_link, fns, unit)
return m_chain(cmf)
identity_m = {
'bind':lambda v,f:f(v),
'unit':lambda v:v
}
maybe_m = {
'bind':lambda v,f:f(v) if v else None,
'unit':lambda v:v
}
print(domonad(identity_m, lambda x: 2*x, lambda x:2*x)(2)) # prints 8
print(domonad(maybe_m, lambda x: None, lambda x:2*x)(2)) # prints None
Please let me know how this would work for you.
EDIT: Okay, one more version after your comment. You could write arbitrary m_ functions following this pattern: they check kwargs for a key "monad". This must be set as a named argument; there is no way to pass it as a positional argument, because of the *fns argument which collects all arguments into a list. I provided default values for bind() and unit() in case they are not defined in the monad, or the monad is not provided; those probably don't do what you want, so replace them with something better.
def m_chain(*fns, **kwargs):
"""what this function does is not relevant to the question"""
def bind(v, f): # default bind if not in monad
return f(v),
def unit(v): # default unit if not in monad
return v
if "monad" in kwargs:
monad = kwargs["monad"]
bind = monad.get("bind", bind)
unit = monad.get("unit", unit)
def m_chain_link(chain_expr, step):
return lambda v: bind(chain_expr(v), step)
return reduce(m_chain_link, fns, unit)
def domonad(fn, *fns, **kwargs):
return fn(*fns, **kwargs)
identity_m = {
'bind':lambda v,f:f(v),
'unit':lambda v:v
}
maybe_m = {
'bind':lambda v,f:f(v) if v else None,
'unit':lambda v:v
}
print(domonad(m_chain, lambda x: 2*x, lambda x:2*x, monad=identity_m)(2))
print(domonad(m_chain, lambda x: None, lambda x:2*x, monad=maybe_m)(2))

Okay, here is my final answer to this question.
You need to be able to rebind some functions at least some of the time. Your hack, backing up the .__globals__ value and pasting in new values, is ugly: slow, non-thread-safe, and specific to CPython. I have thought about this, and there is no Pythonic solution that works this way.
In Python, you can rebind any function, but you have to do it explicitly, and some functions are not a good idea to rebind. For example, I love the builtins all() and any(), and I think it would be scary if you could stealthily rebind them and it would not be obvious.
You want some functions to be rebindable, and I don't think you need them all to be rebindable. So it would make perfect sense to mark the rebindable functions in some way. The obvious and Pythonic way to do this is to make them method functions of a class we can call Monad. You can use the standard variable name m for instances of Monad, and then when someone tries to read and understand their code, they will know that a function with a name like m.unit() is potentially rebindable via some other Monad instance being passed in.
It will be pure Python, and completely portable, if you obey these rules:
All functions must be bound in the monad. If you refer to
m.bind() then "bind" must appear in the .__dict__ of the
instance of Monad.
Functions using Monad must take a named
argument m=, or for functions that will use the *args feature,
must take a **kwargs argument and check it for a key named "m".
Here is an example of what I have in mind.
class Monad(object):
def __init__(self, *args, **kwargs):
# init from each arg. Try three things:
# 0) if it has a ".__dict__" attribute, update from that.
# 1) if it looks like a key/value tuple, insert value for key.
# 2) else, just see if the whole thing is a dict or similar.
# Other instances of class Monad() will be handled by (0)
for x in args:
if hasattr("__dict__", x):
self.__dict__.update(x.__dict__)
else:
try:
key, value = x
self.__dict__[key] = value
except TypeError:
self.__dict__.update(x)
self.__dict__.update(kwargs)
def __identity(x):
return x
def __callt(v, f):
return f(v)
def __callt_maybe(v, f):
if v:
return f(v)
else:
return None
m_identity = Monad(bind=__callt, unit=__identity)
m_maybe = Monad(bind=__callt_maybe, unit=__identity)
def m_chain(*fns, **kwargs):
"""what this function does is not relevant to the question"""
m = kwargs.get("m", m_identity)
def m_chain_link(chain_expr, step):
return lambda v: m.bind(chain_expr(v), step)
return reduce(m_chain_link, fns, m.unit)
print(m_chain(lambda x: 2*x, lambda x:2*x, m=m_identity)(2)) # prints 8
print(m_chain(lambda x: None, lambda x:2*x, m=m_maybe)(2)) # prints None
The above is clean, Pythonic, and should run just as well under IronPython, Jython, or PyPy as it does under CPython. Inside m_chain(), the expression m = kwargs.get("m", m_identity) tries to read out a specified monad argument; if one is not found, the monad is set to m_identity.
But, you might want more. You might want the Monad class to support only optionally overriding a function name; and you might be willing to stick with just CPython. Here is a trickier version of the above. In this version, when the expression m.some_name() is evaluated, if the Monad instance m does not have the name some_name bound in its .__dict__, it will look up some_name in the locals of the caller, and in the globals().
In this case, the expression m.some_name() means "m can override some_name but doesn't have to; some_name might not be in m, in which case some_name will be looked up as if it were not prefixed by m.". The magic is in the function .__getattr__(), which uses sys._getframe() to peek at the locals of the caller. .__getattr__() is only called when the local lookup fails, so we know that the Monad instance doesn't have name bound in .__dict__; so look at the locals belonging to the caller, using sys._getframe(1).f_locals; failing that, look in globals(). Just insert this into the class definition of Monad in the source code above.
def __getattr__(self, name):
# if __getattr__() is being called, locals() were already checked
d = sys._getframe(1).f_locals
if name in d:
return d[name]
d = globals()
if name in d:
return d[name]
mesg = "name '%s' not found in monad, locals, or globals" % name
raise NameError, mesg

here is how i ended up doing it. no idea if this is a good idea. but it lets me write my m_* functions totally independent of the implementation of unit/bind, and also totally independent of any implementation details of the way monads are done in python. the right things are just there in lexical scope.
class monad:
"""Effectively, put the monad definition in lexical scope.
Can't modify the execution environment `globals()` directly, because
after globals().clear() you can't do anything.
"""
def __init__(self, monad):
self.monad = monad
self.oldglobals = {}
def __enter__(self):
for k in self.monad:
if k in globals(): self.oldglobals[k]=globals()[k]
globals()[k]=self.monad[k]
def __exit__(self, type, value, traceback):
"""careful to distinguish between None and undefined.
remove the values we added, then restore the old value only
if it ever existed"""
for k in self.monad: del globals()[k]
for k in self.oldglobals: globals()[k]=self.oldglobals[k]
def m_chain(*fns):
"""returns a function of one argument which performs the monadic
composition of fns."""
def m_chain_link(chain_expr, step):
return lambda v: bind(chain_expr(v), step)
return reduce(m_chain_link, fns, unit)
identity_m = {
'bind':lambda v,f:f(v),
'unit':lambda v:v
}
with monad(identity_m):
assert m_chain(lambda x:2*x, lambda x:2*x)(2) == 8
maybe_m = {
'bind':lambda v,f:f(v) if v else None,
'unit':lambda v:v
}
with monad(maybe_m):
assert m_chain(lambda x:2*x, lambda x:2*x)(2) == 8
assert m_chain(lambda x:None, lambda x:2*x)(2) == None
error_m = {
'bind':lambda mv, mf: mf(mv[0]) if mv[0] else mv,
'unit':lambda v: (v, None)
}
with monad(error_m):
success = lambda val: unit(val)
failure = lambda err: (None, err)
assert m_chain(lambda x:success(2*x), lambda x:success(2*x))(2) == (8, None)
assert m_chain(lambda x:failure("error"), lambda x:success(2*x))(2) == (None, "error")
assert m_chain(lambda x:success(2*x), lambda x:failure("error"))(2) == (None, "error")
from itertools import chain
def flatten(listOfLists):
"Flatten one level of nesting"
return list(chain.from_iterable(listOfLists))
list_m = {
'unit': lambda v: [v],
'bind': lambda mv, mf: flatten(map(mf, mv))
}
def chessboard():
ranks = list("abcdefgh")
files = list("12345678")
with monad(list_m):
return bind(ranks, lambda rank:
bind(files, lambda file:
unit((rank, file))))
assert len(chessboard()) == 64
assert chessboard()[:3] == [('a', '1'), ('a', '2'), ('a', '3')]

Python is already late bound. There's no need to do any work here:
def m_chain(*args):
return bind(args[0])
sourcemodulename = 'foo'
sourcemodule = __import__(sourcemodulename)
bind = sourcemodule.bind
print m_chain(3)

Related

reassign values to args of a function

i have a function with about 20-30 parameters
def function (a,b,c,d,e,....):
....
those paramenters may have any value, including "None".
I want to reasign a specifique string to each var that has the value "None" before my function does its magic.
But I dont want to have a huge block of code such as:
if a is None:
....
if b is None: ....
How can I go through each var and reasign its value if the condition is met?
ty
Unless you are doing something pretty exotic, this kind of thing is usually better handled by collecting the variables in a data structure, e.g. a dict like so:
def function(**kwargs):
default = 42
for key, val in kwargs.items():
if val is None:
kwargs[key] = default
...
print(kwargs)
# Example call
function(a=1, b=None)
You can assign to individual variables using the magic of exec, but it's generally not advised. Furthermore, it's not clear to me how one can successfully use this inside of a function as e.g. exec('a = 42') doesn't actually change the value of the local a variable.
If you have so many arguments to the function, then you can try using iterable unpacking operator * instead of explicitly defining each argument. In that you you will have more control over the contents of the arguments:-
def function(*args):
args = list(args)
for x, y in enumerate(args):
if y is None:
args[x] = "default_value"
print(args)
Then do a function call similar to :-
function(123, 3, None, "hello")
OUTPUT:-
[123, 3, 'default_value', 'hello']
I'm not recommending doing this, but if you insist:
def function(a,b,c,d,e,...):
myvars = locals() # Get a dictionary of parameters
arg_names = myvars.keys() # Get their names
for k in arg_names: # Replace the ones whose value is None
if myvars[k] == None:
myvars[k] = 'None'

Self Implementation of map()

I have to Implement by myself new map() that can get more than one function.
I wrote this code:
def map(iterable, *callbacks):
temporary = []
callbacksList = list(callbacks)
for func in callbacksList:
for item in iterable:
temporary.append(func(item))
callbacksList = callbacksList[1:]
iterable = temporary
temporary = []
return iterable
The code works fine in this case:
c = "hello"
print(map(c,str.upper,ord,chr)) # => ['H','E','L','L','O']
but in this case:
c = "hello"
print(map(c,str.upper,chr,ord))
I got an error "an integer is required (got type str)" - because of chr function that gets str and not int.
I don't understand how can I Adjust my code to work with both cases and be more generic.
Thanks for helpers.
Since your callbacks seem to be applied one after another, why not make this a generator and swap the two for loops (so your iterable can also be a generator):
def map(iterable, *callbacks):
for item in iterable:
for f in callbacks:
item = f(item)
yield item
list(map("hello", str.upper, ord, chr))
# ['H', 'E', 'L', 'L', 'O']
But just like your code this requires that the output of each function is also a valid input for the following function. If this is not the case, you could ignore all functions that raise an exception:
def map2(iterable, *callbacks):
for item in iterable:
for f in callbacks:
try:
item = f(item)
except (TypeError, ValueError): # might want to be more generic than this, depending on your functions
pass
yield item
list(map2("hello", str.upper, chr, ord))
# [72, 69, 76, 76, 79]
But it really depends on your usecase what the right action is in this case.
You are effectively composing your list of callbacks; your call
map(c, str.upper, ord, chr)
should be equivalent to map(c, lambda x: chr(ord(str.upper(c)))). However, your other attempt
map(c, str.upper, chr, ord)
is equivalent to map(c, lambda x: ord(chr(str.upper(c))), and ord(chr('x')) simply isn't defined; chr cannot take a string as an argument.
That said, you can simplify your definition by composing the functions first, then using the built-in map to apply the composed function.
def map(iterable, *callbacks):
# The identity function i = lambda x: x is the identity for function composition.
# That is, f(i(x)) == i(f(x)) == f(x)
f = lambda x: x
for g in callbacks:
# The g=g is necessary due to how Python handles name lookups
f = lambda x, g=g: g(f(x))
return map(f, iterable)
You can also use the functools.reduce function to perform the composition.
def compose(f,g):
def _(x):
return f(g(x))
return _
def ident(x):
return x
def map(iterable, *callbacks):
f = functools.reduce(compose, callbacks, ident)
return map(f, iterable)
One important note: this style of programming is not very efficient in Python, due to the extra anonymous functions that get inserted into the call chain and the relatively high cost of a function call.

Python currying with any number of variables

I am trying to use currying to make a simple functional add in Python. I found this curry decorator here.
def curry(func):
def curried(*args, **kwargs):
if len(args) + len(kwargs) >= func.__code__.co_argcount:
return func(*args, **kwargs)
return (lambda *args2, **kwargs2:
curried(*(args + args2), **dict(kwargs, **kwargs2)))
return curried
#curry
def foo(a, b, c):
return a + b + c
Now this is great because I can do some simple currying:
>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6
But this only works for exactly three variables. How do I write the function foo so that it can accept any number of variables and still be able to curry the result? I've tried the simple solution of using *args but it didn't work.
Edit: I've looked at the answers but still can't figure out how to write a function that can perform as shown below:
>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6
>>> foo(1)(2)
3
>>> foo(1)(2)(3)(4)
10
Arguably, explicit is better than implicit:
from functools import partial
def example(*args):
print("This is an example function that was passed:", args)
one_bound = partial(example, 1)
two_bound = partial(one_bound, 2)
two_bound(3)
#JohnKugelman explained the design problem with what you're trying to do - a call to the curried function would be ambiguous between "add more curried arguments" and "invoke the logic". The reason this isn't a problem in Haskell (where the concept comes from) is that the language evaluates everything lazily, so there isn't a distinction you can meaningfully make between "a function named x that accepts no arguments and simply returns 3" and "a call to the aforementioned function", or even between those and "the integer 3". Python isn't like that. (You could, for example, use a zero-argument call to signify "invoke the logic now"; but that would break special cases aren't special enough, and require an extra pair of parentheses for simple cases where you don't actually want to do any currying.)
functools.partial is an out-of-box solution for partial application of functions in Python. Unfortunately, repeatedly calling partial to add more "curried" arguments isn't quite as efficient (there will be nested partial objects under the hood). However, it's much more flexible; in particular, you can use it with existing functions that don't have any special decoration.
You can implement the same thing as the functools.partial example for yourself like this:
def curry (prior, *additional):
def curried(*args):
return prior(*(args + additional))
return curried
def add(*args):
return sum(args)
x = curry(add, 3,4,5)
y = curry(b, 100)
print y(200)
# 312
It may be easier to think of curry as a function factory rather than a decorator; technically that's all a decorator does but the decorator usage pattern is static where a factory is something you expect to be invoking as part of a chain of operations.
You can see here that I'm starting with add as an argument to curry and not add(1) or something: the factory signature is <callable>, *<args> . That gets around the problem in the comments to the original post.
FACT 1: It is simply impossible to implement an auto currying function for a variadic function.
FACT 2: You might not be searching for curry, if you want the function that will be passed to it * to know* that its gonna be curried, so as to make it behave differently.
In case what you need is a way to curry a variadic function, you should go with something along these lines below (using your own snipped):
def curryN(arity, func):
"""curries a function with a pre-determined number of arguments"""
def curried(*args, **kwargs):
if len(args) + len(kwargs) >= arity:
return func(*args, **kwargs)
return (lambda *args2, **kwargs2:
curried(*(args + args2), **dict(kwargs, **kwargs2)))
return curried
def curry(func):
"""automatically curries a function"""
return curryN(func.__code__.co_argcount, func);
this way you can do:
def summation(*numbers):
return sum(numbers);
sum_two_numbers = curryN(2, summation)
sum_three_numbers = curryN(3, summation)
increment = curryN(2, summation)(1)
decrement = curryN(2, summation)(-1)
I think this is a decent solution:
from copy import copy
import functools
def curry(function):
def inner(*args, **kwargs):
partial = functools.partial(function, *args, **kwargs)
signature = inspect.signature(partial.func)
try:
signature.bind(*partial.args, **partial.keywords)
except TypeError as e:
return curry(copy(partial))
else:
return partial()
return inner
This just allow you to call functools.partial recursively in an automated way:
def f(x, y, z, info=None):
if info:
print(info, end=": ")
return x + y + z
g = curry_function(f)
print(g)
print(g())
print(g(2))
print(g(2,3))
print(g(2)(3))
print(g(2, 3)(4))
print(g(2)(3)(4))
print(g(2)(3, 4))
print(g(2, info="test A")(3, 4))
print(g(2, info="test A")(3, 4, info="test B"))
Outputs:
<function curry.<locals>.inner at 0x7f6019aa6f28>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a0d0>
9
9
9
test A: 9
test B: 9

Professor used "The binary version of a function". Does that even exist?

Our professor used this in the assignment. I don't think "The binary version of a function" exist after searching about it in Google. What do you think it means?
Say we have a function add that adds a bunch of numbers. Rather than
writing add(3, 5, 4, 1) we want to use currying to create an adder
function that can be extended using a chain of calls. We would then
have adder(3)(5)(4)(1)(). Let us assume we have the currying function
that can create this adder given the add2 function (the binary version
of add) and a start value. Let us call it curry. Then we have adder =
curry(add2, 0).
I think he means a function that accepts only two arguments, so it just adds two numbers. His example function add(3, 5, 4, 1) would be a function that accepts any number of arguments and adds them all, but add2 would only accept two arguments, so add2(3, 5) would be 8. "The binary version of a function" in this case means a binary function (a function accepting two arguments).
In this case "binary function" refers to an argument that accepts two arguments. In this case your professor is probably referring to something like this:
def add2(x, y):
return x + y
# equivalently: add2 = lambda x,y: x+y
def curry(func, num):
def wrapped(*args):
if len(args) == 0:
return num
elif len(args) > 1:
raise TypeError('{} takes 1 positional argument but '
'{} were given'.format(
func.__name__, len(args)))
arg = args[0]
return curry(func, func(num, arg))
return wrapped
#AdamSmith and #BrenBarn have already pointed out what binary function means. A simple and clear assignment solution can be write by using object instead of decorator.
class curry():
def __init__(self, func, default):
self._f = func
self._default = default
def __call__(self, val=None):
if val is None:
return self._default
return curry(self._f,self._f(self._default,val))
print(curry(lambda x,y:x+y, 0)(3)(5)(4)(1)())
Neat and simple!
IMHO functors should be used only when the increase readability, simplicity or hide tedious work. In that case the object and functor implementations are really the same but the object version is more readable and straight to understand.

Python 2.7 using the input to a function as a string and variable

I would like to do the following:
print "CC =",CC
but as a function so that i only have to write the variable CC once. I can't work out how to do this in a function as it always evaluates CC as a floating point number (which it is).... Is there a way to accept the input to a function as both a string and floating point number?
I tried this:
def printme(a):
b='%s' % a
print b
return b
but of course it only prints the value of a, not its name.
You could use the inspect module (see also this SO question):
def printme(x):
import inspect
f = inspect.currentframe()
val = f.f_back.f_locals[x]
print x, '=', val
CC = 234.234
printme('CC') # <- write variable name only once
# prints: CC = 234.234
Perhaps a dictionary is a better approach to the problem. Assuming you have several name-value pairs that you want to use, you can put them in a dict:
params = {"CC": 1.2345, "ID": "Yo!", "foo": "bar"}
Then, for example, you could print all the names and values nicely formatted like this:
for key in params:
print "{0} = {1}".format(key, params[key])
But since it is still unclear why you are trying to do this, it's hard to tell whether this is the right way.
I think this is your required solution:
def printme(x):
keys_list = [key for key, value in globals().iteritems() if value == x]
print keys_list
for key in keys_list:
if id(globals()[key]) == id(x):
result = "%s = %s" %(key, x)
print result
break
return result
for example if you declare a variable:
>>> c=55.6
then result of printme(c) will be
>>> 'c = 55.6'
Note: This solution is based on globally unique id matching.
Not exactly what you want, but easy to do:
def printme(**kwargs):
for key, value in kwargs.items():
print '%s=%s' % (key, value)
return value
In [13]: printme(CC=1.23, DD=2.22)
CC=1.23
DD=2.22
Out[13]: 1.23
If I understand you correctly you want something like this?
def f(a):
print('{0}: = {1}'.format(locals().keys()[0], a))
Update:
I am aware that the example doesn't make a lot of sense, as it's basically the same as:
def f(a):
print('a: {0}'.format(a))
I merely wanted to point the OP to locals() as I didn't quite understand what he's trying to accomplish.
I guess this is more what he's looking for:
def f(**kwargs):
for k in kwargs.keys():
print('{0}: {1}'.format(k, kwargs[k]))
f(a=1, b=2)
If I understand you correctly you want a shorthand for printing a variable name and its value in the current scope? This is in general impossible without using the interpreters trace function or sys._getframe, which should in general only be used if you know what you're doing. The reason for this is that the print function has no other way of getting the locals from the calling scope:
def a():
x = 1
magic_print("x") #will not work without accessing the current frame
What you CAN do without these is explicitly pass the locals to a function like this:
def printNameAndValue(varname, values):
print("%s=%s" % (varname, values[varname]))
def a():
x = 1
printNameAndValue("x", locals()) #prints 'x=1'
EDIT:
See the answer by catchemifyoutry for a solution using the inspect module (which internally uses sys._getframe). For completeness a solution using the trace function directly - useful if you're using python 2.0 and inspect isn't available ;)
from sys import settrace
__v = {} #global dictionary that holds the variables
def __trace(frame, event, arg):
""" a trace function saving the locals on every function call """
global __v
if not event == "call":
return __trace
__v.update(frame.f_back.f_locals)
def enableTrace(f):
""" a wrapper decorator setting and removing the trace """
def _f(*a, **kwa):
settrace(__trace)
try:
f(*a, **kwa)
finally:
settrace(None)
return _f
def printv(vname):
""" the function doing the printing """
global __v
print "%s=%s" % (vname, __v[vname])
Save it in a module and use like this:
from modulenamehere import enableTrace, printv
#enableTrace
def somefunction():
x = 1
[...]
printv("x")
used a global variable to achieve this,func.__globals__.keys() contains all the variables passed to func, so I filtered out the name startin with __ and stored them in a list.
with every call to func() the func.__globals__.keys() gets updated with the new variable name,so compare the new varn with the older glo results in the new variable that was just added.
glo=[]
def func(x):
global glo
varn=[x for x in func.__globals__.keys() if not x.startswith('__') and x!=func.__name__]
new=list(set(varn)^set(glo))
print("{0}={1}".format(new[0],x))
glo=varn[:]
output:
>>> a=10
>>> func(a)
a=10
>>> b=20
>>> func(20)
b=20
>>> foo='cat'
>>> func(foo)
foo=cat
>>> bar=1000
>>> func(bar)
bar=1000

Categories