Brideau
Brideau

Reputation: 4771

ValueError: list.remove(x): x not in list (But it is!)

I'm working through the O'Reilly book "Exploring Everyday Things in R and Ruby" and am trying to rewrite all of the Ruby code in Python. The first example is a model for figuring out how many bathrooms a building needs. The code that I am working with is below.

When I run the example34.py file, however, I get the following error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "example34.py", line 39, in <module>
    new_restroom.enter(queue.pop(0))
  File "restroom.py", line 21, in enter
    unoccupied_facilities[0].occupy(person)
  File "restroom.py", line 46, in occupy
    Person.population.remove(person)
ValueError: list.remove(x): x not in list

I am new to Python, so I suspect this is an issue related to variable scope or some unknown (to me) property of Python. The strange thing is that after it breaks, running person in Person.population returns True, and the command Person.population.remove(person) succeeds in removing that person instance, so the person clearly is in the list.

Any ideas?

example34.py:

from restroom import *  # imports the model

# Simulation script 1

frequency = 3  # how many times a person goes to the restroom within the period
facilities_per_restroom = 3
use_duration = 1  # measured in ticks
population_range = range(100, 110, 10)  # Creates an array from 10 to 600 by 10s

# Stores data for printing later
data = {}

# Loops for each population size in the above range
for population_size in population_range:
    # Starts each loop fresh
    Person.population = []
    # Creates the population of people
    Person.population = [
        Person(frequency, use_duration) for
        each in range(population_size)]
    # Creates the key for this population size
    data[population_size] = []
    #Create the restroom
    new_restroom = Restroom(facilities_per_restroom)
    # Iterate over the period
    for each in range(duration):
        # Records the queue size at this tick
        data[population_size].append(len(new_restroom.queue))
        # Create a temporary queue so that we can sort people between the
        # facilities and the restroom queue for this "tick"
        queue = list(new_restroom.queue)
        # Clear the queue to prepare for sorting
        new_restroom.queue = []
        # Take each person from the temporary queue and try adding
        # them a facility
        while bool(queue):
            # De-queue the person at the front of the line, place in an
            # unoccupied facility or, if none, back to the restroom queue
            new_restroom.enter(queue.pop(0))

        # for each person in the population, check if they need to go
        for person in Person.population:
            if person.need_to_go():
                new_restroom.enter(person)
        new_restroom.tick()

print(data)

restroom.py:

from random import randint
duration = 9 * 60  # minutes


class Restroom(object):

    def __init__(self, facilities_per_restroom=3):
        # Start with an empty queue
        self.queue = []
        # The facilities in this restroom
        self.facilities = []
        # Creates the facilities
        self.facilities = ([Facility() for each in
                            range(facilities_per_restroom)])

    def enter(self, person):
        unoccupied_facilities = [
            facility for facility in self.facilities
            if facility.occupied() == 0]
        if unoccupied_facilities:
            unoccupied_facilities[0].occupy(person)
        else:
            self.queue.append(person)
            Person.population.remove(person)

    def tick(self):
        [each.tick() for each in self.facilities]
        [f.tick for f in self.facilities]


class Facility(object):
    def __init__(self):
        self.occupier = None  # no one is occupying this facility at the start
        self.duration = 0  # how long the facility has been occupied

    def occupied(self):
        return 1 if (self.occupier is not None) else 0

    def occupy(self, person):
        # if the facility is unoccupied, add the person. Else, return false.
        if not self.occupied():
            self.occupier = person
            self.duration = 1
            #remove the person from the population since
            # they're in a facility now
            Person.population.remove(person)
            return 1  # return true
        else:
            return 0  # Return false

    def vacate(self):
        Person.population.append(self.occupier)
        self.occupier = None

    def tick(self):
        # if the facility is occupied and they've been
        # there longer than the use duration, have them leave
        if self.occupied() and (self.duration > self.occupier.use_duration):
            self.vacate()
            self.duration = 0
        elif self.occupied():
            # If occupied, increment the time
            self.duration += 1


class Person(object):

    #Class variable for storing the entire population
    population = []

    def __init__(self, frequency=4, use_duration=1):
        # Number of times a person uses the facilities per day
        self.frequency = frequency
        # How long each person uses the facilities
        self.use_duration = use_duration

    def need_to_go(self):

        return randint(1, duration) <= self.frequency

Upvotes: 2

Views: 4059

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1123420

You've encountered a simple bug in your code.

The rest room has a queue, to which Person objects are added for which there is no stall available.

The logic goes:

def enter(self, person):
    unoccupied_facilities = [
        facility for facility in self.facilities
        if facility.occupied() == 0]
    if unoccupied_facilities:
        unoccupied_facilities[0].occupy(person)
    else:
        self.queue.append(person)
        Person.population.remove(person)

Note that either .occupy() removes a person from the Person.population list, or if there are no stalls available, then the person is added to self.queue and also removed from Person.population.

The example34 code then processes that queue:

queue = list(new_restroom.queue)
# Clear the queue to prepare for sorting
new_restroom.queue = []
# Take each person from the temporary queue and try adding
# them a facility
while bool(queue):
    # De-queue the person at the front of the line, place in an
    # unoccupied facility or, if none, back to the restroom queue
    new_restroom.enter(queue.pop(0))

So people are taken from the queue, and reentered into the restroom. If any of the stalls are empty, unoccupied_facilities[0].occupy(person) tries to remove that person from Person.population again. At this point the exception is raised, because that person is most definitely not in that list anymore.

The solution is to not remove person from the population in two different places, or to re-add the person back into the population when processing the queue.

Changing the queue processing code to:

while bool(queue):
    # De-queue the person at the front of the line, place in an
    # unoccupied facility or, if none, back to the restroom queue
    person = queue.pop(0)
    Person.population.append(person)
    new_restroom.enter(person)

fixes the issue, for example:

$ python2.7 example34.py 
{100: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0]}

Upvotes: 2

Srikar Appalaraju
Srikar Appalaraju

Reputation: 73658

But just by looking at the stacktrace it clearly states ValueError: list.remove(x): x not in list. The person you are trying to remove is not in the list. So You need to catch this exception and handle it appropriately -

#some loop, get the person to be removed
...
try:
    Person.population.remove(person)
except ValueError:
    pass #silently continue. Either that person is already removed or is not present.
...

Of course this code is not complete. It was put to merely highlight the use of try: except ValueError: usage.

Upvotes: 0

Related Questions