Here is your problem:
if guess == 'rock' or 'paper' or 'scissors':
This line in is_valid_guess does not do what you think. Instead, it always returns True . What you are looking for looks something like this:
if guess == 'rock' or guess == 'paper' or guess == 'scissors':
or more briefly:
if guess in ('rock', 'paper', 'scissors'):
The problem is that you always return True because of how Python evaluates strings in a boolean context. The string if guess == 'rock' or 'paper' or 'scissors': evaluates to:
if (guess == 'rock') or ('paper') or ('scissors'):
This means that Python checks to see if guess == 'rock' . If this is true, the conditional value evaluates to True . If it is erroneous, the interpreter tries to evaluate bool('paper') . This is always evaluated as True , because all non-empty lines are "true" . Therefore, all of your conditional conditions are always True , and each line is "valid."
As a result, your code considers all lines to be "valid," and then explodes when it fails to assign a number to an assumption that is not actually supported.
As a final note, your is_valid_guess method can be trimmed a bit, as you simply return the result of your boolean expression. Instead of using the status variable as an intermediate element, you can simply evaluate the expression and return it immediately. I also use the lower() method for string objects to allow case insensitivity in case you want to allow.
def is_valid_guess(guess): return guess.lower() in ('rock', 'paper', 'scissors')
You have one more problem that you mentioned in the comments: you user_guess recursive way so that it user_guess itself if the user enters an invalid guess. However, in this case, it does not return the result of the recursive call. You need to either return a recursive result by changing the last line of user_guess to:
return user_guess()
Or else you must force this function to use a loop instead of recursion, which I would do, because the function is not essentially recursive. You can do something like this:
def user_guess():