-2

I am writing code to randomize exams, and I need to be able to randomize one variable so that it is chosen from a list but not equal to another variable.

I'm using eval() to evaluate a string and exec() to define the variables in python so that one value can be create from another. The issue I'm having is that in certain situations the variables aren't being recognized.

In situ, variable names and randomization parameters are pulled as text strings from a separate file. A simplified and extracted block of code that captures my problem is:

import random

def question_randomizer():

    randomdef={'w':'random.choice([1,2,3])', 'x':'w+1', 'y':'random.choice([i for i in [w,x] if i not in [2]])', 'z':'random.choice([i for i in [1,2,3] if i not in [w]])'}

    for var in randomdef:
        variablevalue=eval(randomdef[var])

        exec("%s = %s" % (var,variablevalue))

        print(var, ' = ', variablevalue)

    return

question_randomizer()

The exec call should define the variables within the code, and it works correctly in creating w, x, and y;

w  =  2
x  =  3
y  =  3

but z always returns an error

name 'w' is not defined

Does anyone know how to fix this, or why this error is occurring when w has been used before successfully? My only additional caveat is that I am trying to avoid global variables.

Thanks.

Ex pat
  • 1
  • 1
  • 5
    Use a dict instead of dynamically generating variable names. `foo[variablename] = variablevalue` – chepner Jul 09 '19 at 21:01
  • 2
    And when the variables are named with sequential numbers, it should just be a list, with the numbers turning into indexes. – Barmar Jul 09 '19 at 21:02
  • 1
    Dynamic generation creates a number of issues and is very hard to maintain. I would use a dictionary and iterate. Additionally with your print statement the better way to write that is with f-strings (https://stackoverflow.com/questions/43123408/f-strings-in-python-3-6) – Duncan McFarlane Jul 09 '19 at 21:07
  • See [How do I create a variable number of variables?](https://stackoverflow.com/questions/1373164/how-do-i-create-a-variable-number-of-variables) – martineau Jul 09 '19 at 21:41

1 Answers1

1

First off, exec() and eval() are kind of dangerous and hard to use properly. If you must approach the problem in this way, use the locals() function instead, and manually add to the list of local variables:

locals()[variablename] = variablevalue

But perhaps a better way of addressing this problem would be with the random.sample() function, which allows you to select any number of unique values from a sample:

x1, x2, x4 = random.sample(range(1, 6), 3)  # 3 unique values between 1 and 5 inclusive

The random.choice(vals) function is roughly equivalent to random.sample(vals, 1).

If you want to avoid duplicating values, you can use set subtraction to pare down the range of acceptable random samples:

x3 = random.choice(set(range(1, 6)) - {x1, x2, x4, 2})  # a value between 1 and 5 that is not equal to x1, x2, or x4, or the integer 2
Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53
  • 1
    Um, no, you should *definitely not use `locals()`*, indeed, that is the underlying problem with `exec` here. It will only work when `locals() is globals()` butn in general it won't work and is explicitly warned against in the docs – juanpa.arrivillaga Jul 09 '19 at 22:02