How or when is a variable updated?

Question:

I wrote this code (spoilers for LeetCode problem 13):

roman_numbers = [('I', 1), ('V', 5), ('X', 10), ('L', 50), ('C', 100), ('D', 500), ('M', 1000)]


class Solution:
    def get_value(self, number):
        return dict(roman_numbers)[str(number)]

    def romanToInt(self, s: str) -> int:
        result = 0
        letter_list = list(letter for letter in s)
        for letter in s:
            try:
                if self.get_value(letter_list[0]) >= self.get_value(letter_list[1]):
                    result += self.get_value(letter_list[0])
                    letter_list.remove(letter_list[0])
                else:
                    result -= self.get_value(letter_list[0])
                    letter_list.remove(letter_list[0])
            except IndexError:
                result += self.get_value(letter_list[0])
        return result

The code works, but I wanted to refactor it to make it less repetitive. The pattern self.get_value(letter_list[x]) appears many times, so I’d like to make a variable that stores a result like letter_list.remove(letter_list[0]), so I can write code like

if letter0 >= letter1:
    result += letter0

But since the letter_list will change, I need to make sure that the variables are updated when necessary.

I tried creating the variable inside the for loop, so that it updates every time through the loop:

for letter in s:
    letter0 = self.get_value(letter_list[0])
    letter1 = self.get_value(letter_list[1])
    ...

However, I’m not sure that I’ve properly understood what’s going on.

What exactly causes the variables to update? Is it because the code is inside a function? Is the variable getting re-created each time through the loop?

And is this correct logic – will the variables be up-to-date when they are used? In the past I’ve had many problems with variable values getting out of sync.

Asked By: MIKIBURGOS

||

Answers:

What makes the variable change is the assignment operation:

letter0 = ...

Whatever is to the right of the assignment (=, += etc) is evaluated/executed first, then the result is stuffed in the variable. But no, in Python variables do not evaporate when you reach the bottom of the loop (scope) they are defined in. The next assignment simply replaces what the variable referred to before.

Answered By: alexis

So, variables are just names that refer to objects. You seem to expect a variable to hold an expression that will be dynamically executed. That’s not how that works. Variables hold the result of expressions. If you use the following assignment statement:

letter0 = self.get_value(letter_list[0])

Then letter0 is now referring to the object that results from evaluating the expression self.get_value(letter_list[0]). In this case, it is some str object. When you use the variable in code after that, it simply evaluates to that string object. It doesn’t re-evaluate the expression that was used in the assignment statement: self.get_value(letter_list[0]).

There are two ways you might "update" a variable.

  1. assign or re-assign to it.
  2. you mutate the object that the variable is referring to.

Those are two different things though. Really, only (1) is updating the variable. The other is mutating some object.

Now, you should really read through the whole of Ned Batchelder’s excellent Facts and Myths about Python Names and Values (he’s a StackOverflow legend). But I want to highlight one point made there: lot’s of different things are assignments, not just the simple assignment

<some-var> = <some-expression>

e.g.

result = some_function()

Here are the various ways you can assign to the name X:

X = ...
for X in ...
[... for X in ...]
(... for X in ...)
{... for X in ...}
class X(...):
def X(...):
def fn(X): ... ; fn(12)
with ... as X:
except ... as X:
import X
from ... import X
import ... as X
from ... import ... as X

As for mutating an object, that is generally done through some mutator method, e.g.:

my_object.mutator_method(some_arg)

For example, here are two different mutator methods being called on the same object through the variable mylist:

mylist.append(42)
mylist.pop()

Note, operators and various other language constructs can also mutate an object because underneath the hood, the end up calling a mutator method!

mylist += [42]  # This one actually is an assignment too!
mylist[i] = 42  # This one is *not an assignment*!

Watch out with that last one! It’s actually not an assignment!

Answered By: juanpa.arrivillaga