Recursively getting all objects from all subviews
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...'
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.
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')
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
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
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_subviewto 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
hasattras 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
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 )]
@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.
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)
You might want to consider using
isinstance(x, y)instead of
type(x) == yto 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
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_objectsbefore calling your function.
note that if you want a list generated one time, simply call
liston your generator:
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.
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 :)