Passing function parameter name to another function in Python - python

Given a simple function:
def A(a = 1, b = 2):
return a+b
I want to write another function to change the default parameter value, for either a or b. And user can specify the parameter to change by setting var = a or var = b. For example:
def B(var = 'a', new_value = 10):
temp_dict = {var:new_value}
ans = A(var)
return ans
or
def B(var = 'a', new_value = 10):
ans = A(var = new_value)
return ans
In function def B(), after setting var = a and var = new_value = 10, I expect A(var = new_value) to achieve the same effect as A(a = 10). Do you know the correct way to write function def B()?
Thanks.

You are almost there. From your B() function, while making the call to A(), you need to unpack the temp_dict and pass it as an argument to A(). See below:
>>> def A(a = 1, b = 2):
... return a+b
...
>>> def B(var = 'a', new_value = 10):
... temp_dict = {var:new_value}
... ans = A(**temp_dict)
# ^ unpack the dict, and pass it as an argument
... return ans
...
>>> B()
12
For more details on how this ** works with a dict, please take a look at:
Python: *args and **kwargs?
What does ** (double star/asterisk) and * (star/asterisk) do for parameters?

I took the liberty of interpreting a bit what the OP said he wanted, i.e. change the default parameter value, for either a or b. So what I did was to return a transformed function A with either the a or b defaults changed via a partial:
from functools import partial
def B3(var ="a", new_value=10):
return partial(A, **{var:new_value})
sample outputs:
(Pdb) f = B3("a",10)
(Pdb) f()
12
(Pdb) f = B3("b",10)
(Pdb) f()
11
(Pdb) f(a=10)
20
(Pdb) f(b=13)
14
(Pdb) f(a=5,b=5)
10
That is different from the 2nd half of the request however, that of having something based on B(var="a",new_value=10) as function signature.
The only thing is, it chokes happily if you don't use keyword parameters:
(Pdb) f(7,7)
*** TypeError: A() got multiple values for keyword argument 'b'

Related

Optional arguments in nested functions in Python

Is it possible for one function to take as an argument the name of one of the optional arguments of a second function then call the second function with the optional argument set to the value of some variable in the first function?
Code (that obviously doesn't work)
def foo(a=1,b=2,c=3):
d = a + b + c
return d
def bar(variable):
z = get_user_input()
e = foo(variable = z)
return e
print(bar(a))
The desired result is for bar to call foo(a=z) and print whatever z+2+3 is. Of course Python doesn't know what (a) is here. My guess is that I can somehow reference the list of arguments of foo() , but I am stumped as to how you might do that.
Maybe try the code snippet below
def foo(a=1,b=2,c=3):
d = a + b + c
return d
def bar(variable: str):
z = int(input())
e = foo(**{variable: z})
return e
# variable name should be defined as string
print(bar("a"))
The ** parses all arbitrary arguments on dict, in this case is a. Be careful tho, as if passing wrong variable name (differ from a, b or c) will result raising error.

Python - bound variable scope to closure

I have some function which uses outside variables. A (substantially) simplified example:
a = 2
b = 3
def f(x):
return x * a + b
While I need a and b in f, I don't need them anywhere else. In particular, one can write a = 5, and that will change the behavior of f. How should I make a and b invisible to the outside?
Other languages allow me to write roughly the following code:
let f =
a = 2
b = 3
lambda x: x * a + b
What I want:
f must work as intended and have the same signature
a and b must be computed only once
a and b must not exist in the scope outside of f
Assignments a = ... and b = ... don't affect f
The cleanest way to do this. E.g. the following solution formally works, but it introduces g and then deletes it, which I don't like (e.g. there is a risk of overriding an existing g and I believe that it's simply ugly):
def g():
a = 2
b = 3
return lambda x: x * a + b
f = g()
del g
One method is to simply use a class. This allows you to place a and b in the scope of the class while f can still access them.
custom class
class F:
def __init__(self):
self.a = 2
self.b = 3
def __call__(self, x):
return x * self.a + self.b
f = F()
f(1)
# returns:
5
If you don't like having to call the class constructor, you can override __new__ to essentially create a callable with internal stored variables. This is an antipattern though and not very pythonic.
custom callable
class f:
a = 2
b = 3
def __new__(cls, x):
return x * cls.a + cls.b
f(1)
# returns:
5
This approach is based on the answers provided in this thread, though scoped to the specific problem above. You can use a decorator to update the global variables available to the function while also storin a and b within a closure.
decorator with closure
from functools import wraps
def dec_ab(fn):
a = 2
b = 3
#wraps(fn)
def wrapper(*args, **kwargs):
# get global scope
global_scope = f.__globals__
# copy current values of variables
var_list = ['a', 'b']
current_vars = {}
for var in var_list:
if var in global_scope:
current_vars[var] = global_scope.get(var)
# update global scope
global_scope.update({'a': a, 'b': b})
try:
out = fn(*args, **kwargs)
finally:
# undo the changes to the global scope
for var in var_list:
global_scope.pop(var)
global_scope.update(current_vars)
return out
return wrapper
#dec_ab
def f(x):
"""hello world"""
return x * a + b
This preserves the functions signature and keeps a and b from being altered
f(1)
# returns:
5
a
# raises:
NameError: name 'a' is not defined
You can use default arguments to accomplish this. Default arguments are only computed once, when the closure is created (that is why if you have mutable objects as default arguments, the state is retained between invocations).
def f(x, a=2, b=3):
return x * a + b

Python save function with argumnets to variable without to call a function

It is easy to save function into a variable like
pr = print
pr(5) # 5
But if this possible to save function with arguments without a call, like
some_var = defer print(5) # No call!
some_var() # 5
I tried to use lambda, but it's lead to syntaxys error `l = lambda 5:
Why I need it? For example to no repeat multiple "if" branches:
example:
def foo()
l1 = lambda: 1
l2 = lambda: 2
if 1:
func = l1
elif 2:
func = l2
else:
func = some_outer_func, some_inner_func
return func # To use "func" need additional "if" branches for type and length of a returned value
functools.partial takes a function of many arguments and returns a function with fewer arguments with some of the arguments "saved"
from functools import partial
print_five = partial(print, 5)
print_five() # 5
It also works with keyword arguments
def foo(a, b=False):
print(a, b)
bar = partial(foo, b=True)
foo(1) # 1 False
bar(1) # 1 True
The way with lambda:
pr = lambda: print(5)
pr()

making the Python function default value be evaluated each time the function is called

How can I make a Python function default value be evaluated each time the function is called?
Take this dummy code:
b=0
def a():
global b
return b
def c(d=a()):
return d
What I would expect as output:
>>> c()
0
>>> b=1
>>> a()
1
>>> c()
1
What I actually get:
>>> c()
0
>>> b=1
>>> a()
1
>>> c()
0
One more solution, in closer resemblance to your original answer.
b = 0
def a():
return b
def c(d=a): # When it's a parameter, the call will be evaluated and its return
# value will be used. Instead, just use the function name, because
return d() # within the scope of the function, the call will be evaluated every time.
When a function name is paired with the parentheses and its parameters, like f(x), it is assumed your intention is to call it at that time
d=a() is evaluated at start of program when function c is defined (ie a() gets called while it returns 0 ...)
def c(d=None):
if d == None: d=a()
return d
will cause it to be evaluated at the time you want
The problem here is, as you probably already know, that the d=a() (default argument assignement) is evaluated when the function is defined.
To change that, it is pretty common to use eg. None as default argument and evaluate it in the body of the function:
b=0
def a():
global b
return b
def c(d=None):
if d is None:
d = a()
return d
I'll give a slight variation on the above:
b = 0
def a():
# unless you are writing changes to b, you do not have to make it a global
return b
def c(d=None, get_d=a):
if d is None:
d = get_d()
return d

Pythonic solution for conditional arguments passing

I have a function with two optional parameters:
def func(a=0, b=10):
return a+b
Somewhere else in my code I am doing some conditional argument passing like:
if a and b:
return func(a, b)
elif a:
return func(a)
elif b:
return func(b=b)
else:
return func()
Is there anyway to simplify code in this pattern?
EDIT:
Let's assume that I'm not allowed to implement default parameter logic inside func.
I may have several functions like func: func1, func2 and func3 would all contain the
a = a or 0
b = b or 10
statements.
But I'm invoking these series of functions to eliminate duplication. (using a decorator)
If you don't want to change anything in func then the sensible option would be passing a dict of arguments to the function:
>>> def func(a=0,b=10):
... return a+b
...
>>> args = {'a':15,'b':15}
>>> func(**args)
30
>>> args={'a':15}
>>> func(**args)
25
>>> args={'b':6}
>>> func(**args)
6
>>> args = {}
>>> func(**args)
10
or just:
>>>func(**{'a':7})
17
You can add a decorator that would eliminate None arguments:
def skip_nones(fun):
def _(*args, **kwargs):
for a, v in zip(fun.__code__.co_varnames, args):
if v is not None:
kwargs[a] = v
return fun(**kwargs)
return _
#skip_nones
def func(a=10, b=20):
print a, b
func(None, None) # 10 20
func(11, None) # 11 20
func(None, 33) # 10 33
Going by the now-deleted comments to the question that the check is meant to be for the variables being None rather than being falsey, change func so that it handles the arguments being None:
def func(a=None, b=None):
if a is None:
a = 0
if b is None:
b = 10
And then just call func(a, b) every time.
to solve your specific question I would do:
args = {'a' : a, 'b' : b}
for varName, varVal in args.items():
if not varVal:
del args[varName]
f(**args)
But the most pythonic way would be to use None as the default value in your function:
f(a=None, b=None):
a = 10 if a is None else a
...
and just call f(a, b)
By default, all methods in Python take variable arguments.
When you define an argument in the signature of the method, you explicity make it required. In your snippet, what you are doing is giving it a default value - not making them optional.
Consider the following:
>>> def func(a,b):
... a = a if a else 0
... b = b if b else 10
... return a+b
...
>>> a = b = None
>>> func(a,b)
10
>>> a = 5
>>> b = 2
>>> func(a,b)
7
>>> func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 2 arguments (0 given)
In this snippet, both a and b are required, since I didn't define any default values. They are not optional.
However in your snippet, because you have given them defaults in the method signature they look like they are optional, but in fact they just have defaults assigned to them.
>>> def func(a=0,b=10):
... return a+b
...
>>> func()
10
You can confirm this by checking the argument list inside the body of the method:
>>> def func(a=b,b=10):
... print locals().keys()
...
>>> func()
['a', 'b']
One way to have your function accept any number of arguments (in other words, making them all optional):
>>> def func(*args,**kwargs):
... print len(args),args
... print len(kwargs),kwargs
...
>>> func("hello","world",a=5,b=10)
2 ('hello', 'world')
2 {'a': 5, 'b': 10}
>>> func()
0 ()
0 {}
>>> func(1,2)
2 (1, 2)
0 {}
>>> func(a)
1 (5,)
0 {}
>>> func(foo="hello")
0 ()
1 {'foo': 'hello'}
Why not pass that logic to the function?
def func(a, b):
a = a or 0
b = b or 10
return a + b
To answer your literal question:
func(a or 0, b or 10)
Edit:
See comment why this doesn't answer the question.
You can use the ternary if-then operator to pass conditional arguements into functions
https://www.geeksforgeeks.org/ternary-operator-in-python/
For example, if you want to check if a key actually exists before passing it you can do something like:
def func(arg):
print(arg)
kwargs = {'name':'Adam')
func( kwargs['name'] if 'name' in kwargs.keys() else '' ) #Expected Output: 'Adam'
func( kwargs['notakey'] if 'notakey' in kwargs.keys() else '' ) #Expected Output: ''
This might work:
def f(**kwargs):
a = get(kwargs, 0)
b = get(kwargs, 10)
return a + b

Categories