Decorators and variables scope - python

I am writing a (python3) program and I got stuck when trying to implement a (function) decorator which updates an external variable, a kind of signal emitting decorator. The problem is the conflict with various functions scopes. I have look around to some similar problems but I haven't found yet the useful one... I need to respect, if possible, some design restrictions (see below) and I also would like do avoid to use external libraries.
Here a working example with the globalkeyword which can be useful as starting point
VAR = 'i am a global variable'
# decorator
def update_external_variable():
def f_wrapper(f):
def p_wrapper(p, q):
r = f(p, q) + ': updating the global variable ??'
global VAR
VAR = r
return r
return p_wrapper
return f_wrapper
#update_external_variable()
def a(p, q): return 'a({}, {})'.format(p, q) #target function
o = a('v', 'w')
print(VAR, id(VAR))
Ouput
a(v, w): updating the global variable ?? 140497617759280 # yes, it works!
Design restriction 1: the decorator, update_external_variable, should not depend on the external variable identifier (name), so it has to be passed as parameter. The signature of update_external_variable should contains the info of the global variable, VAR.
Attempt 1: the mokey patch way - I tried mimic the above working example but with no results
VAR = 'i am a global variable'
# decorator
def update_external_variable(ext_var_id): # ext_var_id: string with the variable identifier
def f_wrapper(f):
def p_wrapper(p, q):
r = f(p, q) + ': updating the global variable ??'
exec('global {}'.format(ext_var_id), {}) # -> global VAR
exec('{} = "{}"'.format(ext_var_id, eval(ext_var_id))) # initialize VAR??
#print(dir())
return r
return p_wrapper
return f_wrapper
#update_external_variable(ext_var_id='VAR')
def a(p, q): return 'a({}, {})'.format(p, q) #target function
o = a('v', 'w')
print(o, id(o))
Output
a(v, w): updating the global variable ?? 140686557781040
i am a global variable # failure!
Attempt 2: the parameters' way
VAR = 'i am a global variable'
# decorator
def update_external_variable(ext_var): # ext_var: reference of the global variable
def f_wrapper(f):
def p_wrapper(p, q, ext_var=ext_var):
r = f(p, q) + ': updating the global variable ??'
# global ext_var <- will raise to an error since point to the parameter..
print(ext_var)
ext_var = r
return r
return p_wrapper
return f_wrapper
#update_external_variable(ext_var=VAR)
def a(p, q): return 'a({}, {})'.format(p, q) # target function
o = a('v', 'w')
print(o, id(o))
print(VAR)
Output
i am a global variable
a(v, w): updating the global variable ?? 140406972742896
i am a global variable # failure!
Design restriction 2: if a solution using the attempt 2 exist then I need to impose the following restriction on the signature of the p_wrapper which can could give rise to further problems: def p_wrapper(*args, **kwargs): ... To give to the decorator a universal fingerprint I need that the arguments of p_wrapper to be those of the function to be decorated, r = func(*args, **kwargs).
If someone has an idea on how to solve this problem, it can be either for Attempt 1 or Attempt 2 or even combination of them or another solution as well, I will be very grateful!
Thanks in advance:)

Don't use individual global variables; use a dict that your decorated function can update.
VARS = {
'var1': ...,
'var2': ...,
}
# decorator
def update_external_variable(varkey):
def f_wrapper(f):
def p_wrapper(p, q):
r = f(p, q) + ': updating the global variable ??'
VARS[varkey] = r
return r
return p_wrapper
return f_wrapper
#update_external_variable('var1')
def a(p, q):
return 'a({}, {})'.format(p, q) #target function
o = a('v', 'w')
print(VARS['var1'])
If, for whatever reason, your decorator must work with existing global variables that you can't change, use globals() to get access to the necessary dict.
VAR1 = ...
VAR2 = ...
# decorator
def update_external_variable(varkey):
def f_wrapper(f):
def p_wrapper(p, q):
r = f(p, q) + ': updating the global variable ??'
globals()[varkey] = r
return r
return p_wrapper
return f_wrapper
# Still passing the *name* of the variable as a string
#update_external_variable('VAR1')
def a(p, q):
return 'a({}, {})'.format(p, q) #target function
o = a('v', 'w')
print(VAR1)

Related

Is it possible store return value of one function as Global Variable?

I Have function register a patient that will Return Medical record Number , Need store this as Global Variable to so that use the same for Different Functions Eg: Create Vital Sign , Create Lab Order etc.
aqTestCase.Begin(" User able to Register a Unknown patient")
Log.AppendFolder("Unknown Registeration Logs")
ERPage=Aliases.MedBrowser.pageER
ReusableFunctions.ClickonObject(ERPage.RegisterUnknownPatientIcon)
ReusableFunctions.ClickonObject(ERPage.UnknownRegMaleLabel)
ReusableFunctions.setTextValue(ERPage.txtAge, "20")
ReusableFunctions.ClickonObject(ERPage.UnknownRegregistrBtn)
ReusableFunctions.ClickonButton(ERPage.AssignBuutonclose)
AppReusableFunctions.getToastMsgs(ERPage)
labelER = Aliases.VidaPlusBrowser.pageER.FindElement("//span[.='ER']")
ReusableFunctions.ClickonObject(labelER)
mrn = ERPage.FindElement("//div[10]/div[5]/app-er-patient-grid-mrn/span").contentText
aqUtils.Delay(2000)
ReusableFunctions.ClickonObject(ERPage.ERArrvialTime)
Log.Message(" Unknown Patient Registred MRN is : " +mrn)
return mrn
You can set the variable as a global variable and return it.
def foo():
global X
X = 1
return X
In your case, creating a class may work better.
class Foo:
def __init__(self, x):
self.x = x
def bar(self):
return self.x
f = Foo(1)
f.bar()
x = "awesome"
def myfunc():
global x
x = "fantastic"
myfunc()
print("Python is " + x)

nested functions calling with multiple args in python

This was the program for our test and I couldn't understand what is going on. This problem is called nested function problem.
def foo(a):
def bar(b):
def foobar(c):
return a + b + c
return foobar
return bar
a, b, c = map(int,input().split())
res = foo(a)(b)(c)
print(res)
I have tried to debug this program but couldn't get any idea about why it is working.
Why is foo(a)(b)(c) not giving an error?
Why it is working and what it is called?
This is a closures concept, Inner functions are able to access variables of the enclosing scope.
If we do not access any variables from the enclosing scope, they are just ordinary functions with a different scope
def get_add(x):
def add(y):
return x + y
return add
add_function = get_add(10)
print(add_function(5)) # result is 15
Everything in Python is an object, and functions as well, so you can pass them as arguments, return them, for example:
def inc(var):
return var + 1
def my_func():
return inc
my_inc = my_func()
print(my_inc) # <function inc at ...>
print(my_inc(1)) # 2
Moreover it's closed to decorator's concept:
def log_this(func):
def wrapper(*args, **kwargs):
print('start', str(args))
res = func(*args, **kwargs)
return res
return wrapper
#log_this
def inc(var):
return var + 1
print(inc(10))

The problem of using the try exception dynamically

I have a function called transform_exceptions() that takes a list of functions, then calls each of the functions (functions are without arguments) and the exceptions that occur with the above convention to an object of ExceptionProxy and finally the list of transformed errors. It returns functions in the same order
Note: If a function is executed without error, an ExceptionProxy object should be created and its msg value should be "ok!" Slow quantification
smple:
class ExceptionProxy(Exception):
# define your class here
def transform_exceptions(func_ls):
# implement your function here
def f():
1/0
def g():
pass
tr_ls = transform_exceptions([f, g])
for tr in tr_ls:
print("msg: " + tr.msg + "\nfunction name: " + tr.function.__name__)
Output:
msg: division by zero
function name: f
msg: ok!
function name: g
my code :
from mimetypes import init
class ExceptionProxy(Exception):
def __init__(self, msg, function):
self.msg = msg
self.function = function
def transform_exceptions(func_ls):
exception_list = []
for func in func_ls:
try:
func
except Exception as e:
r = ExceptionProxy(str(e), func)
exception_list.append(r)
else:
r = ExceptionProxy("ok!", func)
exception_list.append(r)
return exception_list
You should do this when calling the function name in the list
func()
Also modified code:
class ExceptionProxy(Exception):
def __init__(self,msg,function):
self.msg = msg
self.function = function
def transform_exceptions(func_ls):
out = []
for x in func_ls:
try:
x()
a = ExceptionProxy("ok!", x)
except Exception as e:
a = ExceptionProxy(str(e), x)
out.append(a)
return out

How to chain Python function calls so the behaviour is as follows

I stumbled upon the following problem on a python challenge: Write a function that satisfies the following rule for any number of function calls.
f()()()()()(s) == 'fooooo' + s;
example:
f('it') == 'fit';
f()('x') == 'fox';
f()()('bar') == 'foobar';
f()()()('l') == 'foool';
The function should be stateless and should not use any variables outside the scope.
The function signature was:
def f(s=None):
# Your code here
I thought that in order to be able to chain multiple calls we will have to return a function when no string is passed into the function, but can't figure out how to build the expected string with no external variables. Suggestions?
def f(s=None):
if s is None:
# concatenate an 'o'?
return f
else:
# Last call, return the result str.
return s
An alternative to Nikola's answer is something like this:
def f(s=None):
if s: return f'f{s}'
def factory(prefix):
def inner(s=None):
return f'f{prefix}{s}' if s else factory(prefix + 'o')
return inner
return factory('o')
using a closure and no helper function.
Obviously, you need to store the number of 'o' somewhere in the memory (e.g. the code) of f. To achieve this, you can benefit from these 2 features of Python:
You can define functions inside other functions
There's this thing called argument binding which allows you to tell Python to fix the some or all of the arguments of your function. This is done through functools.partial
And here's the solution
from functools import partial
def f(s=None):
# Define a new function g which takes the current text and takes s
def g(current_text, s=None):
if s is not None:
return current_text + s
else:
# If called with an empty argument, just rebind current_text with an extra o
return partial(g, current_text + "o")
# Just call g with the initial conditions
return g("f", s)
print(f()()()()()("s")) # fooooos
print(f("s")) # fs
You can try this:
def f(s=None):
string = "f"
def ret(p=None):
nonlocal string
string += "o"
return ret if not p else string + p
return ret if not s else string + s
This is my go at it:
def f(x=None, seq=''):
if x:
return 'f' + seq + x
else:
def g(y=None, p=seq + 'o'):
return f(y, p)
return g
Edit If you really need the function signature to be f(x=None), use this:
def f(x=None):
def f_(x=None, seq=''):
if x:
return 'f' + seq + x
else:
def g(y=None, p=seq + 'o'):
return f_(y, p)
return g
return f_(x)
:^)
Just for printing the string:
def f(s=None):
def fo(s=None):
if s==None:
print('o',end='')
return fo
else:
print(s)
return
if s!=None:
print('f',end='')
print(s)
return
elif s==None:
print('fo',end='')
return fo
Cute problem. This is a compact way to do it:
def f(s=None, prefix="f"):
if s: return prefix + s
return lambda s=None: f(s, prefix=prefix+"o")
FP:
f=lambda s=None,prefix='f':prefix+s if s else lambda s=None,prefix=prefix+'o':f(s,prefix)

Is it possible to assign a variable to use in functions but it should not be global

So what I'm trying to do is I need to assign a variable to use in several functions. But that variable should not be a global variable. How can I do that if it is possible?
Edit: I did forgot one thing. This is for my project and most of it is finished but this. My code needs to be non-repeated as much as it can. I have 5 variables and 15 functions to use some or all of that variables.
Edit2: Let me just post a function here.
def draw_stairs(top_stair_wide, stair_height, stair_count, character):
for o in range(stair_count):
for b in range(stair_height):
draw_straight_line(top_stair_wide, character)
print("")
top_stair_wide += 3
What I need to do is when I use that function, I need to fill "top_stair_wide", "stair_height", "stair_count" with a variable that is not global. I can't just put numbers because I will use those variables in 14 different functions again with maths.
I have a function that draws straight line and before, it inputs and returns character so those are not the problem.
Create a class, make the variables instance variables and turn the functions into methods. Then you can access the instance variables in each method without the explicit need to pass them around.
class C:
def __init__(self, var1, var2, var3):
self.var1 = var1
self.var2 = var2
self.var3 = var3
def add_them(self):
return self.var1 + self.var2 + self.var3
def multiply_them(self):
return self.var1 * self.var2 * self.var3
And so on.
You need parameter(s) in your function definition and then pass your variable(s) as argument(s) when you call it.
Using a main function as you told me in a comment, you could write it like this:
def main():
# Here are your NOT GLOBAL variables:
top_stair_wide = 1
stair_height = 2
stair_count = 3
character = "O"
def draw_stairs(top_stair_wide, stair_height, stair_count, character):
for o in range(stair_count):
for b in range(stair_height):
draw_straight_line(top_stair_wide, character)
print("")
top_stair_wide += 3
# ... more definitions follow
# Then call the functions...
# Job done when you execute the program:
main()
Alternatively:
def main(top_stair_wide, stair_height, stair_count, character): # <-- cram all the expected arguments there
def draw_stairs(top_stair_wide, stair_height, stair_count, character):
for o in range(stair_count):
for b in range(stair_height):
draw_straight_line(top_stair_wide, character)
print("")
top_stair_wide += 3
# ... more definitions follow
# Then call the functions...
# Job done when you execute the program:
main(1, 2, 3, "O")
It's also possible using kwargs, because then you have to know the arguments when you call main() and not when you define it:
def main(**kwargs):
def draw_stairs(**kwargs):
for o in range(kwargs["stair_count"]):
for b in range(kwargs["stair_height"]):
draw_straight_line(kwargs["top_stair_wide"], kwargs["character"])
print("")
kwargs["top_stair_wide"] += 3
# ... more definitions follow
# Then call the functions...
function1(**kwargs)
function2(**kwargs)
function3(**kwargs)
# Job done when you execute the program:
main(top_stair_wide = 1,
stair_height = 2,
stair_count = 3,
character = "O")
You can pass it to your functions like:
def func1(variable):
# your logic here with variable
return 'something'
def func2(variable):
# your logic here with variable
return 'something'
Or you can set it as constant in current file as:
VARIABLE = 'variable'
def func1():
# your logic here with VARIABLE
return 'something'
def func2():
# your logic here with VARIABLE
return 'something'
Another option is using a dictionary storing the shared parameters and only passing this dictionary (rather than all variables separately) as an argument:
def add_params(params):
return params['var1'] + params['var2'] + params['var3']
def multiply_params(params):
return params['var1'] * params['var2'] * params['var3']
>>> params = {'var1': 1, 'var2', 2, 'var3': 3}
>>> add_params(params)
6
>>> multiply_params(params)
6

Categories

Resources