How to deepcopy a function object - python

I create a function which returns an value specified by an variable. Like
y = 1.
def f(x):
return y
I need this function as an function object to create another object
dist = dist.distribution(f, other_variables)
this works fine. But if i want to create several different distribution objects (with different functions f in the sense that y changes) Like
dist = dist.distribution(f, other_variables)
y = 2.
dist2 = dist.distribution(f, other_variables)
Then all distribution objects only return the last specified value y. I.e.
dist.f()(1.)
>>>> 2.
dist2.f()(1.)
>>>> 2.
Instead of the expected
dist.f()(1.)
>>>> 12.
dist2.f()(1.)
>>>> 2.
The problem clearly is, that the function f accesses the variable only when it is called and not one time initially.
Is there a way around it?
What I want at the end is:
A function with one variable only (x, although this doesnt do anything in this case, it is needed in others), which returns the value of y of the moment, when the distribution is created. So in principle I want that at the initialisation of the distribution, the given function is deepcopyed, in the sense, that it is no longer influenced by any change of variables.
Is this possible?

Don't use globals for this. There is no need to 'deepcopy' the function either; the y global is not part of the function state at all.
Use a function factory that provides a scoped value instead, or use a functools.partial() to provide a default argument to your function.
Function factory:
def produce_f(y):
def f(x):
return y
return f
dist = dist.distribution(produce_f(1.), other_variables)
Now y is a scoped value for f, produce_f() returns a new f every time it is called, and y is stored as a cell variable for f.
Demo:
>>> f1 = produce_f(12.)
>>> f2 = produce_f(42.)
>>> f1('foo')
12.0
>>> f2('foo')
42.0
Using functools.partial():
from functools import partial
def f(y, x):
return y
dist = dist.distribution(partial(f, 1.), other_variables)
Here partial(f, 1.) produces a new callable that will call f(1., ...) whenever called, appending any extra arguments passed in.
Demo:
>>> f1 = partial(f, 12.)
>>> f2 = partial(f, 42.)
>>> f1('foo')
12.0
>>> f2('foo')
42.0

Related

Can a function using a global variable use the variable's value from when the function was declared rather than when it was called?

Here is a very simplified example of what I am trying to do:
x = 3
def f():
print(x)
x = 5
f() #f prints 5 but I want it to print 3.
Is there a way, when declaring the function, to turn x into a constant that points somewhere other than the global variable x? I can't provide arguments to the function.
This is a pretty common trick (you usually see it in lambda expressions that want to bind a particular value within a loop):
x = 3
def f(x=x):
print(x)
x = 5
f() # prints 3
The trick is that default parameter values are evaluated at the time of function definition, so in the expression x=x, the x on the right hand side is evaluated (producing the value 3) and then stored as the default value of the x parameter in the function (which shadows the x in the outer scope).
You could equivalently write:
x = 3
def f(n=x):
print(n)
x = 5
f() # prints 3
which has the same result, but doesn't shadow the x variable.
From what I understand, you seem to want x to hold two values simultaneously - which is what complex data structures are for. A list would work fine, or a dict:
>>> x = [3]
>>> def f():
... print(x[0]) # always refers to first element. Functionally constant.
...
>>> x.append(5)
>>> f()
3
>>>
However, it sounds like you really have an XY problem, where you're asking about your solution instead of your actual problem. Go back to your code and check if this seems to be the case. If so, we might be able to point you towards a better way of solving your real issue.

How do I figure out what the named arguments to a function are in python

I am writing a function that is going to take, as one of it's arguments, a dictionary of functions to apply. All of these functions will have at least one argument in common, but some will have others. Without losing the flexibility of letting the user specify arbitrary functions (conditional on having their arguments in scope), how can I introspect a function to see what its arguments are?
Here's a dummy example:
def f1(x, y):
return x+y
def f2(x):
return x**2
fundict = dict(f1 = f1,
f2 = f2)
y = 3 # this will always be defined in the same scope as fundict
for i in list(fundict.keys()):
if <the args to the function have y in them>:
fundict[i](x, y)
else:
fundict[i](x)
Even better would be something that programmatically looks at the definition of the function and feeds the array of arguments to it, conditional on them being in-scope.
I'd also appreciate good general suggestions for different ways to go about solving this problem, that might not involve introspecting a function.
You can use inspect.getfullargspec
import inspect
def f1(x, y):
return x+y
def f2(x):
return x**2
fundict = dict(f1 = f1,
f2 = f2)
y = 3 # this will always be defined in the same scope as fundict
x = 1
for i in list(fundict.keys()):
if 'y' in inspect.getfullargspec(fundict[i]).args:
print(fundict[i](x, y))
else:
print(fundict[i](x))
This gives:
4
1
You can use inspect.getfullargspec.
Example:
>>> for k, fun in fundict.items():
... print(fun.__name__, inspect.getfullargspec(fun)[0])
...
f1 ['x', 'y']
f2 ['x']

lambda function of another function but force fixed argument

I just switched to Python from Matlab, and I want to use lambda function to map function f1(x,y) with multiple arguments to one argument function f2(x) for optimization.
I want that when I map the function f2(x) <- f1(x,y=y1) then y will stay constant no matter what y1 changes, in Matlab this is true by default but if I try in Python, it keeps changing as the following examples
>>> def f1(x,y):
>>> return (x+y)
>>> y1 = 2
>>> f2 = lambda x: f1(x,y1)
>>> f2(1)
3
I expect f2(1) stays 3 even if I change y1, however if I change y1, the whole f1(1) also changes as follows
>>> y1 = 5
>>> f2(1)
6
I wonder is there a way that when I declare f2 = lambda x: f1(x,y1) then f1 will take the value of y1 at that time and fix it to f2. The reason for this because I want to dynamically create different functions for different scenarios then sum them all.
I'm still new to Python, please help, much appreciate.
Try:
f2 = lambda x, y=y1: f1(x,y)
Your issue has to do with how closures work in Python
Your version of the lambda function will use the current version of y1. You need to capture the value of y1 on the line where you've defined the lambda function. To do that, you can define it as the default value of a parameter (the y=y1 part).
As already pointed out, your issue comes down to how closures work. However, you really shouldn't be using a lambda for this - lambdas are for anonymous functions. Make a higher-order function with def statements instead:
>>> def f1(x,y):
... return x + y
...
>>> def f1_factory(y):
... def f1_y(x):
... return f1(x,y)
... return f1_y
...
>>> f1_factory(6)(4)
10
>>> f1_factory(5)(4)
9
It also avoids the problem you encountered:
>>> y = 3
>>> newfunc = f1_factory(y)
>>> newfunc(1)
4
>>> y = 20
>>> newfunc(1)
4
>>>
From PEP8:
Always use a def statement instead of an assignment statement that
binds a lambda expression directly to an identifier.
Yes:
def f(x): return 2*x
No:
f = lambda x: 2*x
The first form means that the name of the resulting
function object is specifically 'f' instead of the generic <lambda>.
This is more useful for tracebacks and string representations in
general. The use of the assignment statement eliminates the sole
benefit a lambda expression can offer over an explicit def statement
(i.e. that it can be embedded inside a larger expression)

python function with arguments assigned to a variable

I have a function
def x(whatever):
....
I want to assign
y = x(whatever)
So that I can pass around y to a wrapper which then calls the method that y refers to. The problem is that each type of function x can have variable arguments. Is it possible to do this in python, assigning y = x(whatever) and then pass around y as a parameter.
I tried y = x(whatever) and passed around y to the wrapper which then did
ret = y() # dict object is not callable
Thanks!
I think you are looking for functools.partial():
from functools import partial
y = partial(x, whatever)
When y is called, x(whatever) is called.
You can achieve the same with a lambda:
y = lambda: x(whatever)
but a partial() accepts additional arguments, which will be passed on to the wrapped callable.

Assigning a function to a variable

Let's say I have a function
def x():
print(20)
Now I want to assign the function to a variable called y, so that if I use the y it calls the function x again. if i simply do the assignment y = x(), it returns None.
You simply don't call the function.
>>> def x():
>>> print(20)
>>> y = x
>>> y()
20
The brackets tell Python that you are calling the function, so when you put them there, it calls the function and assigns y the value returned by x (which in this case is None).
When you assign a function to a variable you don't use the () but simply the name of the function.
In your case given def x(): ..., and variable silly_var you would do something like this:
silly_var = x
and then you can call the function either with
x()
or
silly_var()
when you perform y=x() you are actually assigning y to the result of calling the function object x and the function has a return value of None. Function calls in python are performed using (). To assign x to y so you can call y just like you would x you assign the function object x to y like y=x and call the function using y()
The syntax
def x():
print(20)
is basically the same as x = lambda: print(20) (there are some differences under the hood, but for most pratical purposes, the results the same).
The syntax
def y(t):
return t**2
is basically the same as y= lambda t: t**2. When you define a function, you're creating a variable that has the function as its value. In the first example, you're setting x to be the function lambda: print(20). So x now refers to that function. x() is not the function, it's the call of the function. In python, functions are simply a type of variable, and can generally be used like any other variable. For example:
def power_function(power):
return lambda x : x**power
power_function(3)(2)
This returns 8. power_function is a function that returns a function as output. When it's called on 3, it returns a function that cubes the input, so when that function is called on the input 2, it returns 8. You could do cube = power_function(3), and now cube(2) would return 8.
lambda should be useful for this case.
For example,
create function y=x+1
y=lambda x:x+1
call the function
y(1)
then return 2.
I don't know what is the value/usefulness of renaming a function and call it with the new name. But using a string as function name, e.g. obtained from the command line, has some value/usefulness:
import sys
fun = eval(sys.argv[1])
fun()
In the present case, fun = x.
def x():
print(20)
return 10
y = x
y()
print(y)
gives the output
20
<function x at 0x7fc548cd3040>
so it does not actually assign the value returned by x() to the variable y.

Categories