3

I know that this error message has been discussed already a lot but I haven't found an explanation of the following:

def random2(seed):
    x = seed
    def update():
        x = ( x * 16807 ) % 2147483647
        return x
    return update

r = random2(17283945)
print(r())

This doesn't work because the scope of the variable x seems to get lost by returning the function [UnboundLocalError]. Ok. But now I've found out that there is absolutely no problem with

def random(seed):
    x = [seed]
    def update():
        x.append(( x.pop() * 16807 ) % 2147483647 )
        return x[0]
    return update

r = random(17283945)
print(r()) #580971270
print(r()) #1923475628
print(r()) #1783541505

I'm a little confused why, in this case, the scope of x remains valid. It seems that is has something to do with (im)mutability, but this still doesn't make a lot of sense to me.

Thanks a lot.

ainu
  • 33
  • 2

3 Answers3

3

When you put an assignment operator (=) after a variable name, that variable is automatically assumed to be local. Because you are trying to seemingly reference it before it's assignment, you get the error.

In the second example, you never assigned a variable x to anything, you merely mutated it in-place.

Volatility
  • 31,232
  • 10
  • 80
  • 89
2

If you are using Python 3 you can use nonlocal x

def random2(seed):
    x = seed
    def update():
        nonlocal x
        x = ( x * 16807 ) % 2147483647
        return x
    return update

r = random2(17283945)
print(r())

In python 2 I always do what you did with the list.

User
  • 14,131
  • 2
  • 40
  • 59
  • 1
    Thank you very much! I'm new to Python and wasn't aware that there is the "nonlocal" keyword... – ainu Mar 10 '13 at 10:59
0

@volatility has explained why it happens, but for reference here's how you might write a similar function that avoids nested scopes using generators.

def random(seed):
    x = seed
    while True:
        x = ( x * 16807 ) % 2147483647
        yield x

This will give you a sequence of pseudorandom numbers when iterated over like for rand_num in random(100). You can also get new random numbers on demand without using a for loop: rand_gen = random(100); rand_num = next(rand_gen).

You can see that random number generators are a natural use for generators: the function is shorter, clearer, can be used in a natural way (for...in and next) and less prone to the errors that can arise due to nonlocal. See this question for a better explanation of generators and yield.

Community
  • 1
  • 1
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • This doesn't change the `x` in the outer function though - meaning it'll produce the same number every time. Pseudorandom number generators give you different numbers each time, not the same one over and over. – Volatility Mar 10 '13 at 11:24
  • Ah, good point, I wasn't thinking. I suppose the best way to go about this would be to use a generator - I'll change my answer. – Benjamin Hodgson Mar 10 '13 at 11:27