Access variable declared in a function from another function - python

How to access a variable x that is local for a function main() from another function sub() that is called inside main()?
The MEW below throws an error NameError: global name 'x' is not defined when the script is run.
If I add global x, l in main() there are no errors anymore, but I do not want to make these variables global as such.
MWE:
def sub():
print "x from main: {}".format(x)
print "l from main: {}".format(l)
def main():
x = 5
l = [1,2,3,4,5]
print "z from top: {}".format(z)
sub()
if __name__ == "__main__":
z = 'SOME'
main()
Edit1 I am editing an existing code and prefer not to change arity of functions

Passing the parameters as arguments gets what you want done.
def sub(x, l):
print "x from main: {}".format(x)
print "l from main: {}".format(l)
def main(z):
x = 5
l = [1,2,3,4,5]
print "z from top: {}".format(z)
sub(x, l)
if __name__ == "__main__":
z = 'SOME'
main(z)
This prints:
z from top: SOME
x from main: 5
l from main: [1, 2, 3, 4, 5]
If you don't want to use this approach, I guess you could follow this answer's approach, although seems convoluted for a case like this.
import inspect
z = 'SOME'
def sub():
frame = inspect.currentframe()
locales = None
try: locales = frame.f_back.f_locals
finally: del frame
if locales:
for k, v in locales.items():
print("{} from main: {}".format(k, v))
def main():
x = 5
l = [1,2,3,4,5]
print("z from top: {}".format(z))
sub()
main(z)
Maybe this would be cleaner if you could turn this into a context manager.
You could also use *args and *kwargs
def function(*args, **kwargs):
if args:
for a in args:
print "value: {} was passed in args".format(a)
if kwargs:
for k, v in kwargs.items():
print "key {} was passed with value: {} in kwargs".format(k, v)
def caller():
x = 5
l = [1,2,3,4,5]
function(x, l = l)
caller()
#value: 5 was passed in args
#key l was passed with value: [1, 2, 3, 4, 5] in kwargs
From here you can assign values to locals
within function
locals['x'] = args[0]
locals['l'] = kwargs[l]
OR use dict.get this way you can set defaults:
l = kwargs.get('l', None)

The best way to do what you'd like, also requires the least amount of changes. Just change the way you're declaring the variables; by adding the function name before the variable name, you're able to access the variable from anywhere in your code.
def sub():
print "x from main: {}".format(main.x)
print "l from main: {}".format(main.l)
def main():
main.x = 5
main.l = [1,2,3,4,5]
print "z from top: {}".format(z)
sub()
if __name__ == "__main__":
z = 'SOME'
main()
This will print:
z from top: SOME
x from main: 5
l from main: [1, 2, 3, 4, 5]

Related

how to fix TypeError: class() takes no arguments problem?

I am trying to create a class name RangePrime and it's instance attributes should print out the range of them and finally appear an issue. How to fix them?
class RangePrime:
def range_list(self,a,b):
self.a = a
self.b = b
x = []
if b < a:
x.extend(range(b, a))
x.append(a)
else:
x.extend(range(a, b))
x.append(b)
return x
after i ran this -> range_prime = RangePrime(1, 5) and it start to appear
TypeError: RangePrime() takes no arguments
I should get the following result:
>>> range_prime = RangePrime(1, 5)
>>> range_prime.range_list
[1, 2, 3, 4, 5]
It looks you have mixed using functions without a class definition and at the same time misdefining the functions within the class by lacking an __init__(). I have modified your code a bit to account for the class and its functions, so the intention of your code remains the same. Kindly try:
class RangePrime():
def __init__(self, a,b):
self.a = a
self.b = b
def range_list(self):
a = self.a
b = self.b
x = []
if b < a:
x.extend(range(b, a))
x.append(a)
else:
x.extend(range(a, b))
x.append(b)
return x
Which when running:
range_prime = RangePrime(1, 5)
range_prime.range_list()
Returns:
[1,2,3,4,5]
create object of the class first and then pass those parameter in your class method then it will work
ob=RangePrime()
print(ob.range_list(1,5))

Remove unused variables in Python source code

The Question
Is there a straightforward algorithm for figuring out if a variable is "used" within a given scope?
In a Python AST, I want to remove all assignments to variables that are not otherwise used anywhere, within a given scope.
Details
Motivating example
In the following code, it is obvious to me (a human), that _hy_anon_var_1 is unused, and therefore the _hy_anon_var_1 = None statements can be removed without changing the result:
# Before
def hailstone_sequence(n: int) -> Iterable[int]:
while n != 1:
if 0 == n % 2:
n //= 2
_hy_anon_var_1 = None
else:
n = 3 * n + 1
_hy_anon_var_1 = None
yield n
# After
def hailstone_sequence(n: int) -> Iterable[int]:
while n != 1:
if 0 == n % 2:
n //= 2
else:
n = 3 * n + 1
yield n
Bonus version
Extend this to []-lookups with string literals as keys.
In this example, I would expect _hyx_letXUffffX25['x'] to be eliminated as unused, because _hyx_letXUffffX25 is local to h, so _hyx_letXUffffX25['x'] is essentially the same thing as a local variable. I would then expect _hyx_letXUffffX25 itself to be eliminated once there are no more references to it.
# Before
def h():
_hyx_letXUffffX25 = {}
_hyx_letXUffffX25['x'] = 5
return 3
# After
def h():
return 3
From what I can tell, this is somewhat of an edge case, and I think the basic algorithmic problem is the same.
Definition of "used"
Assume that no dynamic name lookups are used in the code.
A name is used if any of these are true in a given scope:
It is referenced anywhere in an expression. Examples include: an expression in a return statement, an expression on the right-hand side of an assignment statement, a default argument in a function definition, being referenced inside a local function definition, etc.
It is referenced on the left-hand side of an "augmented assignment" statement, i.e. it is an augtarget therein. This might represent "useless work" in a lot of programs, but for the purpose of this task that's OK and distinct from being an entirely unused name.
It is nonlocal or global. These might be useless nonlocals or globals, but because they reach beyond the given scope, it is OK for my purposes to assume that they are "used".
Please let me know in the comments if this seems incorrect, or if you think I am missing something.
Examples of "used" and "unused"
Example 1: unused
Variable i in f is unused:
def f():
i = 0
return 5
Example 2: unused
Variable x in f is unused:
def f():
def g(x):
return x/5
x = 10
return g(100)
The name x does appear in g, but the variable x in g is local to g. It shadows the variable x created in f, but the two x names are not the same variable.
Variation
If g has no parameter x, then x is in fact used:
def f():
x = 10
def g():
return x/5
return g(100)
Example 3: used
Variable i in f is used:
def f():
i = 0
return i
Example 4: used
Variable accum in silly_map and silly_sum is used in both examples:
def silly_map(func, data):
data = iter(data)
accum = []
def _impl():
try:
value = next(data)
except StopIteration:
return accum
else:
accum.append(value)
return _impl()
return _impl()
def silly_any(func, data):
data = iter(data)
accum = False
def _impl():
nonlocal accum, data
try:
value = next(data)
except StopIteration:
return accum
else:
if value:
data = []
accum = True
else:
return _impl()
return _impl()
The solution below works in two parts. First, the syntax tree of the source is traversed and all unused target assignment statements are discovered. Second, the tree is traversed again via a custom ast.NodeTransformer class, which removes these offending assignment statements. The process is repeated until all unused assignment statements are removed. Once this is finished, the final source is written out.
The ast traverser class:
import ast, itertools, collections as cl
class AssgnCheck:
def __init__(self, scopes = None):
self.scopes = scopes or cl.defaultdict(list)
#classmethod
def eq_ast(cls, a1, a2):
#check that two `ast`s are the same
if type(a1) != type(a2):
return False
if isinstance(a1, list):
return all(cls.eq_ast(*i) for i in itertools.zip_longest(a1, a2))
if not isinstance(a1, ast.AST):
return a1 == a2
return all(cls.eq_ast(getattr(a1, i, None), getattr(a2, i, None))
for i in set(a1._fields)|set(a2._fields) if i != 'ctx')
def check_exist(self, t_ast, s_path):
#traverse the scope stack and remove scope assignments that are discovered in the `ast`
s_scopes = []
for _ast in t_ast:
for sid in s_path[::-1]:
s_scopes.extend(found:=[b for _, b in self.scopes[sid] if AssgnCheck.eq_ast(_ast, b) and \
all(not AssgnCheck.eq_ast(j, b) for j in s_scopes)])
self.scopes[sid] = [(a, b) for a, b in self.scopes[sid] if b not in found]
def traverse(self, _ast, s_path = [1]):
#walk the ast object itself
_t_ast = None
if isinstance(_ast, ast.Assign): #if assignment statement, add ast object to current scope
self.traverse(_ast.targets[0], s_path)
self.scopes[s_path[-1]].append((True, _ast.targets[0]))
_ast = _ast.value
if isinstance(_ast, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
s_path = [*s_path, (nid:=(1 if not self.scopes else max(self.scopes)+1))]
if isinstance(_ast, (ast.FunctionDef, ast.AsyncFunctionDef)):
self.scopes[nid].extend([(False, ast.Name(i.arg)) for i in _ast.args.args])
_t_ast = [*_ast.args.defaults, *_ast.body]
self.check_exist(_t_ast if _t_ast is not None else [_ast], s_path) #determine if any assignment statement targets have previously defined names
if _t_ast is None:
for _b in _ast._fields:
if isinstance((b:=getattr(_ast, _b)), list):
for i in b:
self.traverse(i, s_path)
elif isinstance(b, ast.AST):
self.traverse(b, s_path)
else:
for _ast in _t_ast:
self.traverse(_ast, s_path)
Putting it all together:
class Visit(ast.NodeTransformer):
def __init__(self, asgn):
super().__init__()
self.asgn = asgn
def visit_Assign(self, node):
#remove assignment nodes marked as unused
if any(node.targets[0] == i for i in self.asgn):
return None
return node
def remove_assgn(f_name):
tree = ast.parse(open(f_name).read())
while True:
r = AssgnCheck()
r.traverse(tree)
if not (k:=[j for b in r.scopes.values() for k, j in b if k]):
break
v = Visit(k)
tree = v.visit(tree)
return ast.unparse(tree)
print(remove_assgn('test_name_assign.py'))
Output Samples
Contents of test_name_assign.py:
def hailstone_sequence(n: int) -> Iterable[int]:
while n != 1:
if 0 == n % 2:
n //= 2
_hy_anon_var_1 = None
else:
n = 3 * n + 1
_hy_anon_var_1 = None
yield n
Output:
def hailstone_sequence(n: int) -> Iterable[int]:
while n != 1:
if 0 == n % 2:
n //= 2
else:
n = 3 * n + 1
yield n
Contents of test_name_assign.py:
def h():
_hyx_letXUffffX25 = {}
_hyx_letXUffffX25['x'] = 5
return 3
Output:
def h():
return 3
Contents of test_name_assign.py:
def f():
i = 0
return 5
Output:
def f():
return 5
Contents of test_name_assign.py:
def f():
x = 10
def g():
return x/5
return g(100)
Ouptut:
def f():
x = 10
def g():
return x / 5
return g(100)

Init a generator

I have the following generator function which adds two numbers:
def add():
while True:
x = yield "x="
y = yield "y="
print (x+y)
And I can call it like this:
x=add()
next(x)
'x='
x.send(2)
'y='
x.send(3)
# 5
I thought it would be trivial to add in an init so that I don't have to do the next and I can just start sending it values, and so I did:
def init(_func):
def func(*args, **kwargs):
x=_func(*args, **kwargs)
next(x)
return x
return func
Or, simplifying it to receive no input variables like the function above:
def init(func):
x=func()
next(x)
return x
I thought that doing:
x=init(add) # doesn't print the "x=" line.
x.send(2)
'y='
x.send(3)
5
Would work, but it seems it acts just like as if the init is not there at all. Why does this occur, and how can I get rid of that behavior?
It seems to work for me. Tried
def add():
while True:
x = yield 'x='
y = yield 'y='
print (x+y)
def init(func):
x=func()
next(x)
return x
a = init(add)
a.send(5)
a.send(10)
For me this returns 15, as expected.
[update]
After your update, I think you might just want to print out the a.send():
def add():
while True:
x = yield 'x='
y = yield 'y='
print (x+y)
def init(func):
x=func()
print(next(x))
return x
a = init(add)
print(a.send(5))
a.send(10)
Your code works as-is, however, if you want to print the output that occurs before the field yield statement, then you can adapt the init method to do just that. For example:
def init(func):
x=func()
a=next(x)
if a: print (a) # this line will print the output, in your case 'x='
return x
And now you have:
>>> x=init(add)
x=
>>> x.send(2)
'y='
>>> x.send(3)
5
And finally, to keep your more generalized approach, you can do something like the following with a decorator:
def init_coroutine(_func):
def func(*args, **kwargs):
x=_func(*args, **kwargs)
_ = next(x)
if _: print (_)
return x
return func
#init_coroutine
def add():
while True:
x = yield "x="
y = yield "y="
print (x+y)
>>> x=add()
x=
>>> x.send(2)
'y='
>>> x.send(3)
5

How to make variable accessible inside another function

Consider this example:
def func1():
val = 1
res = [1]
def fun2():
print(res)
print(val)
val = 2
fun2()
print(val)
func1()
It raises the following exception:
UnboundLocalError: local variable 'val' referenced before assignment
List res can be accessed by fun2, but val cannot. I know list is mutable and int is not, but is there a way to make val accessible by fun2 as well? In a class, I could easily achieve that with self.val, but is there a way to do it inside a function?
Use the nonlocal statement to make a variable defined in an enclosing function accesible inside the inner function, like so:
def func1():
val = 1
res = [1]
def fun2():
nonlocal val
print(res)
print(val)
val = 2
fun2()
print(val)
func1()
See also: earlier SO question.
You can do it in the following way:
def func1():
val = 1
res = [1]
def fun2(val=val, res=res):
print(res)
print(val)
val = 2
return val
val = fun2()
print(val)
Output is then
>>> func1()
[1]
1
2

How do I reset variables to standard values in Python

I am working on an application where variables get initialized to default values.
The user can change those values at any time. It should be possible for the user to reset some or all of the variables to default at any time.
How is the best way of going about this?
This would be a solution, but I have a feeling that it is suboptimal. Can you tell me if my feeling is correct and how I can do it better?
A_DEFAULT = "A_def"
B_DEFAULT = "B_def"
C_DEFAULT = "C_def"
class BusinessLogic_1(object):
def __init__(self):
self.setVariablesToDefault()
def setVariablesToDefault(self, variableNames=None):
# pass None to set all Variables to default
variableNames = variableNames or ["A","B","C"]
if "A" in variableNames:
self.A = A_DEFAULT
if "B" in variableNames:
self.B = B_DEFAULT
if "C" in variableNames:
self.C = C_DEFAULT
def printVariables(self):
print "A: %s, B: %s, C: %s" % (self.A, self.B, self.C)
if __name__ == "__main__":
print "0: Initialize"
businessLogic_1 = BusinessLogic_1()
businessLogic_1.printVariables()
print "Change A,B,C and then reset A,C"
businessLogic_1.A = "A_new"
businessLogic_1.B = "B_new"
businessLogic_1.C = "C_new"
businessLogic_1.printVariables()
This might be a solution.
It's a way to pass names of variables as strings and then to access them via these names.
Objects can have more than one name simultaneously, but names always point to a single object (at a given point of time).
class classWithVariablesThatHaveDefaultValues(object):
def __init__(self, some, example, variables, defaultValuesDict):
self.some = some
self.example = example
self.variables = variables
self.dictionaryWithDefaultValues = defaultValuesDict
def resetValues(self, *listOfVariables):
for variable in listOfVariables:
for key in self.dictionaryWithDefaultValues:
if key == variable:
vars(self)[key] = self.dictionaryWithDefaultValues[key]
if __name__ == "__main__":
defaultValuesDict = {"some":4, "example":5, "variables":6}
exampleObject = classWithVariablesThatHaveDefaultValues(1, 2, 3, defaultValuesDict)
exampleObject.some = 15
print exampleObject.some, exampleObject.example, exampleObject.variables
exampleObject.resetValues("example", "some")
print exampleObject.some, exampleObject.example, exampleObject.variables
The Output is:
15 2 3
4 5 3
You can achieve it in a manner very similar to how decimal.localcontext() works, but depending on your use case, this may not be suitable. This will allow you to manipulate and call any methods on the original object reflecting the updated values, and at the end of the with block, reset them to the values upon entry.
from contextlib import contextmanager
class A(object):
def __init__(self):
self.a = 3
self.b = 5
def display(self):
return self.a, self.b
a = A()
#contextmanager
def mycontext(obj):
old_a = obj.a
old_b = obj.b
yield obj
obj.a = old_a
obj.b = old_b
print 'before:', a.display()
with mycontext(a) as obj:
print 'entered:', a.display()
a.a = 3
a.b = 7
print 'changed:', a.display()
print 'hopefully original', a.display()
before: (3, 5)
entered: (3, 5)
changed: (3, 7)
hopefully original (3, 5)

Categories

Resources