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
-
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 )])
-
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)
-
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}
-
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)
-
Two cool standard library packages are
dis
andtimeit
. Based on our discussion on GitHub, I used them to check out the size in Python byte codes and performance of two implementations ofapply_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()'))
-
@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 😱 -
@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.