How to make a function accept a local variable as a parameter - python

I have defined the following functions
def f(x):
return x*a
def g(x,a):
return f(x)
g(1,2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in g
File "<stdin>", line 2, in f
NameError: name 'a' is not defined
Now if I try to evaluate g(x,a) for any value of x and a it states that a is not defined. I suspect this is because a should be a global variable.
I have heard that using global variables is bad practice so my question is how do I make g(x,a) give a result with a given as an argument?
Note: The reason I am not giving a as an argument to f(x) is because it needs to be solved as a differential equation (using scipy) with only the relevant variables as arguments.

Since a is a variable within the function g, why not avoid the entire global situation by the following:
def f(x):
# do something to alter x
return x
def g(x, a):
return f(x)*a
g(1,2) # returns f(x) *2

Related

Python AST exec "... is not defined" error on recursive function

I came across this error
def test_rec():
import ast
exec(compile(ast.fix_missing_locations(ast.parse("""
def fact(n):
return 1 if n == 0 else n * fact(n - 1)
print(fact(5))
"""), "<string>", "exec")))
This yield this error, which is weird
Traceback (most recent call last):
File "/Users/gecko/.pyenv/versions/3.9.0/envs/lampy/lib/python3.9/site-packages/nose/case.py", line 198, in runTest
self.test(*self.arg)
File "/Users/gecko/code/lampycode/tests/test_let_lang.py", line 6, in test_rec
exec(compile(ast.fix_missing_locations(ast.parse("""
File "<string>", line 4, in <module>
File "<string>", line 3, in fact
NameError: name 'fact' is not defined
If I copy and paste the same code in REPL it works fine
>>> def fact(n):
... return 1 if n == 0 else n * fact(n - 1)
...
>>> print(fact(5))
120
>>>
Any ideas?
I could reduce the problem further here is the minimal exempla, this would overflow the stack but it gives me the same not defined error
def test_rec3():
exec("""
def f():
f()
f()
""")
--
Second edit, going even further, this only happens inside functions
This works
exec("""
def f(n):
print("end") if n == 1 else f(n-1)
f(10)""")
But this gives me the same error as above
def foo():
exec("""
def f(n):
print("end") if n == 1 else f(n-1)
f(10)""")
foo()
If you use exec with the default locals, then binding local variables is undefined behavior. That includes def, which binds the new function to a local variable.
Also, functions defined inside exec can't access closure variables, which fact would be.
The best way to avoid these problems is to not use exec. The second best way is to provide an explicit namespace:
namespace = {}
exec(whatever, namespace)

Different results when passing function / method to equation solver

This question is a follow-up of previous question pass class method to fsolve.
#jim's answer in that question about difference between the function object name and a function call clarified my confusion and solved the problem. However, when I tried similar things in sympy:
from sympy.solvers import solve
from sympy import Symbol
class Demo():
def __init__(self, var):
self.i = var
def func(self):
return self.i ** 2 - 4
x = Symbol('x')
def func(v):
return v ** 2 - 4
new = Demo(x)
solve(new.func(), x) # This works fine, even as a function call
solve(func(x), x) # This works fine, even as a function call
Why do I have different results? (In scipy I need to pass function name to solver while in sympy I need to pass the function call.) Is it because different implementation of the two libraries? In the above example, if I substitute the function call with function name, exception will be raised:
File "<ipython-input-26-3554c1f86646>", line 13, in <module>
solve(new.func, x)
File "Anaconda3\lib\site-packages\sympy\solvers\solvers.py", line 817, in solve
f, symbols = (_sympified_list(w) for w in [f, symbols])
File "Anaconda3\lib\site-packages\sympy\solvers\solvers.py", line 817, in <genexpr>
f, symbols = (_sympified_list(w) for w in [f, symbols])
File "Anaconda3\lib\site-packages\sympy\solvers\solvers.py", line 808, in _sympified_list
return list(map(sympify, w if iterable(w) else [w]))
File "Anaconda3\lib\site-packages\sympy\core\sympify.py", line 324, in sympify
raise SympifyError('could not parse %r' % a, exc)
SympifyError: Sympify of expression 'could not parse '<bound method Demo.func of <__main__.Demo object at 0x0000018A37DA6518>>'' failed, because of exception being raised:
SyntaxError: invalid syntax (<string>, line 1)
When the solver is provided parameter (func(x), x), it is equivalent to provide (x ** 2 - 4, x), the first part of which is an expression containing the declared symbol x.
However when the parameter list is (func, x), the variable in function definition, v, is undefined and does not match that provided as the second argument, which is x. This causes exception to be raised.
In the scipy case, returned expression of the function definition has one variable, var, to be solved, and can thus work properly.

How does the list.append work?

alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger']
>>> show('tiger','cat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (2 given)
>>> show('tiger','cat', {'name':'tom'})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (3 given)
Since the method append of alist only accepts one argument, why not detect a syntax error on the line alist.append(*args, **kwargs) in the definition of the method show?
It's not a syntax error because the syntax is perfectly fine and that function may or may not raise an error depending on how you call it.
The way you're calling it:
alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger']
>>> show('tiger','cat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in show
TypeError: append() takes exactly one argument (2 given)
A different way:
alist = []
def show(*args, **kwargs):
alist.append(*args, **kwargs)
print(alist)
>>> show('tiger')
['tiger', 'tiger']
>>> class L: pass
...
>>> alist = L()
>>> alist.append = print
>>> show('tiger','cat')
tiger cat
<__main__.L object at 0x000000A45DBCC048>
Python objects are strongly typed. The names that bind to them are not. Nor are function arguments. Given Python's dynamic nature it would be extremely difficult to statically predict what type a variable at a given source location will be at execution time, so the general rule is that Python doesn't bother trying.
In your specific example, alist is not in the local scope. Therefore it can be modified after your function definition was executed and the changes will be visible to your function, cf. code snippets below.
So, in accord with the general rule: predicting whether or not alist will be a list when you call .append? Near-impossible. In particular, the interpreter cannot predict that this will be an error.
Here is some code just to drive home the point that static type checking is by all practical means impossible in Python. It uses non-local variables as in your example.
funcs = []
for a in [1, "x", [2]]:
def b():
def f():
print(a)
return f
funcs.append(b())
for f in funcs:
f()
Output:
[2] # value of a at definition time (of f): 1
[2] # value of a at definition time (of f): 'x'
[2] # value of a at definition time (of f): [2]
And similarly for non-global non-local variables:
funcs = []
for a in [1, "x", [2]]:
def b(a):
def f():
print(a)
a = a+a
return f
funcs.append(b(a))
for f in funcs:
f()
Output:
2 # value of a at definition time (of f): 1
xx # value of a at definition time (of f): 'x'
[2, 2] # value of a at definition time (of f): [2]
It's not a syntax error because it's resolved at runtime. Syntax errors are caught initially during parsing. Things like unmatched brackets, undefined variable names, missing arguments (this is not a missing argument *args means any number of arguments).
show has no way of knowing what you'll pass it at runtime and since you are expanding your args variable inside show, there could be any number of arguments coming in and it's valid syntax! list.append takes one argument! One tuple, one list, one int, string, custom class etc. etc. What you are passing it is some number elements depending on input. If you remove the * it's all dandy as its one element e.g. alist.append(args).
All this means that your show function is faulty. It is equipped to handle args only when its of length 1. If its 0 you also get a TypeError at the point append is called. If its more than that its broken, but you wont know until you run it with the bad input.
You could loop over the elements in args (and kwargs) and add them one by one.
alist = []
def show(*args, **kwargs):
for a in args:
alist.append(a)
for kv in kwargs.items():
alist.append(kv)
print(alist)

Python global and local variables

In Python 2.7, running the following code:
def f():
a = a + 1
f()
gives the following result:
Traceback (most recent call last):
File "test.py", line 4, in <module>
f()
File "test.py", line 2, in f
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment
But if I change the code to below:
def f():
a[0] = a[0] + 1
f()
I get the different error:
Traceback (most recent call last):
File "test.py", line 4, in <module>
f()
File "test.py", line 2, in f
a[0] = a[0] + 1
NameError: global name 'a' is not defined
Why is Python considering a is a local variable when it is an int, global when list? What's the rationale behind this?
P.S.: I was experimenting after reading this thread.
The key is found in the documentation on the assignment statement:
Assignment of an object to a single target is recursively defined as
follows.
If the target is an identifier (name) (e. g. a = a + 1):
If the name does not occur in a global statement in the current code
block: the name is bound to the object in the current local namespace.
Otherwise: the name is bound to the object in the current global
namespace.
The name is rebound if it was already bound. This may cause
the reference count for the object previously bound to the name to
reach zero, causing the object to be deallocated and its destructor
(if it has one) to be called.
...
If the target is a subscription (e. g. a[0] = a[0] + 1): The primary expression in the
reference is evaluated. It should yield either a mutable sequence
object (such as a list) or a mapping object (such as a dictionary).
Next, the subscript expression is evaluated.
In f1 Python sees that you are binding some value to a, sees that a has not been used in a global a statement in this scope and prepares a local variable. Then it attempts to evaluate the expression a + 1, looks up the variable a and finds an uninitialized local variable. This results in the UnboundLocalError.
In f2 Python sees that you are assigning some value to a subscription of the variable a. It looks up that variable in the local namespace and fails to find it. It then walks up the non-local namespaces (closures) until it reaches the global namespace. Once it fails to find a in the global namespace it throws a NameError.
Could you try to do something like this :
def f(a):
a += 1
print a
def g():
a = 3
f(a)
g()

difference between F(x) and F x in Python

In Python it is possible to call either del x or del (x) . I know how to define a function called F(x) , but I do not know how to define a function that cal be called like del, without a tuple as parameters.
What is the difference between F x and F(x), and how can I define a function that can be called without parenthesis ?
>>> a = 10
>>> a
10
>>> del a <------------ can be called without parenthesis
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> a = 1
>>> del (a)
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> def f(x): 1
...
>>> f (10)
>>> print f (10)
None
>>> def f(x): return 1
...
>>> print f (10)
1
>>> f 1 <------ cannot be called so
File "<stdin>", line 1
f 1
^
SyntaxError: invalid syntax
>>>
The main reason is that del is actually a statement and therefore has special behavior in Python. Therefore you cannot actually define these (and this behavior) yourself* - it is a built-in part of the language for a set of reserved keywords.
**I guess you could potentially edit the source of Python itself and build your own in, but I don't think that is what you're after :)*

Categories

Resources