omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Recursively getting all objects from all subviews

    Pythonista
    3
    12
    7321
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Phuket2
      Phuket2 last edited by

      I would appreciate any comments or help. I am trying to get every object in a ui.View recursively. The code I have seems to work. But I was trying to create a generator with yield so I could iterate over the objects outside the recursive function. I am way out of my depth of knowledge, tried a few variations with yield. While the generator worked syntactically, I was not getting all the objects back.

      
      def all_sub_views(v):
      	for view in v.subviews:
      		print view, view.name
      		for item in view.subviews:
      			if type(item)==ui.View:
      				print 'called this'
      				all_sub_views(item)
      			else:
      				pass
      	print 'exiting...'
          
      
      
      1 Reply Last reply Reply Quote 0
      • Phuket2
        Phuket2 last edited by

        Update: the above code seems to be able to print out every object that can be created in the ui designer, regardless of the number of subviews the object resides in, except for the segment control. Throws a barrizare error.
        SystemError:/users/ole/development/Xcode/...tuple object.c:54....

        1 Reply Last reply Reply Quote 0
        • Phuket2
          Phuket2 last edited by

          This code seems to work ok with the exception of the segment control (as said above). Having the my_objects list was not what I was going for, but probably more efficient to collect all the objects once, then can iterate over the types as required.

          
          # coding: utf-8
          
          import ui
          my_objects =[]
          
          def treat_button(btn):
          	btn.border_width = 1
          	btn.background_color = 'gray'
          	btn.tint_color = 'white'
          		
          def all_sub_views(v):
          	for view in v.subviews:
          		my_objects.append(view)
          		for item in view.subviews:
          			if type(item)==ui.View:
          				all_sub_views(item)
          			else:
          				pass
          	
          if __name__ =='__main__':
          	v = ui.load_view()
          	all_sub_views(v)
          	for item in my_objects:
          		if type(item)== ui.Button:
          			treat_button(item)
          	v.present('sheet')
          
          
          1 Reply Last reply Reply Quote 0
          • Phuket2
            Phuket2 last edited by

            I guess you could also use a dict instead of a list for my_objects, Saving the depth(layer) of the object etc..., which could further help with theming/style type treatments or other ideas. I assume this would force you to name all objects uniquely. Also would provide a flattened out object addressing way for All the objects contained in any depth of subviews in a given ui.View

            1 Reply Last reply Reply Quote 0
            • Phuket2
              Phuket2 last edited by

              Sorry, my ideas - improvements coming in drips and draps. But here is my code to work around the problem with the _ui.SegmentedControl crashing. The _ui.SegmentedControl still is inserted into the my_objects list

              
              def all_sub_views(v):
              	for view in v.subviews:
              		my_objects.append(view)
              		try:
              			for item in view.subviews:
              				if type(item)==ui.View:
              					all_sub_views(item)
              				else:
              					pass
              		except:
              			#this is an error, but a work around'
              			pass
              
              
              1 Reply Last reply Reply Quote 0
              • ccc
                ccc last edited by

                https://realpython.com/blog/python/the-most-diabolical-python-antipattern

                1 Reply Last reply Reply Quote 0
                • JonB
                  JonB last edited by

                  i think there are some bugs in ui.SegmentedControl, which may have been part of the issue:

                  >>> import ui
                  >>> s=ui.SegmentedControl()
                  >>> s.subviews
                  Traceback (most recent call last):
                    File "<string>", line 1, in <module>
                  SystemError: /Users/ole/Development/xcode/Pythonista/python/Objects/tupleobject.c:54: bad argument to internal function
                  

                  likewise, trying to add_subview to a segmentedcontrol causes pythonista to crash. ccc suggests avoiding except/pass pattern, which is good advice. in this case, the exception was caused by segmentedcontrol not having subviews, in which case using hasattr as a check is a more general way to avoid raising an exception in the first place.

                  as to your specific question, here are a few ways you can tackle the sort of tree traversal problem.
                  here is a method using yield to go depth first recursively... I think you originally wanted to do something like this.

                  # coding: utf-8
                  import ui
                  from collections import deque
                  def depthfirst(v):
                     '''recursivdly walk tree'''
                     if hasattr(v,'subviews'):
                        for sv in v.subviews:
                           yield sv
                           for n in depthfirst(sv):
                              yield n
                      
                  

                  here is another approach, which uses a custom iterator object, so can go depth first or breadth first, nonrecursively. to go depth first, you basically keep a stack (pushing subviews onto the stack). breadth first you use a fifo. the idea is to pop one view from the "todo queue", and add its sub views to the queue, and return the popped view. when there are no views left, edit (for iterations this means raising StopException)

                  class ViewWalker(object):
                     '''simple iterator for ui.View objects, capable of depth or breadth first traversal'''
                     def __init__(self,v,breadthfirst=False):
                        self._dq=deque([v])
                        self._bredth=breadthfirst
                        
                     def __iter__(self):
                        '''required for iterator objects'''
                        return self
                        
                     def next(self):
                     """required for iterator objects.  raise stopiteration once the queue is empty.  """
                        if not self._dq:
                           raise StopIteration
                        #pop next view...
                        if self._breadth:
                           v=self._dq.popleft()# oldest entry (FIFO)
                        else:
                           v=self._dq.pop() # newest entry (stack)
                        #then push its subviews
                        if hasattr(v,'subviews'):
                           self._dq.extend(v.subviews)
                        return v
                        
                  v=ui.load_view('test')
                  print 'depthfirst:', [s.name for s in ViewWalker(v)]
                  print 'breadthfirst:', [s.name for s in ViewWalker(v,True )]
                  
                  1 Reply Last reply Reply Quote 0
                  • Phuket2
                    Phuket2 last edited by

                    @ccc, yes agreed, understand that sort of code should enter production code, also should not be used as a crutch. But, using it sometimes in my learning phase, just to get code running and to be able post something here to get feedback.

                    @JonB, thanks again. I will try both your functions. The first one, of most interest to me.
                    Do you think if my function was implemented with checking hasattr(v, 'subviews') , is a valid implementation? Even though initially I was after a generator, I can see the value of creating a so called object map only once, possibly making it more functional saving a dict of list of dict items.

                    1 Reply Last reply Reply Quote 0
                    • Phuket2
                      Phuket2 last edited by

                      Adjusted code

                      
                      def all_sub_views(v):
                      	for view in v.subviews:
                      		my_objects.append(view)
                      		if hasattr(view, 'subviews'):
                      			for item in view.subviews:
                      				if type(item)==ui.View:
                      					all_sub_views(item)
                      
                      1 Reply Last reply Reply Quote 0
                      • ccc
                        ccc last edited by

                        You might want to consider using isinstance(x, y) instead of type(x) == y to find all objects that are instances of y and subclasses of y.

                        import ui
                        class MyView(ui.View):               # MyView is a subclass of ui.View
                            pass
                        my_view = MyView()
                        print(type(my_view) == ui.View)      # prints False because MyView != ui.View
                        print(isinstance(my_view, ui.View))  # prints True  because MyView is a ui.View
                        
                        1 Reply Last reply Reply Quote 0
                        • JonB
                          JonB last edited by

                          I think you'll have problems using the global variable approach once you start dealing with multiple views, or, for example once the "always clear globals" bug is fixed in the beta. you will need to make sure you always clear my_objects before calling your function.

                          note that if you want a list generated one time, simply call list on your generator:

                          my_objects=list(depthfirst(v))
                          

                          this traverses the list once, but avoids globals.

                          note in my original example there was a bug, t(sv) should have read depthfirst(sv)... I'll edit the post to correct this.

                          1 Reply Last reply Reply Quote 0
                          • Phuket2
                            Phuket2 last edited by

                            Really, thanks guys, such great answers. So helpful.

                            @JonB, I see your point. Really comes down to knowing the language well enough to derive different results from the same function with some small syntax changes. Blows me away daily how flexible Python is. But, it's clear to me that my code isn't valid. All can be done with your first example. So I will keep that code in a lib of helpers for the ui. Module

                            @ccc, thanks also. I can see I need to get my head around isinstance, I am not thinking in a Object way. Hmmm, so much to learn :)

                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post
                            Powered by NodeBB Forums | Contributors