1

I'm implementing a loop that should continue until a user presses the Return key following the instructions on this page i got this to work:

def _input_thread_2( L ):
    raw_input()
    L.append( None )

#### test _input_thread()
L = []
thread.start_new_thread(_input_thread_2, (L, ))
while True: 
    time.sleep(1)
    print "\nstill going..."
    if L: 
        break

My question is why the following seemingly simple adaptation doesn't work? Instead of exiting the loop upon pressing a key, it just keeps looping:

def _input_thread_3( keep_going ):
    """ 
    Input: doesn't matter
    Description - When user clicks the return key, this changes the input to the False bool. 
    """
    raw_input()
    keep_going = False

#### test _input_thread()
keep_going = True
thread.start_new_thread(_input_thread_3, (keep_going, ) )
while True: 
    time.sleep(1)
    print "\nstill going..."
    if not keep_going: 
        break

Can you help me understand the difference between these?

travelingbones
  • 7,919
  • 6
  • 36
  • 43

2 Answers2

3

In Python, why can a function modify some arguments as perceived by the caller, but not others?

This is the reason. Your keep_alive is immutable, while a list is mutable.

Hannu
  • 11,685
  • 4
  • 35
  • 51
  • 1. "if you have more than one thread..." are you saying that if we used mulitprocessing, the global variables are not global across processes? 2. In my example that doesn't work, python is keeping two variables both named "keep_going", so one is modified when the user presses return. That is counter intuitive since I passed it that variable. So it begs the question, in the first example that i posted why doesn't the variable L (to which we append "None") have two copies-- one that remains empty and one that gains length? 3. Finally, is the 1st example the works "bad programming", why? – travelingbones Sep 20 '17 at 15:20
  • 1
    If you use multiprocessing, global variables are not global to all of them. 1) Your subprocesses of course inherit whatever is there when they are launched, but subprocesses then modify their copies and main program modifies theirs. You can share variables using `Monitor` though. In your case you use threads, not multiprocessing, and threads do share global variables. – Hannu Sep 20 '17 at 15:24
  • 1
    But this is not very good programming. If you have several threads that modify L, eventually there will be a conflict and the result of L is not what you expect it to be. You can avoid this by using locks, or modifying your code so that worker threads return their results for example in a queue and main program appends to L whatever it reads from that queue. There are other ways, too. – Hannu Sep 20 '17 at 15:32
  • 1
    If you only have one thread that modifies L, then you will be safe and a global variable might just be the simplest way of doing this. Some programmers do not like using global variables this way, but that is more matter of programming style. If you are happy with it, that is the only thing that matters. – Hannu Sep 20 '17 at 15:36
  • Thanks for teaching me, this is very helpful. I'm still confused on 2. L is instantiated to an empty list. then we start a new thread. L is passed to _input_thread() function. According to what you said, the function makes a copy of L. Then upon raw_input() executing, the copy of L is appended to. So in the while loop, it should see the original L, which never got appended to-- right? Evidently not, but from what you said that's what I think should happen. – travelingbones Sep 20 '17 at 16:09
  • This is exactly what happens. You append in your thread to a copy of empty L, then discard the copy (the thread exits). Your main program sees the empty L from global scope all the time. And because it is an empty list `L = []` and always remains such in the global scope, your `if L` is always false and you never call `break`. Does this make sense? – Hannu Sep 20 '17 at 16:15
  • I'm sorry, it does not make sense because in the first example the loop ends! – travelingbones Sep 20 '17 at 16:22
  • 1
    A-ha, I was careless when reading your question. Sorry about that. This is what this is all about: https://stackoverflow.com/questions/575196/in-python-why-can-a-function-modify-some-arguments-as-perceived-by-the-caller When you pass a boolean or an integer, it will be immutable (keep_alive) but if you pass a list, it will be mutable. So actually my answer is wrong. I will modify it. – Hannu Sep 20 '17 at 16:30
-1

This is a more verbose version of @Hannu's answer.

In both _input_thread() functions above, a "copy" of the input is created. In the first, since L is a list (mutable), the copy is really a second name L that points to the list. Both L's are different list references, but have the same contents. The .append() method changes the contents of the list. Both references L see it.

In the second version a new version of name keep_going is made that points to (a new) bool (immutable). So only the second version is changed and the while loop never sees it.

travelingbones
  • 7,919
  • 6
  • 36
  • 43
  • It's simply wrong. There is no copy of the input created in either case. Python never implicitly copies values. What happens behind the scenes is exactly the same for a list and a boolean value. In both cases you have a local name that refers to the very same object given as argument. What's different is what is done with the local names and values. In the list case the list object is mutated. In the boolean case the local name is bound to another value. Binding a name to a value doesn't have any effect on the value previously bound to it. Mutating a value obviously has an effect on it. – BlackJack Sep 26 '17 at 17:09