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.
Getting the parent of a dynamically method as a function
-
Wrappers also chain nicely, so you might have e.g. styles in a different inheritance hierarchy and extra functionality in other.
The only thing that is not chained is subject, so it probably makes sense inherit from a common base class and include a method that does the chaining, as follows:
# coding: utf-8 import ui from proxy import ObjectWrapper class ChainableWrapper(ObjectWrapper): def __init__(self, obj): ObjectWrapper.__init__(self, obj) # Follow the chain of __subject__'s, if needed def get_subject(self): subject = self.__subject__ while isinstance(subject, ObjectWrapper): subject = subject.__subject__ return subject class DefaultStyle(ChainableWrapper): def __init__(self, obj): super(DefaultStyle, self).__init__(obj) self.background_color = '#fff7ee' self.tint_color = 'black' class HighlightStyle(DefaultStyle): def __init__(self, obj): super(HighlightStyle, self).__init__(obj) self.tint_color = 'red' class ToggleButton(ChainableWrapper): def __init__(self, obj): super(ToggleButton, self).__init__(obj) def extra_functionality(self): pass button = ToggleButton(HighlightStyle(ui.Button())) button.title = 'Styled button' button.present() # Get the wrapped ui.* instance some_other_view.add_subview(button.get_subject())
-
@omz, would there be any chance to have add_subview accept proxies in addition to direct view objects? And have ObjCInstance recognize proxies and use the underlying reference instead?
-
@mikael , just let you know, I got it working again. For some reason my version of ProxyTypes was corrupt 😂
-
I wrote the below on another thread. I think it would help a lot without having to change much. Not sure what you think. Was not sure you seen it or not.
Phuket2 posted a day ago reply quote 0
I am not sure if being able to subclass ui compents is in the wind or will ever be. But I thought of something that may be quite useful, at least in my mind.
The simplest form of my idea is that you could call a ui method that sets a callback function that is called upon creation of any ui component. We could be passed the object type, or worse case check with type.
A little more refined would be able to also set different callbacks for the different types of ui components.That would offer a lot of flexibility and possibilities. Also, especially when testing would be so easy just to style the objects. I know, you can still just have a make button function anyway. But this just seems so much cleaner.
Not sure the impact this would have on performance or threading issues etc. I assume threading type problems would not be an issue as it would only be called on creation.
-
@Phuket2, thanks, I did see it, and read it with interest.
In the end I think I did not yet fully grasp the intended use case and value when compared to the effort:
- Styling objects has not been a major source of pain for me
- It feels like it could actually be a bit of effort for omz to implement in the UI module
- I try to avoid callbacks as long as I can
- Could lead to some really hard-to-debug situations when you take someone else's code and you both have used these callbacks
You could try it out by making a function that you use to wrap every object creation and call the callback(s).
-
I have revisited this topic and this whole thread, and have decided to turn my coat on the proxy thing. Now I think that with a simple utility function, derived from what @Phuket2 presented earlier in the thread, I can have the same clean, inheritance-friendly extension mechanism, which will also play nice with add_subview and ObjCInstance.
In concrete terms, here is the utility function:
import types def extend(this, with_that): items = [(i, getattr(with_that, i)) for i in dir(with_that) if not i.startswith('__')] for k, v in items: if callable(v): setattr(this, k, types.MethodType(v.__func__, this)) else: setattr(this, k, v) return this
With that, we can have just as clean custom component code as with the proxies, with the added benefit of the object still being the original ui component. (Different ways to set style properties used for demonstration.)
# coding: utf-8 import ui class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() self.tint_color = 'red' view = ui.View() button = extend(ui.Button(), HighlightStyle()) button.title = 'Styled button' view.add_subview(button) view.present() button.frame = view.bounds.inset(300, 100)
Note that new-style classes are needed, i.e. must inherit from
object
. -
@mikael , this looks good. I think I am doing something stupid through. I can not get the button to appear in the ui.View
I changed things around a little, just to help me test.I am setting the buttons x,y, attrs in different places. I was just trying to get it to work. When I do a print dir(button) it looks fine. I can see the added attrs I threw in the extend func are there.
I am sure I am missing something very stupid. Over thinking this now.
import types import ui def extend(this, with_that): items = [(i, getattr(with_that, i)) for i in dir(with_that) if not i.startswith('__')] for k, v in items: if callable(v): setattr(this, k, types.MethodType(v.__func__, this)) else: setattr(this, k, v) # add some attrs this.my_center = (0,0) this.xxx = 0 return this class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() #DefaultStyle.__init__(self) self.tint_color = 'red' self.border_width = 1 self.bg_color = 'yellow' self.x = 10 self.y = 10 if __name__ == '__main__': f = (0,0,500,500) v = ui.View(frame = f) v.bg_color = 'white' button = extend(ui.Button(name = 'test'), HighlightStyle()) print dir(button) button.title = 'Styled button' button.x = 10 button.y = 10 button.border_width = .5 v.add_subview(button) print button.superview v.present('sheet')
-
I think the button has to have a size before it is visible, not just location. E.g. with
size_to_fit()
. -
@mikael , thanks. That was it. I wrote a 3 a4 page rant, not to you , but about being frustrated. But I decided to delete it 😱😬
Also not to to @omz. Ok, I will shut up now.
But again thanks. I have to also think about every I was trying to squeeze out of this idea and see if it carries through.One thought, I have never used ... As a param before. But if the -
def extend(this, with_that): Could be written something like
def extend(this, ...):
So you could pass x number of classes to extend to function could be a good idea. Eg, I added some attrs in the extend function, just as a test. But if I had a class MyCustomAttrs
I could possibly write-
def extend(this, with_style, with_attrs) etc...
As I say, I have never used ... As a param. I just assume that's the idea of it. Sure, you still have to do the right thing in the extend function. So you could pass it a list of objects to iterate over. This just seems nicer -
That sounds interesting and certainly feasible. Could you please open up a bit more what you would want to happen?
- Expand several target instances with one "expander"
- Expand one target instance with several "expanders"
- Apply additional attributes to the target instance
- Only apply the listed attributes from the "expander"
- Something else
-
@mikael , honestly, I am not sure yet. But I can see if you do
button = extend(ui.Button(), HighlightStyle())
Then have to do
button = extend(ui.Button(), MyExtraAttrs())Just saying if you can pass in multiple classes , it would be nicer in my mind. Ok, have a precedence issue. But it makes sense that the last class passed would take precedence as it would if you did it line by line.
Again, I am not really sure the best way. I just think a solution like this has to be very compelling to make it worth wild
-
@Phuket2, that sounds like a wothwhile convenience feature. Here is a version that let's you apply several "expanders" with one call:
# coding: utf-8 import types # Extend the instance given as the first argument with the methods and properties of the instances given as the second and subsequent arguments def extend(target, *args): for source in args: for key in dir(source): if key.startswith('__'): continue value = getattr(source, key) if callable(value): setattr(target, key, types.MethodType(value.__func__, target)) else: setattr(target, key, value) return target
-
And sample usage/test case:
# coding: utf-8 import ui from extend import extend class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() self.tint_color = 'red' class ClickHandler(object): def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color view = ui.View() button = extend(ui.Button(), HighlightStyle(), ClickHandler()) button.title = 'Styled button' view.add_subview(button) view.present() button.frame = view.bounds.inset(300, 100)
-
@mikael nice. Yes. I just tested also.
All my idea is that it needs to be very compelling and super useful to make sense that other will use it.
Getting late here now, I will try and push it more tomorrow.import types import ui # Extend the instance given as the first argument with the methods and properties of the instances given as the second and subsequent arguments def extend(target, *args): for source in args: for key in dir(source): if key.startswith('__'): continue value = getattr(source, key) if callable(value): setattr(target, key, types.MethodType(value.__func__, target)) else: setattr(target, key, value) return target class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() #DefaultStyle.__init__(self) self.tint_color = 'red' self.border_width = 1 self.bg_color = 'yellow' self.x = 10 self.y = 10 class MyCustomAttrs(object): def __init__(self): self.xzy = 666 if __name__ == '__main__': f = (0,0,500,500) v = ui.View(frame = f) v.bg_color = 'white' button = extend(ui.Button(name = 'test'), HighlightStyle(), MyCustomAttrs()) print dir(button) button.title = 'Styled button' button.size_to_fit() button.x = 10 button.y = 10 button.border_width = .5 v.add_subview(button) print button.superview v.present('sheet')
-
@mikael , but again for the idea to be relevant it has to compete with something like this. Looks like more code then we have been doing. But when you look at the one off code it's not.
Just saying that's how I am trying to evaluate if it's worth while or not# coding: utf-8 import ui def MyMother(sender): print 'I love my Mums cooking' _default_btn_style = \ { 'border_width' : .5, 'corner_radius' : 3, 'bg_color' : 'teal', 'tint_color' : 'white', 'action' : MyMother, } _btn_style = _default_btn_style _extra_attrs = \ { 'myx' : 0, 'myy' : 0, } def make_button(style = _btn_style, ext_attrs = _extra_attrs , *args, **kwargs): btn = ui.Button() # process the normal atttrs for k,v in kwargs.iteritems(): if hasattr(btn, k): setattr(btn, k, v) # process a style dict for k,v in style.iteritems(): if hasattr(btn, k): setattr(btn, k, v) # process addtional attrs... for k,v in ext_attrs.iteritems(): setattr(btn, k, v) # if kwargs has a parent key, then we add the subview to the parent if kwargs.has_key('parent'): kwargs['parent'].add_subview(btn) btn.size_to_fit() # size to fit is too tight btn.frame = ui.Rect(*btn.bounds).inset(0, -5) return btn if __name__ == '__main__': f = (0,0,500,500) v = ui.View(frame = f) btn = make_button(title = 'What do I Like', parent = v) v.present('sheet')
-
I know this is an older post, but I am wondering why no one recommended using a button action?
def test_func(sender): return sender v = ui.load_view() button = v['button1'] button.action = test_func
Then the only question would be how to pass parameters to a button's action function.
"sender" is the button that called the function. -
@TutorialDoctor, we are not focused on the button, but on reasonably elegant ways to get around the fact that we can not subclass ui components. Button is just being used as a shared sample, so to say.
-
@Phuket2, to exaggerate a bit, it looks like where I have been working towards the most object-oriented, legible and maintainable way to create custom components, emphasizing maybe a low number of complex components, you might have been working on the most flexible and time-saving functional-programming button maker, with a possible further focus on generating relatively many less-complex components easily.
Pretty opposite ends of the spectrum, one could say, but nevertheless this has been very instructive and interesting, and I have learned a lot about Python meta-programming in the process. Thanks!
Out of curiosity, I will try and see what a roughly similar generator would look like in "my style".
-
@mikael , also thanks. I have also enjoyed it and learnt a lot. For me it's about remembering what I have learnt 😳
But I think I am starting to learn something that seems so obvious, but not really. We I guess depending on your experience. But that is to really get clear what you are trying to improve on. I can see I go off on all sort of tangents trying to solve problems that either don't exists, I just keep working on something without looking back and comparing the methods, basically having a bench mark.
I still have some years left in me to learn 😁 So all good.
-
@TutorialDoctor , yes this thread has been around the would and back.