dulrich dulrich - 3 months ago 8
Python Question

Why doesn't this method, redefined at runtime, retain its value?

I'm stuck on something I suspect is quite simple, but I just can't wrap my head around it. I'm trying to create a class with a method that can be redefined on the fly. I want to be able to do this for an arbitrary number of instances, but I'm only showing two here in order to keep things simple.

Here is a MWE of my code:

class Foo():
def __init__(self, z):
self.z = z

def f(self, t):
return 0

def f(obj1, obj2, t):
return (obj1.z - obj2.z) * t

a, b = Foo(3), Foo(5)
print(a.f(1), b.f(1)) # --> 0, 0

x, y = a, b
x.f = lambda t: f(x, y, t)
print(a.f(1), b.f(1)) # --> -2, 0

x, y = b, a
x.f = lambda t: f(x, y, t)
print(a.f(1), b.f(1)) # --> 2, 2


Why does the value of
a.f(1)
change?

Answer

It changes because you are modifying the x and y global variables, which are referred to by the f function you defined:

In [2]: a, b = Foo(3), Foo(5)

In [3]: print(a.f(1), b.f(1))
0 0

In [4]: x, y = a, b
   ...: x.f = lambda t: f(x, y, t)
   ...: print(a.f(1), b.f(1))
   ...: 
-2 0

In [5]: x, y = b, a 

In [6]: print(a.f(1), b.f(1))  # you changed x and y
2 0

After you swap x and y you have that a.f is the function lambda t: f(x, y, t) which means it calls f(b, a, t) and since b = Foo(5) and a = Foo(3) you have 5-3 == 2 instead of -2.

If you want to fix the value passed by f and "unlink" it from the global variables you could use default arguments:

In [2]: x, y = a, b
   ...: x.f = lambda t, x=x, y=y: f(x, y, t)
   ...: 

In [3]: print(a.f(1), b.f(1))
-2 0

In [4]: x, y = b, a

In [5]: print(a.f(1), b.f(1))
-2 0

Since default values are evaluated at definition time, by using lambda t, x=x, y=y you end up fixing the values of x and y as they were when the function was defined, so that when you subsequently swap them this doesn't affect that function.

Comments