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.


    [Re Share] ui.View walker

    Pythonista
    traverse share walk ui.view
    2
    7
    6045
    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 Phuket2

      This is some code @JonB did for me a long time ago. Well i mean helped me with from a post.

      But if you are working with ui's , it can be useful to easily get each subview regardless of its nesting.

      His class can either do depth or breath.

      As I say, this is more or less a re-post

      # from @JonB
      
      import ui
      from collections import deque
      
      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._breadth=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
      
      class MyClass(ui.View):
      	def __init__(self, *args, **kwargs):
      		super().__init__(*args, **kwargs)	
      		
      		self.make_view()
      		
      	def make_view(self):
      		# just add some btns to the view as a test
      		for i in range(1, 11):
      			btn = ui.Button(name = 'btn{}'.format(i))
      			self.add_subview(btn)
      			lb = ui.Label(name = 'lbl{}'.format(i))
      			btn.add_subview(lb)
      		
      			
      mc = MyClass(name = 'CustomView')
      print('depthfirst:', [s.name for s in ViewWalker(mc)])
      print('\n')
      print ('breadthfirst:', [s.name for s in ViewWalker(mc,True )])
      
      1 Reply Last reply Reply Quote 0
      • Phuket2
        Phuket2 last edited by

        Sorry, decided to change this a little.
        Made 2 more methods in the class to do the list comprehensions, returns list of names or objects.
        Also add a an example of a for loop iteration of the ViewWalker object.

        # from @JonB
        
        import ui
        from collections import deque
        
        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._breadth=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
        	
        	def sub_view_objects(self, breadthfirst = False):
        		self._breadth=breadthfirst
        		return [s for s in self]
        	
        	def sub_view_names(self, breadthfirst = False):
        		self._breadth=breadthfirst
        		return [s.name for s in self]
        		
        class MyClass(ui.View):
        	def __init__(self, *args, **kwargs):
        		super().__init__(*args, **kwargs)	
        		
        		self.make_view()
        		
        	def make_view(self):
        		# just add some btns to the view as a test
        		for i in range(1, 11):
        			btn = ui.Button(name = 'btn{}'.format(i))
        			self.add_subview(btn)
        			lb = ui.Label(name = 'lbl{}'.format(i))
        			btn.add_subview(lb)
        		
        			
        mc = MyClass(name = 'CustomView')
        print('breadthfirst', ViewWalker(mc, True).sub_view_objects())
        print('\n')
        print('depthfirst', ViewWalker(mc).sub_view_objects())
        print('\n')
        print('breadthfirst', ViewWalker(mc, True).sub_view_names())
        print('\n')
        print('depthfirst', ViewWalker(mc).sub_view_names())
        print('\n')
        print('breadthfirst', [s.name for s in ViewWalker(mc, True)])
        print('\n')
        for v in ViewWalker(mc, False):
        	print(v)
        
        1 Reply Last reply Reply Quote 0
        • Phuket2
          Phuket2 last edited by

          Just an extra method for the ViewWalker Class.

          Just return a dict using the name of the view as key to the subview object. Ok, there is a flaw, this assumes that all your view names are unique. Personally, I think it's better to fail if this is not the case.

          The reason why I think this is handy. You might build a very complicated view in the Ui Designer, with lots of Subviews etc. it can be a bit of a pain to interact you your controls. Eg a button that's 3 levels deep in Subviews. This essentially allows you to flatten out the access method to your controls.

          I am pretty sure I have been told this is a bad idea in the past. But I am going to use the ui Designer at lot more now. I think this is great for more complicated views. Just my opinion.

          Maybe someone has a smarter idea for the mapping. I havnt done anything more complicated than this before. This seems pretty straight forward to me. Only bummer being you are still using strings rather than field names. Hmmm, maybe a namedtuple's would be better. Not sure. Food for thought.

          def sub_views_mapping(self, breadthfirst = False):
          		self._breadth=breadthfirst
          		return {s.name : s for s in self}
          
          1 Reply Last reply Reply Quote 0
          • Phuket2
            Phuket2 last edited by

            Sorry, reposting the code here. I found a one liner on stackflow to add namedtuples. It's not as useful as they could be because it's late bound, but still nicer to code with dot notation than string subscripts, in my mind...

            # from @JonB
            
            import ui
            from collections import deque, namedtuple
            
            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._breadth=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
            	
            	def sub_view_objects(self, breadthfirst = False):
            		self._breadth=breadthfirst
            		return [s for s in self]
            	
            	def sub_view_names(self, breadthfirst = False):
            		self._breadth=breadthfirst
            		return [s.name for s in self]
            	
            	def as_dict(self, breadthfirst = False):
            		# returns a dict
            		self._breadth=breadthfirst
            		return {s.name : s for s in self}
            	
            	def as_namedtuple(self, breadthfirst = False):
            		# returns a namedtuple
            		self._breadth=breadthfirst
            		d = self.as_dict(breadthfirst)
            		return namedtuple('ViewObjects', d.keys())(**d)
            		
            class MyClass(ui.View):
            	def __init__(self, *args, **kwargs):
            		super().__init__(*args, **kwargs)	
            		
            		self.make_view()
            		
            	def make_view(self):
            		# just add some btns to the view as a test
            		for i in range(1, 11):
            			btn = ui.Button(name = 'btn{}'.format(i))
            			self.add_subview(btn)
            			lb = ui.Label(name = 'lbl{}'.format(i))
            			btn.add_subview(lb)
            		
            			
            mc = MyClass(name = 'CustomView')
            for sv in ViewWalker(mc, False):
            	print(sv)
            
            # get a dict, string subscripts :(
            d = ViewWalker(mc).as_dict()
            print(d['btn1'].name)
            print(d['btn1'])
            
            # get a nampledtuple, now can use field names
            vo = ViewWalker(mc).as_namedtuple()
            print(vo.btn1.name)
            print(vo.btn1)
            
            1 Reply Last reply Reply Quote 0
            • ccc
              ccc last edited by ccc

              Two cool standard library packages are dis and timeit. Based on our discussion on GitHub, I used them to check out the size in Python byte codes and performance of two implementations of apply_kwargs(). dis.dis() tells us that one implementation results in 63 bytes of bytescodes and the other 62 -- neck and neck. However, timeit.timeit() shows us that there is a performance difference between the two. Not vital in this case but still it is cool to see how these two modules allow us to explore our code...

              import dis, timeit
              
              def apply_kwargs_0(obj, **kwargs):
                  for k, v in kwargs.items():
                      if hasattr(obj, k):
                          setattr(obj, k, v)
              
              print(dis.dis(apply_kwargs_0))
              
              print('=====')
              
              def apply_kwargs_1(obj, **kwargs):
                  for key in set(vars(obj)) & set(kwargs):
                      setattr(obj, key, kwargs[key])
              
              print(dis.dis(apply_kwargs_1))
              
              class MyClass(object):
                  def __init__(self, a=0, b=1, c=2):
                      self.a = a
                      self.b = b
                      self.c = c
              
              obj = MyClass()
              print(vars(obj))  # {'c': 2, 'b': 1, 'a': 0}
              apply_kwargs_0(obj, b=7, c=8, d=9)
              print(vars(obj))  # {'c': 8, 'b': 7, 'a': 0}
              
              obj = MyClass()
              print(vars(obj))  # {'c': 2, 'b': 1, 'a': 0}
              apply_kwargs_1(obj, b=7, c=8, d=9)
              print(vars(obj))  # {'c': 8, 'b': 7, 'a': 0}
              
              print(timeit.timeit('apply_kwargs_0(obj, b=7, c=8, d=9)',
                                  globals=globals(), setup='obj = MyClass()'))
              
              print(timeit.timeit('apply_kwargs_1(obj, b=7, c=8, d=9)',
                                  globals=globals(), setup='obj = MyClass()'))
              
              
              Phuket2 2 Replies Last reply Reply Quote 1
              • Phuket2
                Phuket2 @ccc last edited by

                @ccc , this is nice. I am surprised about the results though. Which is the whole point of profiling I guess.
                Your suggested version apply_kwargs_1, is approx 25% faster on my iPad Pro using py3 beta. I have used sets with kwargs before, but obviously I should keep them more in mind when trying to solve problems 😱

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

                  @ccc , maybe I am just being stupid. But vars does not seem to work on Live ui objects. Ie vars(ui.View()) , but works on types eg vars(ui.View)

                  I am just doing something stupid or is it because of c? Seems strange that ui.View as a live object does not work. It would make more sense if that worked and ui.Button didn't.

                  For a while I thought I was definitely wrong, then when I looked closer at your example, i realised your classes are based on object. Just so used to MyClass using ui.View so I didn't notice it.

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