0

I had this Python 2.7 code which queries the movement limits from a device:

pan_low_limit = int(self.send_query("PN", 2))
pan_high_limit = int(self.send_query("PX", 2))
tilt_low_limit = int(self.send_query("TN", 2))
tilt_high_limit = int(self.send_query("TX", 2))

I thought I could write this nicer as:

limits = [(pan_low_limit, "PN"), (pan_high_limit, "PX"), (tilt_low_limit, "TN"), (tilt_high_limit, "TX")]
for v, c in limits:
    v = int(self.send_query(c, 2))
    print("Result from {}: {}".format(c, v))
print(limits)
print("tilt_low_limit: {}".format(tilt_low_limit))

Print statements are to find what's going on. When I run this, I get the following output (these four variables had been initialized with value 1 before this piece of code):

Result from PN: -27067
Result from PX: 27067
Result from TN: -27999
Result from TX: 9333
[(1, 'PN'), (1, 'PX'), (1, 'TN'), (1,'TX')]
tilt_low_limit: 1
PN: 1

I don't really get what's going on. It seems like the value of v in "v = int(self.send_query(c, 2))" is what I'd expect, but at the next line, these variables have the old values again?

Amedeo
  • 847
  • 7
  • 17
  • 3
    Python's variables are not symbolic names for memory addresses (like in C or Pascal) but just names bound to an object. Rebinding a name only make this name pointing to another object, it doesn't affect the object that was previously bound to this name. You want to read this for more in-depth explanations: https://nedbatchelder.com/text/names.html – bruno desthuilliers Sep 28 '18 at 14:48
  • I don't understand exactly what you mean, so I'll read up on the link you've provided. – Dieter Vansteenwegen ON4DD Sep 28 '18 at 15:07
  • @brunodesthuilliers that link was very helpful and clears up a lot. Recommended reading. I also found that pythontutor.com is helpful to visualize what variables/objects are doing in small snippets of code. – Dieter Vansteenwegen ON4DD Oct 02 '18 at 07:46

2 Answers2

1

This is what you want:

for i, (v, c) in enumerate(limits):
    limits[i] = int(self.send_query(c, 2))

v = does not change the value in limits, it merely changes the value of the reference/variable v.

Quelklef
  • 1,999
  • 2
  • 22
  • 37
  • That doesn't seem to work. After the 'for' loop, limits = [-27067, 27067, -27999, 9333]. However the values of the variables pan_low_limit/... are all still 1. – Dieter Vansteenwegen ON4DD Sep 28 '18 at 15:07
  • @DieterVansteenwegenON4DD Yes, there is no way to modify the variables in the way you'd like (unless they're global, in which case you can modify the `globals()` dict, but don't do that). So I'm suggesting a reasonable alternative. – Quelklef Sep 28 '18 at 15:31
0

In Python a symbolic name (like pan_low_limit in your code) is a reference to a value (like "-27067").

When one name is assigned to the other (as happens for limits and v in your code), the new name becomes an additional reference to that value. In your case, in the first iteration right before the assignment, the names: pan_low_limit, limits[0][0] and v all refer to the same value:

before

However, when you assign (or bind) a name to a new value, only that name will refer to the new value. In your case, after the assignment, you will have:

after

To obtain the result that you want, you should change the variable, instead of reassigning it, so that all the references will refer to the same new value. However, integers are immutable in Python, so you cannot change it. The solution is to use something that is mutable, a list for example.

If you had a mutable variable, the original value could be changed to have the result that you are expecting. You can see this difference in the following two snippets:

Using integers:

pan_low_limit = 1
pan_high_limit = 1
tilt_low_limit = 1
tilt_high_limit = 1

limits = [(pan_low_limit, "PN"), (pan_high_limit, "PX"), (tilt_low_limit, "TN"), (tilt_high_limit, "TX")]
for v, c in limits:
    v = 2
    print("Result from {}: {}".format(c, v))
print(limits)
print("tilt_low_limit: {}".format(tilt_low_limit))

Result:

Result from PN: 2
Result from PX: 2
Result from TN: 2
Result from TX: 2
[(1, 'PN'), (1, 'PX'), (1, 'TN'), (1, 'TX')]
tilt_low_limit: 1

Using lists, we can modify the value, instead of reassigning it:

pan_low_limit = [1]
pan_high_limit = [1]
tilt_low_limit = [1]
tilt_high_limit = [1]

limits = [(pan_low_limit, "PN"), (pan_high_limit, "PX"), (tilt_low_limit, "TN"), (tilt_high_limit, "TX")]
for v, c in limits:
    v[0] = 2
    print("Result from {}: {}".format(c, v))
print(limits)
print("tilt_low_limit: {}".format(tilt_low_limit))

Result:

Result from PN: [2]
Result from PX: [2]
Result from TN: [2]
Result from TX: [2]
[([2], 'PN'), ([2], 'PX'), ([2], 'TN'), ([2], 'TX')]
tilt_low_limit: [2]

Reference: link

Thanks @bruno desthuilliers for pointing out flaws in a previous answer.

Amedeo
  • 847
  • 7
  • 17
  • Sorry but stating that it's "similar to an assignment in C" is just plain wrong. If you were to rewrite the C example with a struct instead of an int, you'd find out that changing one of the field of `a` would not impact `b[0] ` - since it would indeed _copy_ the "value" - while in Python (using any mutable object instead), mutating the object via the iteration variable (or any other name pointing to this object) DOES change the one stored in the list. To make a long story short: Python's "variables" have nothing in common with C variables and you cannot explain one it terms of the other. – bruno desthuilliers Oct 02 '18 at 08:24
  • Thanks for pointing this out. I was indeed over-simplifying. I have now updated the answer. – Amedeo Oct 02 '18 at 12:11
  • that's better but still wrong ;-) The fact that ints are immuable is actually totally irrelevant, the problem is that rebinding a name only makes the name refer to another object. The behavior would be the same with mutable objects. The difference is not between mutable and immutable objects but between mutating and rebinding. – bruno desthuilliers Oct 02 '18 at 22:36
  • I don't fully understand what you mean. Probably a more detailed explanation is needed. It does make a difference if the variable is mutable or immutable, as you can see in my example. Look also at this answer for reference https://stackoverflow.com/questions/28228461/understanding-pythons-name-binding I think you should provide a new answer with details on your theory, references and some code that proves it. I will be happy to upvote it :) – Amedeo Oct 03 '18 at 08:05
  • Yes, I agree that in one case I mutate it and in the other case I do not, because it is *immutable* :). I also agree that, if your answer is "that's how python works, period.", it is better if you do not provide a new answer. Your own reference clearly say why mutable and immutable are relevant in this scenario. The SO question that I linked explains this also. – Amedeo Oct 03 '18 at 10:09
  • I think you don't understand what I mean. Can you please run this code https://pastebin.com/KPkzER0D and report what you observe ? Yes, the result is just the same as with ints, despite lists being mutable. That's what I mean when I say that mutability is irrelevant wrt/ how binding work. Of course your solution relies on mutating the list instead of rebinding it, which is a totally different operation (nb : `somelist[index] = value` is only syntactic sugar for `somelist.__setitem__(index, value)`, so it's a mutation, not a binding). – bruno desthuilliers Oct 03 '18 at 10:21
  • I see now what you mean. The mutability is important if you consider the question and the result that he wants to achieve (that cannot be done with an immutable variable). I updated the answer, making this distinction more clear. – Amedeo Oct 03 '18 at 12:22