Check if a function has been called from a loop - python

Is it possible to check dynamically whether a function has been called from within a loop?
Example:
def some_function():
if called_from_loop:
print("Hey, I'm called from a loop!")
for i in range(0, 1):
some_function()
some_function()
Expected output:
> Hey, I'm called from a loop!

Not sure if this is going to be 100% reliable. It's also rather cumbersome and would have to be written inside the function of interest rather than in its own discrete function for fairly obvious reasons.
We can get the source code for the currently executing .py file
We can also get a stack trace.
Therefore, we can determine the line number in the code from where our function has been called. Thus we can navigate the source code (backwards) taking care to note indentation and see where the indentation decreases and if the line where that occurs starts with either 'for' or 'while' then we were called from within a loop.
So here's the fun:
import traceback
import inspect
import sys
def getws(s):
return len(s) - len(s.lstrip())
def func():
source = inspect.getsource(sys.modules[__name__]).splitlines()
stack = traceback.extract_stack()
lineno = stack[-2].lineno-1
isLooped = False
if (ws := getws(source[lineno])) > 0:
for i in range(lineno-1, -1, -1):
if (_ws := getws(line := source[i])) < ws:
if (tokens := line.split()) and tokens[0] in {'for', 'while'}:
isLooped = True
break
if (ws := _ws) == 0:
break
print('Called from loop' if isLooped else 'Not from loop')
def looper():
for _ in range(1):
# banana
func()
def looper2(): # this code is nonsense
i = 5
while i > 0:
if i < 10:
func()
break
looper()
looper2()
func()
while True:
func()
break
if __name__ == '__main__':
func()
Output:
Called from loop
Called from loop
Not from loop
Called from loop
Not from loop

Related

Recursion program flow

I have some textbook code that calls itself recursively. I don't understand the program flow. Here is the code:
def Recur_Factorial_Data(DataArray):
numbers = list(DataArray)
num_counter = 0
list_of_results = []
for num_float in numbers:
n = int(num_float)
1. result = Recur_Factorial(n)
list_of_results.append(result)
def Recur_Factorial(num):
if num == 1:
2. return num
else:
result = num*Recur_Factorial(num-1)
3. return result
if num < 0:
return -1
elif num == 0:
return 0
else:
return 0
In Recur_Factorial_Data, I loop through the data elements and call Recur_Factorial, which returns its value back to the calling function (Recur_Factorial_Data). I would expect that the lines marked 2 ("return num") and 3 ("return result") would always return a value back to the calling function, but that's not the case. For example, where the initial value (from the array DataArray) is 11, the function repeatedly calls itself until num is 1; at that point, the program falls to the line marked 2, but it does not loop back to the line marked 1. Instead, it falls through to the next line. The same happened on the line marked 3 -- I would expect it to return the result back to the calling function, but it does that in some cases and not in others.
I hope this is enough description to understand my question -- I just don't know why each return does not loop back to return the result to the calling function.
EDIT: The question at Understanding how recursive functions work is very helpful, and I recommend it to anyone interested in recursion. My question here is a bit different -- I asked it in the context of the program flow of the Python code where the recursive function is called.
If it call itself recursively 10 times, it will be at the 10th level of recursion and should go back 10 times at the point where there was a recursive call with an intermediate result and only then exit from the recursive function to the place where it was called. Learn more about recursion
Also try to rearrange instructions in Recur_Factorial function in this way:
def Recur_Factorial(num):
if num < 0:
return -1
elif num == 0:
return 0
elif num == 1:
return num
else:
return num * Recur_Factorial(num-1)

Python - using returns in conditional statements

I want to use return instead of print statements but when I replace the print statements with return I don't get anything back. I know I'm missing something obvious:
def consecCheck(A):
x = sorted(A)
for i in enumerate(x):
if i[1] == x[0]:
continue
print x[i[0]], x[i[0]-1]
p = x[i[0]] - x[i[0]-1]
print p
if p > 1:
print "non-consecutive"
break
elif x[i[0]] == len(x):
print "consecutive"
if __name__ == "__main__":
consecCheck([1,2,3,5])
-----UPDATE------ HERE IS CORRECTED CODE AFTER TAKING HEATH3N's answer:
def consecCheck(A):
x = sorted(A)
for i in enumerate(x):
if i[1] == x[0]:
continue
print x[i[0]], x[i[0]-1]
p = x[i[0]] - x[i[0]-1]
print p
if p > 1:
a = "non-consecutive"
break
elif x[i[0]] == len(x):
a = "consecutive"
return a
if __name__ == "__main__":
print consecCheck([4,3,7,1,5])
I don't think you understand what a return statement does:
A return statement causes execution to leave the current subroutine and resume at the point in the code immediately after where the subroutine was called, known as its return address.
You need to wrap consecCheck([1,2,3,5]) in a print statement. Otherwise, all it does it call the function (which no longer prints anything) and goes back to what it was doing.
print takes python object and outputs a printed representation to console/output window
when return statement used in function execution of program to calling location, also if function execution reaches to return statement then no other line will be executed. read detail difference between print and return
So In your case if you want to show result in output console you may do it as following example:
def my_function():
# your code
return <calculated-value>
val = my_function()
print(val) # so you can store return value of function in `val` and then print it or you can just directly write print(my_function())
In your code you are printing values and continuing execution in that case you might consider using yield keyword suggested by #COLDSPEED or just use print to all statement except last one

Redo for loop iteration in Python

Does Python have anything in the fashion of a "redo" statement that exists in some languages?
(The "redo" statement is a statement that (just like "break" or "continue") affects looping behaviour - it jumps at the beginning of innermost loop and starts executing it again.)
No, Python doesn't have direct support for redo. One option would something faintly terrible involving nested loops like:
for x in mylist:
while True:
...
if shouldredo:
continue # continue becomes equivalent to redo
...
if shouldcontinue:
break # break now equivalent to continue on outer "real" loop
...
break # Terminate inner loop any time we don't redo
but this mean that breaking the outer loop is impossible within the "redo-able" block without resorting to exceptions, flag variables, or packaging the whole thing up as a function.
Alternatively, you use a straight while loop that replicates what for loops do for you, explicitly creating and advancing the iterator. It has its own issues (continue is effectively redo by default, you have to explicitly advance the iterator for a "real" continue), but they're not terrible (as long as you comment uses of continue to make it clear you intend redo vs. continue, to avoid confusing maintainers). To allow redo and the other loop operations, you'd do something like:
# Create guaranteed unique sentinel (can't use None since iterator might produce None)
sentinel = object()
iterobj = iter(mylist) # Explicitly get iterator from iterable (for does this implicitly)
x = next(iterobj, sentinel) # Get next object or sentinel
while x is not sentinel: # Keep going until we exhaust iterator
...
if shouldredo:
continue
...
if shouldcontinue:
x = next(iterobj, sentinel) # Explicitly advance loop for continue case
continue
...
if shouldbreak:
break
...
# Advance loop
x = next(iterobj, sentinel)
The above could also be done with a try/except StopIteration: instead of two-arg next with a sentinel, but wrapping the whole loop with it risks other sources of StopIteration being caught, and doing it at a limited scope properly for both inner and outer next calls would be extremely ugly (much worse than the sentinel based approach).
No, it doesn't. I would suggest using a while loop and resetting your check variable to the initial value.
count = 0
reset = 0
while count < 9:
print 'The count is:', count
if not someResetCondition:
count = count + 1
This is my solution using iterators:
class redo_iter(object):
def __init__(self, iterable):
self.__iterator = iter(iterable)
self.__started = False
self.__redo = False
self.__last = None
self.__redone = 0
def __iter__(self):
return self
def redo(self):
self.__redo = True
#property
def redone(self):
return self.__redone
def __next__(self):
if not (self.__started and self.__redo):
self.__started = True
self.__redone = 0
self.__last = next(self.__iterator)
else:
self.__redone += 1
self.__redo = False
return self.__last
# Display numbers 0-9.
# Display 0,3,6,9 doubled.
# After a series of equal numbers print --
iterator = redo_iter(range(10))
for i in iterator:
print(i)
if not iterator.redone and i % 3 == 0:
iterator.redo()
continue
print('---')
Needs explicit continue
redone is an extra feature
For Python2 use def next(self) instead of def __next__(self)
requires iterator to be defined before the loop
I just meet the same question when I study perl,and I find this page.
follow the book of perl:
my #words = qw(fred barney pebbles dino wilma betty);
my $error = 0;
my #words = qw(fred barney pebbles dino wilma betty);
my $error = 0;
foreach (#words){
print "Type the word '$_':";
chomp(my $try = <STDIN>);
if ($try ne $_){
print "Sorry - That's not right.\n\n";
$error++;
redo;
}
}
and how to achieve it on Python ??
follow the code:
tape_list=['a','b','c','d','e']
def check_tape(origin_tape):
errors=0
while True:
tape=raw_input("input %s:"%origin_tape)
if tape == origin_tape:
return errors
else:
print "your tape %s,you should tape %s"%(tape,origin_tape)
errors += 1
pass
all_error=0
for char in tape_list:
all_error += check_tape(char)
print "you input wrong time is:%s"%all_error
Python has not the "redo" syntax,but we can make a 'while' loop in some function until get what we want when we iter the list.
Not very sophiscated but easy to read, using a while and an increment at the end of the loop. So any continue in between will have the effect of a redo. Sample to redo every multiple of 3:
redo = True # To ends redo condition in this sample only
i = 0
while i<10:
print(i, end='')
if redo and i % 3 == 0:
redo = False # To not loop indifinively in this sample
continue # Redo
redo = True
i += 1
Result: 00123345667899
There is no redo in python.
A very understandable solution is as follow:
for x in mylist:
redo = True
while redo:
redo = False
If should_redo:
redo = True
It's clear enough to do not add comments
Continue will work as if it was in the for loop
But break is not useable, this solution make break useable but the code is less clear.
Here is a solution for python 3.8+ since now we have the := operator:
for key in mandatory_attributes: # example with a dictionary
while not (value := input(f"{key} (mandatory): ")):
print("You must enter a value")
mandatory_attributes[key] = value

Start a new iteration of a loop from a different function in the loop [python]

How do you go about starting the next i without using continue or break?
def function_in_main():
if #something happens:
#start new next iteration for the loop in the main function
def main():
n = 1
for i in range(len(alist)):
print ('step ', n)
function_in_main()
n += 1
main()
Output should look somewhat like:
step 1
#if or until something happens
step 2
etc
Just make function_in_main return when your if statement is true. when it returns, the loop will move on to the next iteration and then re-call function_in_main.
Maybe try raising an exception:
def function_in_main():
if #something happens:
raise Exception
def main():
n = 1
for i in range(len(alist)):
print ('step ', n)
try:
x()
except Exception:
continue
n += 1
main()
You can specify or make whatever type of exception you want.
Here is an example:
def function_in_main(x):
return x=='Whopee' # will return True if equal else False
def main():
alist=['1','ready','Whopee']
for i,item in enumerate(alist, 1):
print ('step {}, item: "{}" and {}'.format(i,item,function_in_main(item)))
main()
Prints:
step 1, item: "1" and False
step 2, item: "ready" and False
step 3, item: "Whopee" and True
Note the use of enumerate rather than manually keeping a counter.
Sure you're not thinking of a while loop or something? It's a little tough to see what you're getting at…
def your_function():
#…do some work, do something to something else
if something_is_true:
return True
else:
return False
def main():
condition = True
for i in range(len(your_list)):
while condition:
print "Step: %d" % i
your_function()
Only when your_function returns False will the loop in main continue, otherwise it will stay within the while loop.

Can a def function break a while loop?

def CardsAssignment():
Cards+=1
print (Cards)
return break
while True:
CardsAssignment()
Yes, I know that I cannot return break. But how can I break a while loop by the def function? Or my concept is wrong?
No it cannot. Do something like:
def CardsAssignment():
Cards+=1
print (Cards)
if want_to_break_while_loop:
return False
else:
return True
while True:
if not CardsAssignment():
break
A very Pythonic way to do it would be to use exceptions with something like the following:
class StopAssignments(Exception): pass # Custom Exception subclass.
def CardsAssignment():
global Cards # Declare since it's not a local variable and is assigned.
Cards += 1
print(Cards)
if time_to_quit:
raise StopAssignments
Cards = 0
time_to_quit = False
while True:
try:
CardsAssignment()
except StopAssignments:
break
Another, less common approach would be to use a generator function which will return True indicating that it's time to quit calling next() on it:
def CardsAssignment():
global Cards # Declare since it's not a local variable and is assigned.
while True:
Cards += 1
print(Cards)
yield not time_to_quit
Cards = 0
time_to_quit = False
cr = CardsAssignment() # Get generator iterator object.
next(cr) # Prime it.
while next(cr):
if Cards == 4:
time_to_quit = True # Allow one more iteration.
You could have CardsAssignment return True (to continue) or False (to stop) and then have
if not CardsAssignment():
break
or indeed just loop
while CardsAssignment():
If you use a for loop instead of a while, you can cause it to break early by raiseing StopIteration - this is the usual signal for a for loop to finish, and as an exception, it can be nested inside functions as deep as you need and will propagate outward until it is caught. This means you need something to iterate over - and so, you probably want to change your function into a generator:
def cards_assignment():
cards += 1
yield cards
for card in cards_assignment():
print(card)
, in which case instead of doing raise StopIteration, you would just return from the generator and the loop will finish. However, note that this (as well as options having the function return a flag that you test in the loop condition) is subtly different to using break - if you use an else clause on your loop, returning from a generator will trigger it, whereas break in the loop body won't.
def CardsAssignment():
Cards+=1
print (Cards)
if (break_it):
return False
else:
return True
while CardsAssignment():
pass
I would be tempted to just re-factor similar to:
def somefunc():
from random import randint
while True:
r = randint(1, 100)
if r != 42:
yield r
And then you can do things such as:
for cnt, something in enumerate(somefunc(), start=1):
print 'Loop {} is {}'.format(cnt, something)
This allows a possible meaningful value to be returned from somefunc() instead of using it as a "do I break" flag.
This will also allow the following construct:
sf = somefunc()
for something in sf:
if some_condition(something):
break
# other bits of program
for something in sf: # resume...
pass
in 3.5, 3.6 you can
return "break"

Categories