You can do it by keeping track of what variables have already been chosen. If if the values are unique and hashable, a very efficient way to check for membership is to use a set
as shown below. If the values aren't unique or hashable you can use a list
instead, but that could potentially makes things a lot slower depending on many variables there are.
The code below makes use of an assignment expression (aka as "the walrus operator"), which was added in Python 3.8, to simplify the code slightly.
import random
a = 0
b = 1
c = 2
elements = [a, b, c]
chosen = set()
while (x := random.sample(elements, 1)[0]) in chosen:
pass
chosen.add(x)
while (y := random.sample(elements, 1)[0]) in chosen:
pass
chosen.add(y)
print(x)
print(y)
However the better way, if you know the number of samples needed in advance, is to just get them all at once and allow random.sample()
handle things because it always returns a list of unique elements when you ask for more than one (and doesn't care whether the values are unique or hashable):
x, y = random.sample(elements, 2)
print(x)
print(y)
Update
If you do this a lot, it might be worth creating a class that encapsulates the bookkeeping need to simplify usage. Whenever one or more samples are needed, you can merely call the instance and it'll keep track of everything. Here's what I mean:
from collections.abc import Set, Sequence
import random
class RandSampler:
"""Return a single element or variable length list of unique elements
randomly chosen from the population sequence or set. Used for random
sampling without replacement.
"""
def __init__(self, population):
if isinstance(population, Set):
population = tuple(population)
if not isinstance(population, Sequence):
raise TypeError('Population must be a sequence or set.')
self._elements = list(population)
self._chosen = set() # For tracking selections.
def __call__(self, k=1):
if k == 1:
elements, chosen = self._elements, self._chosen # Optimize access.
while (sample := random.sample(elements, 1)[0]) in chosen:
pass
self._chosen.add(sample)
return sample
else:
samples = random.sample(self._elements, k)
self._chosen.update(samples)
return samples
def reset(self):
"""Clear internal set of elements that have already beem chosen."""
self._chosen.clear()
if __name__ == '__main__':
a = 0
b = 1
c = 2
elements = [a, b, c]
sampler = RandSampler(elements)
x = sampler()
y = sampler()
print(x)
print(y)
sampler.reset()
x, y = sampler(2)
print()
print(x)
print(y)