Clean way to variable check + continue in while loop - python

I have a relatively large main() function where I need to check a variable several times at different places in the function and then decide if I should continue the loop accordingly, like so:
def main():
while True:
...
if foo.check():
reset()
continue
...
Where foo is working as a time keeper of sorts, therefore I need to check it at certain intervals. These 3 lines appear about 3 to 5 times within the function.
I hate how dirty this is, so is there a cleaner way to do this?

You haven't specified enough information. I have two questions:
Is the call foo.check() idempotent meaning it always returns the same value and has no side-effects?
Is there a path through the code where you can reach the nth call to foo.check() in the same block governed by the continue statement without first calling the n-1th occurrence?
If, for example, the answer to the answer is yes and the second question were no, then you could remove all but the first occurrence of the call to foo.check() because the return value is clearly False or else you would never reach the second occurence.
If the answer to the first question is yes and the second is yes, then if the call to foo_check() is expensive, I might consider up front setting:
check_result = foo.check()
and then replacing every call to foo_check() with check_result. But ultimately you still need to do all the checks. But in all cases you can create a function check_and_reset:
def check_and_reset():
if foo_check():
reset()
return True
return False
Then your code becomes:
if check_and_reset(): continue

I have two suggestions:
Suggestion 1:
Create another function and call wherever you want.
Suggestion 2:
Make it as a one-liner
if foo.check():reset();continue

If it is just a complex way to control some timing while running a set of tasks, I'd suggest to introduce a loop over the tasks. You can even easily pass partial results around:
def task_a():
...
def task_c(some_input):
...
tasks = [lambda x: task_a(), task_c]
last_stage_output = None
while True:
reset()
for task in tasks:
if not foo.check():
break
last_stage_output = task(last_stage_output)
This way, you make it clear that it is just a series of tasks to be done, it's simple to add, remove of reshuffle them, and the timing logic is concentrated in a single point.

Related

What happen if we do for loop using exhausted generator in Python?

I am creating a generator in python 3 which may yield a single value or more.
The condition that I wanted is, I want to loop with this iterator starting at the second value and so on, running an API request function with that value. If the generator yield only a single value, the for loop and corresponding code is not needed to be executed. If the generator yield more than one value, the function inside the for-loop will be executed starting from the second value of generator and so on.
The reason why I want to start at the second value is because the first value is already accessed for the API request and its result has been stored.
My question is related to a generator that produce a single value.
I give the code example below: (I simplified API Request with print() function):
def iterexample(): # creating a simple iterator that return a single value
yield 0
print(0)
iter = iterexample()
next(iter) #generator is consumed once here
for i in iter: #1 generator is exhausted
print(i, ' inside loop') #2 this is skipped because the generator is exhausted
#3 rest of the code outside the loop will be executed
It returns what I expected: only 0 is printed, not "0 inside loop"
0
My question is:
Is it the safest and the most pythonic way to do that? will it raise
any error?
Will it produce infinite loop? I am very afraid if it will result as
infinite loop of API request.
Please review my #1 ~ #3 comment in above codes, are my
understanding correct?
Thanks for the response and the help. Cheers!
1 Is it the safest and the most pythonic way to do that? will it raise any error?
Once a generator is exhausted, it will continually raise StopIteration exceptions when asked for new values. For loops can handle this case by terminating the loop when this exception is raised, which makes it safe to pass an exhausted generator to a for loop constructor.
However, your code calls next directly, and is therefore only safe only if it also handle StopIteration exceptions. In this case you would need to document that the generator provided must produce 1 or more values or be tolerant of the empty case. If the generator returned no values, then you would get an error. e.g.
def iterexample():
while False:
yield 0
print(next(iterexample()))
Traceback (most recent call last):
File "test.py", line 5, in <module>
print(next(iterexample()))
StopIteration
To prevent against empty generators you can use the second optional default argument to next.
print(next(iterexample(), "default"))
default
2 Will it produce infinite loop? I am very afraid if it will result as infinite loop of API request.
Again this depends on the generator. Generators do not need to have an end value. You can easily define non-ending generators like this:
def iterexample():
i = 0
while True:
yield i
i += 1
for i in iterexample(): #This never ends.
print(i)
If this is a concern for you, one way to prevent never ending outputs would be to use an islice that cuts off your generator after so many values are consumed:
from itertools import islice
for i in islice(iterexample(), 5):
print(i)
0
1
2
3
4
If I understand correctly your issue: you have a first value that you need for a case, and the rest for another case.
I would recommend building a structure that fits your needs, something like this:
class MyStructrue:
def __init__(self, initial_data):
if not initial_data:
# Make sure your data structure is valid before using it
raise ValueErro("initial_data is empty")
self.initial_data = initial_data
#property
def cached_value(self):
return self.initial_data[0]
#property
def all_but_first(self):
return self.initial_data[1:]
In this case, you make sure your data is valid, and you can give your accessors names that reflects what you those value are representing. In this example, I gave them dummy names, but you should try to make something that is relevant to your business.
Such a class could be used this way (changed names just to illustrate how method naming can document your code):
tasks = TaskQueue(get_input_in_some_way())
advance_task_status(tasks.current_task)
for pending_task in tasks.pending_tasks:
log_remaining_time(pending_tasks)
You should first try to understand what your datastructure represents and build a useful api that hide the implementation to better reflect your business.

Conditional Function Definition Best Practise

My coworker and I have been talking about some code that he wrote in a pull request. It involves defining a function within each branch of a conditional statement.
Original:
if is_something: # constant, but unknown until runtime
def do_thing():
return 'x'
else:
def do_thing():
return 'y'
while True:
# other code here
foo = do_thing()
My first thought was to shift the conditional into the function definition and use it like that, but that does mean the condition is needlessly evaluated for every loop.
My second thought was to define two different functions (with different names), then in a conditional to assign the correct function to a variable which is then later called in the loop.
Option 2:
def thing_x():
return 'x'
def thing_y():
return 'y'
if is_something: # constant, but unknown until runtime
thing_func = thing_x
else:
thing_func = thing_y
while True:
# other code here
foo = thing_func()
I like this version better, but I can't articulate why I like this version better.
Is there any practical reason for one or the other, or is it just a "pick one" situation?
I would go with Option 2. Defining a function inside an if statement is a poor way to organize your code. If you group all your functions together it will be easier to read and maintain. Pretend that you have left your company and someone else is trying to update your code. You want it to be as readable as possible.
I know this is just your example snippet but I also wanted to point out that having a "while True" condition is also a bad idea as it could create an infinite loop.
It seems like you could do the following and then the code would even work if is_something switches:
def do_thing(is_something):
if is_something:
# Some code
else:
# Other code
while True:
do_thing(is_something)
Option 2 is more reasonable. We define function so that we can call them and use as and when necessary without rewriting the code again and again. I don't understand why you need to write a function within a condition. Assuming do_thing() is same function :
Original code can be written as:
def do_thing():
return 'x'
if is_something:
do_thing()
else:
do_thing()
while True:
# other code here
foo = do_thing()

Updating only the first occurrence of an object in a queue with python without break

I created a function in python that will change the priority for the first occurrence of an object, but im having trouble fixing it so that it would only work for the first occurrence without the use of break. In my code below I have used break and it works as intended but I would like to not use it.
def update_priority(self, object, priority):
for i in range(len(self._queue)):
if object == self._queue[i].get_item():
# checking object already has that priority
if priority == self._queue[i].get_priority():
# dont change if it has the priority
pass
# if the object does not have that priority set to new
else:
self._queue[i].set_priority(pri)
break
else:
pass
It sounds like you also want to learn how to reduce the length of the code. A rule of thumb is to concentrate on making your code clear and concise first, so you can try to identify ways to simplify the structure. For example, you can restructure and remove redundant branches. Many of your cases are just passes. Also, yes, college classes say that break statements aren't great. For clarity, you might want to use a conditional variable to end the loop. In this case, you don't really need to do that, but an alternative is to wrap the code in a function and bypass the rest of a loop simply by returning, You already use a function, so you can just return from the function if all you do is update the one item's priority (and exit). Perhaps you want to return a status code indicating that an item was found or not. (True or False).
def update_priority(self, object, priority):
# check all items in the queue (you do not require the index.)
# the entry is stored in "entry"
for entry in self._queue:
# if you find the object and its priority needs an update
if object == entry.get_item() and priority != entry.get_priority():
# set the priority
entry.set_priority(priority)
# return true for success, you found the object and updated it
return True
""" If you arrive at this line, the object didn't exist or
it didn't need an update (if you need to distinguish between "object not found" and
"object found but didn't update," use an extra flag or nest the != entry.get_priority as you did before"""
return False
Your solution is correct you do not need to change it so it doesnt use break. There is no other way to end a loop unless its a while loop and the condition is false or you reach the end of range in a for loop.

How to save values from a "for" loop? Python 3

Yesterday I posted a question where I was searching for a way to do an infinite for loop, without using while at all (because my teacher wants so, and also, we can't use any commands we haven't seen in class). It was difficult as apparently there wasn't a very viable option that didn't use while, or other functions like itertools or .append, etc.
You can see that question here
Also, thanks a lot for the feedback you guys brought me! :)
But I managed to talk with my teacher and we got permission to use itertools or just a range big enough (instead of actually infinite).
I solved a few exercises already, but now I have the following instructions:
(Think about grades)
• Ask the user a number (through inputs), and keep asking until the user tells to stop.
• Then, calculate the average from all the numbers entered.
(It's actually a little more complex, but I shortened it and I believe I can deal with the rest)
As I said, I must use a for loop, and I can't use whiles at all.
If I could use while, I'd do something like this:
def grades():
totalg = 0
countg = 0
keepAdding = "y"
while(keepAdding == "y"):
qualif = int(input("Insert grades obtained, in scale from 0 to 100 "))
totalg = totalg + qualif
countg = countg + 1
keepAdding = str(input("Do you wish to keep adding data? (y/n) "))
print("The average of your grades is", totalg/countg)
How can I do something like that, but with for loops? I have no idea on how to store the data for later calculation.
Also, I'm interested into knowing a more "proper" way to be able to end the loop, but I can't use a break neither.
Thanks in advance! Any advice is appreciated and welcome! :)
One way to do this is, without outside modules, is to use two-arg iter; when passed two arguments, the first is a no-argument function to call over and over, and the second is a sentinel value than indicates you should stop.
So for example, you could make an infinite loop with something as simple as:
for _ in iter(bool, True):
Since the bool constructor returns False, a sentinel value of True will never occur, so that's an infinite loop.
Similarly, to make a loop that prompts until the user responds with a given sentinel string, like 'q' for quit (or just the empty string), you could do:
for inp in iter(lambda: input("Insert grades obtained, in scale from 0 to 100 (type 'q' to quit)"), 'q'):
val = int(inp)
... rest of loop ...
Obviously, this is a little obscure (two-arg iter is rarely seen), so usually you'd use while True: with the loop containing a test-and-break or test-and-return (the latter doesn't violate the letter of the teacher's requirements). Similarly, exception handling can be used to exit the loop, though it's ugly:
try:
for ...:
if test_for_end:
raise StopIteration
except StopIteration:
pass
# You're outside the loop
Note: Literally everything I've mentioned is terrible style, aside from an actual while True: loop with a test-and-break/return case. But you've got one hand tied behind your back, so I'm suggesting some terrible mutant hands to substitute.
Even though 'no-break' rule and a request for a "proper" way to end a loop are somewhat contradictory, I'd say that is possible, even without return :
grades = [0]
for j in grades:
t = int(raw_input('grade:'))
ans = raw_input('more? [y/n]:').lower()
if(ans == 'y'):
grades.append(t)
else:
grades[0] = t
print(sum(grades)*1.0/len(grades))
here we just iterate over ever-growing list of values, and when we need to stop - we simply stop adding values to it, and the loop ends.
HOWEVER
This is NOT a proper way of handling that issue, read ShadowRanger for more details - this is bad coding style and should not be used.
Maybe saving all the data obtained in an array?
I think arrays on python work as Linked Lists, so you won't have any overflow problems, at least that would be a starting point

Python question on parse routines with dicts, while loops, and assignments

I have some code from a much-maligned Python resource, if you ask SO, but I am trying to divine Python’s parse routine from it, regardless its didactic merits.
def princess_lives_here():
#<some messages and conditionals to keep you going in the text-based game>
#<one of the conditionals end in a `return 'death'`, another function in ROOMS>
ROOMS = {
'death': death,
'princess_lives_here': princess_lives_here,
}
def runner (map, start):
next = start
while True:
room = map[next]
print "\n--------"
next = room()
runner(ROOMS, 'princess_lives_here')
What I am not sure about is how princess_lives_here is ever run in the while loop. I can only assume that it is run is the last line, but what looks like an assignment to me must be an execution of room(), i.e. princess_lives_here. Why, when and how is this execution performed?
My second question is how, when, and why the while loop is run again, when the parsing routines hits return 'death'.
I’ve created a gist with the entire code, in case you want the greater picture. It just takes up a lot of lines of code: https://gist.github.com/848228.
After looking at the original code will analyze it for you:
1) The program enters at the point:
runner(ROOMS, 'princess_lives_here')
2) runner function gets a map (a list of available rooms where we can go) and a start point at the map. So, the previous call uses the map defined in the ROOMS dictionary and sets us in the princess_live_here room.
3) The runner loop, gets the actual room dictionary value, so, we get a reference for a function called princess_live_here that is defined at top of the file, prints some output and calls that function using the reference.
4) At princess_lives_here we can input some text, that text will decide what to do next, returning the next room we will go. For example, if we write: "make her eat it", the function will return the key for the 'gold_koi_pond' room, and the loop will then setup that room as the actual room for us, and get again the dictionary value for that room and call it.
So, answering you more focused way:
a) The execution is that I've described above this lines
b) When any function returns 'death' the execution is the same as above, but the death function will print a random quote and exit the program.
Just that.
First thing to mention, is that next is a built-in function in Python and as soon as you assign it to anything else you are loosing it's reference for futures accesses. Therefore:
>>> next
<built-in function next>
>>> next = 'a'
>>> next
'a'
>>>
It's considered a bad practice to override built-in functions. As soon as you assign next to start (first line of runner function) the reference to next built-in is lost in the scope of that function.
More specifically on the questions you posted:
1) Think of functions as 'objects' that can be assigned to variables and that by postfixing () you make a call to them. It's also similar to the concept of pointer to a function in C. In your design, the pointers to functions reside in the dictionary ROOMS, so that when you access as:
room = ROOMS["princess_lives_here"]
room will point to the function princess_lives_here and room() will perform a call to that function. If this function returns a string that is a key on the ROOM dictionary then the next iteration of the loop will do exactly the same.
2) Looking at the code you posted it. if "death" is returned then next will hold that string value, which will be the lookup key for the dictionary ROOM (1st line of the loop), which will make room to hold a pointer to the function death(2nd line of the loop). And the last line of the loop will perform a call to the function that is pointed by room, which is death in this case. Since this function performs exit then the program will terminate. No more iterations will happen.

Categories