How can I change the value of variable in a exec() function? - python-3.x

This is the code:
def test():
c = 0
exec('c = 4 + 5')
print('result', c)
The value of c is 0. What can I do to fix it, let the value of c changed in exec() function?

In cpython, you can't do what you want inside a function. That's because a function's local namespace can't be easily edited by other code (editing the dictionary returned by locals() doesn't do it). If instead you call exec from the top level of your module, it will work as you want, but it is still probably a bad idea.
A better approach may be to use a namespace dictionary. You can pass it to exec and the change will appear there, rather than in the function's local namespace or the module's global namespace:
def test():
namespace = {'c': 0}
exec('c = 4 + 5', namespace)
print('result', namespace['c'])

Related

A problem when I use enclosing functions with global/nonlocal variables in Python3.8

I want to figure out how does the enclosing function work, and I run three code blocks as below.
Block 1
def a1_(r):
r+=1
res = 0
print('a1_res=',id(res))
def a2(r):
global res
print('res = ',res)
print('a2_res=',id(res))
res+=r
return res
return a2(r)
a1_(1)
it cannot work and the error is NameError: name 'res' is not defined which occurred in Line 6.
Block 2
def foo():
a = 1
def foo2():
global a
print(a)
a+=1
return a
return foo2()
foo()
it is work without any error.
Block 3
I replaced the 'global' to 'nonlocal' in Block 1, it works!
def a1_(r):
r+=1
res = 0
print('a1_res=',id(res))
def a2(r):
nonlocal res
print('res = ',res)
print('a2_res=',id(res))
res+=r
return res
return a2(r)
a1_(1)
Just feeling quite confused, because The structure bewteen block 1 and block 2 is similar. However, block 2 works but block 1 not.
Also, I just replaced 'global' by 'nonlocal' in block 3, it works.(PS: In my opinion, the effect of global and nonlocal is the same)
Could someone help me to figure it out? Thanks a lot!
As far as I can tell Block 2 shouldn't work as well, at least it doesn't for me. You can use print(globals()) to see which variables are in the global namespace.
For both Block 1 and Block 2 you try to access a variable in the global namespace which only works if you defined that variable in that namespace.
For Block 3 you are using nonlocal to access a variable in the outer namespace. If you search for python namespaces in a search engineyou can probably learn more about what you are investigating (for example this or this).
Edit: NameError: name 'res' is not defined tells you that this variable is not defined in the relevant namespace. Calling global res tells Python is okay even if res isn't defined in the global namespace from before, but before using it it needs to be defined thus the line causing the error should be print('res = ', res) as here res is not defined in the global namespace.

Parameterize function name in Python

I have a generic function for which I would like the name to change based on the value of predefined variable. Not sure this is possible in Python:
n = 'abc'
def f{n}(x):
print(x)
f{n}(3)
In the above, the function name will end up becoming "fabc"?
It is possible using a code-generation + exec pattern. It is also possible by instantiating a types.FunctionType instance directly. But a much simpler solution would be to leave the generic name, and then inject an alias (another reference to the same function object) for the dynamic name into whichever namespace you want.
>>> def generic_function(x):
... print(x ** 2)
...
>>> dynamic_name = "func_abc"
>>> globals()[dynamic_name] = generic_function
>>> func_abc(3)
9
To inject in some other module namespace that would be a setattr call:
setattr(other_mod, dynamic_name, generic_function)
You could also rewrite the function's __name__ attribute if you wanted to, but there's probably not much point.
You could do it with something like the below:
def f(n):
exec(f"def f{n}(x): print(x)")
return eval(f"f{n}")

Why can't I access a variable that's being returned from a function?

I am new to Python and am at a lost as to what I'm doing wrong. I am trying to use the fqdn variable that is being returned to the caller which is main() but I'm getting NameError: name 'fqdn' is not defined
I'm betting this is some type of global variable statement issue or something like that, but I've been researching this and can't figure it out.
If a function from a module returns a value, and the caller is main(), shouldn't main() be able to use that returned value???
Here's the layout:
asset.py
def import_asset_list():
# Open the file that contains FQDNs
openfile = open(r"FQDN-test.txt")
if openfile.mode == 'r':
# Remove CR from end of each item
fqdn = openfile.read().splitlines()
# Add https to the beginning of every item in list
fqdn = ["https://" + item for item in fqdn]
openfile.close()
return fqdn
tscan.py
def main():
import asset
asset.import_asset_list()
# Iterate through list
for i in fqdn:
if SCHEDULED_SCAN == 1:
create_scheduled_scan(fqdn)
launch_scan(sid)
check_status_scan(uuid)
else:
create_scan(fqdn)
launch_scan(sid)
check_status_scan(uuid)
Short Explanation
Yes, main() should be able to use the returned value, but it's only the value that is returned, not the variable name. You have to define a variable of your own name, to receive the value, and use that instead.
Long Explanation
The name of a variable inside any function is simply a "label" valid only within the scope of this function. A function is an abstraction which means "Give me some input(s), and I will give you some output(s)". Within the function, you need to reference the inputs somehow and, potentially, assign some additional variables to perform whatever it is you would like to. These variable names have no meaning whatsoever outside the function, other than to, at most, convey some information as to the intended use of the function.
When a function returns a value, it does not return the "name" of the variable. Only the value (or the reference in memory) of the variable. You can define your own variable at the point where you call the function, give it your own name and assign to it the returned result of the function, so you simply have to write:
def main():
import asset
my_asset_list = asset.import_asset_list()
# Iterate through list
for i in my_asset_list:
if SCHEDULED_SCAN == 1:
create_scheduled_scan(my_asset_list)
launch_scan(sid)
check_status_scan(uuid)
else:
create_scan(my_asset_list)
launch_scan(sid)
check_status_scan(uuid)
I don't know where the uuid and the sid variables are defined.
To make sure you have understood this properly, remember:
You can have multiple functions in the same file, and use identically-named variables within all those functions, this will be no problem because a variable (with its name) only exists within each specific function scope.
Variable names do not "cross" the boundaries of the scope, only variable values/references and to do this, a special construct is used, i.e. the return [something] statement.

Indirect variable modification not working withing class, but works outside of class (Python 3.7)

Since I get variable definitions from an external text file i need to modify my local variables indirectly.
What I want to do is basically working, but not once I try to implement it within a class.
X = "0"
vars()["X"]+="1"
print(X) #gives "01" as expected
class Test:
def __init__(self):
x = "0"
vars()["x"]+="1"
self.x = x
test = Test()
print(test.x) # gives "0", but why?
While the procedual code snip produces the expected result "01", the code inside the class does not ("0"). Why?
Here is what the manuals says about vars (https://docs.python.org/3/library/functions.html#vars):
Return the dict attribute for a module, class, instance, or any other object with a dict attribute.
Objects such as modules and instances have an updateable dict attribute; however, other objects may have write restrictions on their dict attributes (for example, classes use a types.MappingProxyType to prevent direct dictionary updates).
Without an argument, vars() acts like locals(). Note, the locals dictionary is only useful for reads since updates to the locals dictionary are ignored.
In other words, when writing directly you are at the module level that has a writable __dict__, not so when you are inside a method.
Although this is bad practice and is not recommended, but this can help you get going:
class Test:
def __init__(self):
self.x = '0'
self.__dict__['x']+='1'
test = Test()
print(test.x) # gives "01"
You are mixing a lot of things here. First I will comment on your code:
X = "0"
vars()["X"]+="1"
print(X) #gives "01" as expected
class Test:
def __init__(self):
global X
x = "0" # this is a local variable inside __init__()
vars()["X"]+="1" # vars() only shows local variables, will lead to an key error because vars()['X'] does not exist
self.x = x # assign member variable with value of local variable
test = Test()
print(test.x) # gives "0", because you just assigned self.x = x, which is "0"
You could use the global keyword to make X visible inside init() but this is considered bad practice.
X = "0"
class Test(object):
def __init__(self):
global X
self.x = X + "1"
test = Test()
print(test.x) # gives "01"
You better initalize Test with the variable needed:
X = "0"
class Test(object):
def __init__(self, myvar: str):
self.x = myvar + "1"
test = Test(X)
print(test.x) # gives "01"
The documenation for vars() without an argument says:
Without an argument, vars() acts like locals(). Note, the locals dictionary is only useful for reads since updates to the locals dictionary are ignored.
But that is incomplete. The documentation for locals() says:
Note that at the module level, locals() and globals() are the same dictionary.
And changes to the dictionary returned by globals() are not ignored as it is the actual symbol table:
Return a dictionary representing the current global symbol table. This is always the dictionary of the current module (inside a function or method, this is the module where it is defined, not the module from which it is called).
So by this really strange design, vars() when used at module level actually does return the writeable symbol table -- and anywhere else, it uses a view on which writes are ignored.
None of this really matters, as using these functions is usually a good sign that you're doing something wrong, but still, here it is.

How do I set an instance attribute in Python when the instance is determined by a function?

I would like to iterate through a selection of class instances and set a member variable equal to a value. I can access the members value with:
for foo in range(1,4): #class members: pv1, pv2, pv3
bar[foo] ='{0}'.format(locals()['pv' + str(foo)+'.data'])
However when I try to set/mutate the values like so:
for foo in range(1,4): #class members:
'{0}'.format(locals()['pv' + str(foo)+'.data']) = bar[foo]
I obviously get the error:
SyntaxError: can't assign to function call
I have tried a few methods to get it done with no success. I am using many more instances than 3 in my actual code(about 250), but my question is hopefully clear. I have looked at several stack overflow questions, such as Automatically setting class member variables in Python -and- dynamically set an instance property / memoized attribute in python? Yet none seem to answer this question. In C++ I would just use a pointer as an intermediary. What's the Pythonic way to do this?
An attr is a valid assignment target, even if it's an attr of the result of an expression.
for foo in range(1,3):
locals()['pv' + str(foo)].data = bar[foo]
Another developer wrote a few lines about setattr(), mostly about how it should be avoided.
setattr is unnecessary unless the attribute name is dynamic.
But they didn't say why. Do you mind elaborating why you switched your answer away from setattr()?
In this case, the attr is data, which never changes, so while
for i in range(1, 3):
setattr(locals()['pv' + str(i)], 'data', bar[i])
does the same thing, setattr isn't required here. The .data = form is both good enough and typically preferred--it's faster and has clearer intent--which is why I changed it. On the other hand, if you needed to change the attr name every loop, you'd need it, e.g.
for i in range(1,3):
setattr(locals()['pv' + str(i)], 'data' + str(i), bar[i])
The above code sets attrs named data1, data2, data3, unrolled, it's equivalent to
pv1.data1 = bar[1]
pv2.data2 = bar[2]
pv3.data3 = bar[3]
I originally thought your question needed to do something like this, which is why I used setattr in the first place. Once I tested it and got it working I just posted it without noticing that the setattr was no longer required.
If the attr name changes at runtime like that (what the other developer meant by "dynamic") then you can't use the dot syntax, since you have a string object rather than a static identifier. Another reason to use setattr might be if you need a side effect in an expression. Unlike in C, assignments are statements in Python. But function calls like setattr are expressions.
Here is an example of creating a class which explicitly allows access through index or attribute calls to change internal variables. This is not generally promoted as 'good programming' though. It does not explicitly define the rules by which people should be expected to interact with the underlying variables.
the definition of __getattr__() function allows for the assignment of (object).a .
the definition of __getitem__() function allows for the assignment of
(object)['b']
class Foo(object):
def __init__(self, a=None,b=None,c=None):
self.a=a
self.b=b
self.c=c
def __getattr__(self, x):
return self.__dict__.get(x, None)
def __getitem__(self, x):
return self.__dict__[x]
print
f1 = Foo(3,2,4)
print 'f1=', f1.a, f1['b'], f1['c']
f2 = Foo(4,6,2)
print 'f2=', f2.a, f2['b'], f2['c']
f3 = Foo(3,5,7)
print 'f3=', f3.a, f3['b'], f3['c']
for x in range(1, 4):
print 'now setting f'+str(x)
locals()['f'+str(x)].a=1
locals()['f'+str(x)].b=1
locals()['f'+str(x)].c=1
print
print 'f1=', f1.a, f1['b'], f1['c']
print 'f2=', f2.a, f2['b'], f2['c']
print 'f3=', f3.a, f3['b'], f3['c']
The result is
f1= 3 2 4
f2= 4 6 2
f3= 3 5 7
now setting f1
now setting f2
now setting f3
f1= 1 1 1
f2= 1 1 1
f3= 1 1 1

Resources