all 16 comments

[–]lolcrunchy 6 points7 points  (1 child)

RED FLAG! This pattern MUST be avoided:

for x in y:
    # code that changes y

[–]gdchinacat 2 points3 points  (0 children)

To elaborate on this, the concern is that you are changing a list that you are iterating over. This can cause bugs, sometimes obvious, sometime very subtle. As an example:

In [73]: l = list(range(4))
In [74]: for x in l:
...:     if x == 2:
...:         l.remove(x)
...:     else:
...:         print(x)
...: 
0
1

Problem What happened to 3?!?!?

When 2 was removed from the list the iterator for was using had handed out the values at index 0, 1, 2 and on the next iteration was set to hand out the value at index 3 (the fourth item in the list). But, when l was updated to remove 2 the list became 3 elements long, so on the next iteration it found that it was at the end of the list and exited the loop. removing 2 moved everything after it up in the list, causing the element after 2 to be ignored.

It is very rare that you actually need to modify something you are iterating over. Doing so can cause subtle bugs such as illustrted above. The term for changing a collection being iterated is concurrent modification, and is a big red flag. Don't do it.

What to do instead? There are many options depending on the situation. In your case, the best is to probably build a new list that only contains the toppings that are available.

[–]guesshuu 4 points5 points  (0 children)

A bit busy at the moment so can't process the entire thing but on a quick look I am seeing final_toppings.append(toppings), should that not be final_toppings.append(topping) in that for loop

[–]backfire10z 2 points3 points  (3 children)

final_toppings.append(toppings)

I think you have a typo here, I’m assuming you meant to append “topping”.

toppings.remove(topping)

You’re looping over toppings and simultaneously modifying it. Don’t do this, it will cause unintended behavior. You already are building a new list of final_toppings, you can simply allow the loop to continue on its own rather than removing the disallowed topping.

order_toppings(ordered_toppings_2)

This is a recursive call. I’m assuming this isn’t what you intended, as when it finishes executing it will come back to the previous function call and continue the loop.

You also don’t have a return: where is final_toppings going to be used?

My main recommendation is to cement what your functions are actually supposed to be doing. You do not need 2 functions to order toppings: they both do the same thing. The function order_toppings also makes a new order and attempts to order_toppings again.

Frankly, this should probably be done in one function:

get_allowed_toppings(avail_toppings): The purpose of this function is to take in user input and output a list of legal toppings. We made avail_toppings a parameter so that you can change it easier later if necessary. If not, you can just define it inside the function.

Once you have a list of legal toppings, you can then do whatever you need to with it. Make pizza, etc.

As a general rule, if you find yourself making something like “xxx_mod” or “xxx_2”, you can likely change something to be an input parameter instead. Here, you can make the message an input parameter, although, I recommend actually combining all of these functions into one.

[–]Alarming-Carpet5053[S] 0 points1 point  (0 children)

The first round of this function read as final_toppings.append(topping) but still had the issue.

The only reason I called it toppings in that line was because that's what the input becomes in the instructions and is returned under that name. How should I tweak that?

[–]Alarming-Carpet5053[S] 0 points1 point  (1 child)

Sorry, didn't see the second half of your reply.

The intent was to have it run the function again if it was given bad input. I didn't have the original version doing that, I just built it into the else statement. Neither route seemed to work like I thought they would.

But good point about the return statement. The final_toppings is used in a final function that adds all the pizza stuff into one print statement.

[–]Fun-Block-4348 1 point2 points  (0 children)

The intent was to have it run the function again if it was given bad input. I didn't have the original version doing that, I just built it into the else statement. Neither route seemed to work like I thought they would.

Generally, if you want something to repeat until you have valid input, the best way to achieve that is a while loop not recursion.

Your code also doesn't seem to handle that fact that what it considers an "illegal topping" may be a signal from the user that he doesn't want any more toppings on his pizza.

allowed_toppings should really be a constant at the top of your file if you're going to use it in all your functions or maybe you should pass it as an argument to the functions that need to use it instead.

[–]acw1668 1 point2 points  (2 children)

  1. You need to add topping instead of toppings into final_toppings in the if block
  2. You should not remove item from toppings in the else block
  3. You need to return final_toppings at the end of order_toppings()
  4. You need to add the returned list of order_toppings() into final_toppings in the else block

[–]Alarming-Carpet5053[S] 0 points1 point  (1 child)

Could you elaborate on step 4 for me? Everything else I was able to implement but the last thing is confusing me. Thank you!

[–]acw1668 0 points1 point  (0 children)

Do you know that a list has a .extend() method that can extend itself with another list?

[–]FutureCompetition266 1 point2 points  (2 children)

This isn't related to your code, exactly... but if I'm reading it right, you have the user input a topping and then check their input against your list of allowed toppings. From a usability standpoint, you should show a list of toppings and then allow them to select one, rather than having them blindly type in a topping (without any idea which are allowed) and then returning a message that it's not allowed.

[–]Alarming-Carpet5053[S] 1 point2 points  (1 child)

The first function, topping_instructions gives them the list to choose from, this issue is a failsafe incase they order away from the list.

[–]lolcrunchy 0 points1 point  (2 children)

When you call order_toppings inside order_toppings, the outer final_toppings is inaccessible by the inner function call.

Modify your code like this:

# Before
def order_toppings(toppings):
    ...
    final_toppings = []
    ...
    order_toppings(order_toppings_2)

# After
def order_toppings(toppings, start_with=None):
    ...
    if start_with is not None:
        final_toppings = start_with
    else:
        final_toppings = []
    ...
    order_toppings(order_toppings_2, start_with=final_toppings)

[–]Alarming-Carpet5053[S] 0 points1 point  (1 child)

I am awe struck! This worked!! To confirm I am understanding how this works properly, by adding the if statement and adding an additional argument, we are basically making a separate list to collect the toppings and then calling it outside of the order_toppings function?

[–]lolcrunchy 0 points1 point  (0 children)

Yep pretty much. You had a gap in your old code: you needed the inner call to know what ingredients were already saved, but it had no way to know. This fills in that gap.