2

Is it possible to identify if we are in the middle of a loop?

I want to make the class automatically identify if the variable came from an iterator or not.

I checked inspect module and didn't succeed. Maybe there is some scope identifier or some brilliant magic methods manipulation that can flag when a code runs inside a loop.

Here's an example to make myself clear. How to set inside_loop Student attribute?

class Student:
    def __init__(self, id):
        self.id = id
        self.inside_loop = ???
    def print_grade(self):
        if self.inside_loop:
            print("you are inside a loop")
        else:
            print("checking grade...")     

>>> S = Student(1)
>>> S.print_grade()
>>> checking grade...

>>> student_ids = [1, 2, 3]
>>> for student in student_ids:
>>>     S = Student(student) 
>>>     S.print_grade()
>>>       
>>> you are inside a loop
>>> you are inside a loop
>>> you are inside a loop

Thanks in advance.

VirtualScooter
  • 1,792
  • 3
  • 18
  • 28
O Pardal
  • 647
  • 4
  • 21
  • set it to true/false before and after the loop? – dfundako Jul 27 '21 at 13:16
  • Thanks, @dfundako. My problem here is to make the class automatically identify if the variable came from a iterator or not. – O Pardal Jul 27 '21 at 13:18
  • Does this answer your question? [How to get the caller's method name in the called method?](https://stackoverflow.com/questions/2654113/how-to-get-the-callers-method-name-in-the-called-method) – A.M. Ducu Jul 27 '21 at 13:20
  • I think getting the method name can help you know if you are in a loop or not. – A.M. Ducu Jul 27 '21 at 13:21
  • 1
    If you need `print_grade` to behave differently when it's inside a loop, it should probably be two different methods, or have an extra parameter. The behave you are describing would be confusing sorcery to anyone trying to debug the code. – khelwood Jul 27 '21 at 13:23
  • Why `Student` needs to know if it part of a loop? – balderman Jul 27 '21 at 13:24
  • You could make a custom `loop()` function that takes in a lambda or a function and runs that function a specified amount of times. That function could write to a global flag while it is iterating. – zr0gravity7 Jul 27 '21 at 13:26
  • Rename `students = [1, 2, 3]` to `students_ids = [1, 2, 3]`. Naming is important. – balderman Jul 27 '21 at 13:26
  • @balderman It is just curiosity about python possibilities. My real question is related to the very well-known fact one should avoid SQL inside loops. I have a class that execute a very expensive query in a database. I want to make possible use the class to execute a query with a single value for a parameter. But, in a for loop I want to first accumulate values and only then execute the SQL. This problem is already tackled by SQL libraries . But I would like to check the python capabilities. – O Pardal Jul 27 '21 at 13:28
  • Hmm..., what about this one: `def foo(student):` `S = Student(student)` `S.print_grade()` and then `for i in students: foo(i)`. Is it inside a loop (it is at the outer level) or not (it uses a local variable in a function)? – Serge Ballesta Jul 27 '21 at 13:29
  • @zr0gravity7 I'm dont know exactly how to implement your idea, but I am checking. Thanks – O Pardal Jul 27 '21 at 13:30
  • What you ask for is maybe possible with a lot of hacking, but this kind of cleverness is not a good way to write programs in my opinion. – Paul Hankin Jul 27 '21 at 13:31
  • @PaulHankin, You are right. I'm just trying to expand my understanding of python skeleton. – O Pardal Jul 27 '21 at 13:34
  • Only my opinion but I think that some points are the responsability of the producer of a class, and some others are the responsability of the **user** of the class. Warning that a method uses an expensive query is the reponsability of the producer, but not using in repeatedly in a loop if the one of the user. I'm afraid you are about to knock your head against numerous corner cases... – Serge Ballesta Jul 27 '21 at 13:34
  • ... For example, the variable could be computed from various values included one coming from an iterator. Good luck if you want to programatically identify that... – Serge Ballesta Jul 27 '21 at 13:37
  • @A.M.Ducu I read about caller's method name you sent, but I didnt realize how to connect with for loop. – O Pardal Jul 27 '21 at 13:37

1 Answers1

1

I have advanced a little bit in this question. I used ast and inspect standard modules of python. Since we had some worrisome comments, I would appreciate if you point out possible bugs in the code below. Until now, it solves the original problem.

With inspect.currentframe().f_back I get the frame where the class was instantiate. Then the frame is used to build the abstract tree. For each tree node, it is set the parent attribute. Finally, I select the node that has the same line number than the original frame class instantiation assignment and check if this node parent is a ast.For, ast.While, or a ast.AsyncFor.

It does not work in a pure python shell. See discussion in get ast from python object in interpreter.

import ast
import inspect

class Student:

    def __init__(self, id):
        self.id = id
        self.previous_frame = inspect.currentframe().f_back
        self.inside_loop = self._set_inside_loop()
    
    def _set_inside_loop(self):
    
        loops = ast.For, ast.While, ast.AsyncFor
        nodes = ast.parse(inspect.getsource(self.previous_frame))
    
        for node in ast.walk(nodes):
            try:
                for child in ast.iter_child_nodes(node):
                    child.parent = node
            
                instantiate_class = node.lineno == self.previous_frame.f_lineno
                loop_parent = isinstance(node.parent, loops)
            
                if instantiate_class & loop_parent:
                    return True
            except Exception:
                continue
        else:
            return False
    
    def print_grade(self):
        if self.inside_loop:
            print("you are inside a loop")
        else:
            print("checking grade...")  

Now we have:

d = Student(4)
d.print_grade()

>>> checking grade...

students = [1,2,3]
for i in students:
    s = Student(i)
    s.print_grade()

>>> you are inside a loop
>>> you are inside a loop
>>> you are inside a loop

i = 1
while i<4:
    s = Student(i)
    s.print_grade()
    i +=1

>>> you are inside a loop
>>> you are inside a loop
>>> you are inside a loop
VirtualScooter
  • 1,792
  • 3
  • 18
  • 28
O Pardal
  • 647
  • 4
  • 21
  • Can you elaborate on "it does not work in the python shell"? I tried it in iPython 7.22.0 with Python 3.9.5, and it ran just fine; I got exactly the output you describe in the second code block. – VirtualScooter Jul 30 '21 at 22:25
  • Actually, I mean pure python interpreter. – O Pardal Jul 30 '21 at 22:54
  • 1
    Here is some discussion about the issue: https://stackoverflow.com/questions/60797338/get-ast-from-python-object-in-interpreter – O Pardal Jul 30 '21 at 22:56