Forum Archive

Getting the parent of a dynamically method as a function

Phuket2

Sorry, this is more likely a Python question than it is a Pythonista question. But I have searched else where and can not find the answer. It also could be particular to Pythonista, not sure.

What I would like to do is , add a function to a ui Element such as a ui.Button. Then from inside that function at runtime determine the calling classes instance. so self for short.

In the sample code below, I know, I could pass the instance object as a param. Just not ideal....
I think the answer must be in the inspect module. I have tried with stack(), current(), outerframes(), I just can't seem to figure it out.
If you do have a solution to share that would be great, but could you please also comment on how robust you think the solution is.

import ui

def test_func(msg):
    '''
    i would like to get a reference to the caller here
    being the btn. 
    i am pretty sure the answer lies with the inspect module,
    but i cant figure it out.
    MAYBE, its even easier than that... well that would be great
    '''
    print msg


btn = ui.Button()
btn.msg = test_func
btn.msg('I am on to something...')

dgm

I would like to try and help if I can. I'm not quite sure exactly what your trying to do..

Are you using a child of button such that the button has a button.msg attribute?

When you declare btn.msg = func(),
Then declare btn.msg = msg,
I believe it acts as if button.msg = func() never happened.. The second reference replaces the first.

I may be wrong but I will try playing around to see what I can come up with.

dgm

I couldn't read it while typing but I see now there isn't an = in the second one. Oops.

Oscar

@Phuket2, I don't know why you want to do this, I'm not sure I even know what you want to do.

Perhaps something like this?

import ui

def extra_function(self, message):
  print 'My class: ', self.__class__
  print message

b = ui.Button()
b.extra_function = lambda *a, **kw: extra_function(b, *a, **kw)

b.extra_function('The message')
Olaf

@Phuket2, is my understanding correct that originally your intent was to modify the class, only to get can't set attributes of built-in/extension type '_ui.Button'?
Going one way, i.e. to make a derived class, is a dead end, yielding Error when calling the metaclass bases, type '_ui.Button' is not an acceptable base type. Going the other way, modifying a class object, helps as your example shows.
However, I don't think an attribute can find out the object it belongs to, as you try to achieve. Though inspect can find the caller, that is the calling function, i.e. one "up" the stack, __main__ in your example, not the "calling" (actually containing) object.
Sorry I can't be of more help, but I think this isn't feasible along this route.

Olaf

On 2nd thought, if the reason you want your function to know the button is to be "self-aware" when being pressed, you may want to look to method action, which gets a singlesender argument doing just that

Phuket2

Sorry guys, appears I was not clear. But the code I posted works. But in the test_func, I want to be able to access the object that called the function.
I can pass a direct reference as below... But this is not good..


import ui

def test_func(obj, msg):
    '''
    i would like to get a reference to the caller here
    being the btn. 
    i am pretty sure the answer lies with the inspect module,
    but i cant figure it out.
    MAYBE, its even easier than that... well that would be great
    '''
    print msg
    print obj.title

btn = ui.Button(title = 'help')
btn.msg = test_func
btn.msg(btn, 'I am on to something...')

So in test_func, I would like to get access to ui object that called it.

Phuket2

@Olaf , not about actions. With this mechanism can extend ui elements eg ui.button , ui.TextField etc with your own methods/function dynamically. It can still be done, but if you need to access the object your are called from you are up the creek unless you explicitly pass the obj each time.

Phuket2

@Olaf , are you on 1.6 or 1.5? Both code samples I posted in this thread work.

Oscar

@Phuket2 Did you see my reply above? Isn't that exactly what you are asking for? Why not? You have the reference to the button in the method without giving it as an argument every time you call it.

Phuket2

@Oscar , YES.....Exactly what I wanted. Sorry the syntax is not very clear to me. lambda etc... it Threw me off. But I did a few tests with your solution and worked perfectly. Late here now. But I will implement tomorrow. I am adding these funcs at runtime based on dicts. Not that difficult, I just want to get my head around your code. Easy for you, not for me 😱😬
But that little bit of code is powerful and opens up a lot in my mind.
One question, as stupid as it maybe, is it possible to express the same without the use of the Lambda?

mikael

Other ways to bind a new method to an object instance (class not affected):

  1. Using types, really binding:
import types
btn.msg = types.MethodType(test_func, btn)
  1. Using functools, just add the object to be always included as the first argument (like the lambda):
from functools import partial
btn.msg = partial(test_func, btn)
Oscar

@mikael beat me to it.. I was going to suggest partial if you prefer to not use lambda: https://docs.python.org/2/library/functools.html#functools.partial

Phuket2

Thanks everyone here for all the excellent feedback and help. Will have a go with types now. Looks straight fwd enough

Phuket2

@mikael , I was stumped there for a few mins because I am adding the extra functions from a dict. Where the k is method name and v is function/method (address).

But I did the below. It works. But not really understanding this well enough, just wanted to ask as you guys if this is seen as an acceptable way call it. As I say, it works great. Just want to be sure I haven't overlooked something

Thanks

    for k,v in _extended_methods.iteritems():
        setattr(ctl, k, types.MethodType(v, ctl))
        #ctl. = types.MethodType(v, ctl)

mikael

Coming back to this...

Here is how I add functionality to built-in ui classes these days, using ProxyTypes.

import ui
from proxy import ObjectWrapper

class ButtonWrapper(ObjectWrapper):
    def __init__(self, obj):
        ObjectWrapper.__init__(self, obj)
    def msg(self, leader):
        print leader + ': ' + self.name

button = ButtonWrapper(ui.Button(name = 'Wrapped ui.Button'))

button.msg('Calling extra method')
Phuket2

@mikael said:

Coming back to this...

Here is how I add functionality to built-in ui classes these days, using ProxyTypes.

import ui
from proxy import ObjectWrapper

class ButtonWrapper(ObjectWrapper):
def init(self, obj):
ObjectWrapper.init(self, obj)
def msg(self, leader):
print leader + ': ' + self.name

button = ButtonWrapper(ui.Button(name = 'Wrapped ui.Button'))

button.msg('Calling extra method')

@mikael, sorry just revisiting this. But is above complete? I just mean button is no longer considered a ui.View.
Sorry, I have a idea, I am just totally missing the point here

mikael

I am heavily utilizing this in my current project, so I can confirm that it works. The only gotcha I have found is that you have to give proxy_view.__subject__ instead of just the proxy_view to add_subview since, as you say, proxy_view is not descended from View. And likewise for ObjCInstance, if you are using objc_util.

mikael

@Phuket2, that said, I think I understand what you are aiming for in your recent discussions, and agree that we are not there yet. Let me get back to this.

Phuket2

@mikael , ok. Sorry been a while since I looked at this. I just thought there was some magic happening that I was not getting

mikael

So, I think that one of the things we want is the ability to style UI components with inheritance. This is relatively straightforward and clean with the proxy approach:

# coding: utf-8
import ui
from proxy import ObjectWrapper

class DefaultStyle(ObjectWrapper):
    def __init__(self, obj):
        ObjectWrapper.__init__(self, obj)
        self.background_color = '#fff7ee'
        self.tint_color = 'black'

class HighlightStyle(DefaultStyle):
    def __init__(self, obj):
        DefaultStyle.__init__(self, obj)
        self.tint_color = 'red'

button = HighlightStyle(ui.Button())
button.title = 'Styled button'
button.present()
Phuket2

@mikael , hmmm. I am having a bad day today. Nothing is working 😱
You can see below I have to
from peak.util.proxies import ObjectWrapper
I can't
from proxy import ObjectWrapper

But I am getting a no module named 'pkg_resources' error. Are you on v2 beta?
Could be something with the beta

# coding: utf-8
import ui
#from proxy import ObjectWrapper

from peak.util.proxies import ObjectWrapper

class DefaultStyle(ObjectWrapper):
    def __init__(self, obj):
        ObjectWrapper.__init__(self, obj)
        self.background_color = '#fff7ee'
        self.tint_color = 'black'

class HighlightStyle(DefaultStyle):
    def __init__(self, obj):
        DefaultStyle.__init__(self, obj)
        self.tint_color = 'red'

button = HighlightStyle(ui.Button())
button.title = 'Styled button'
button.present()
mikael

Sorry, I have the proxies code in a different place, but it is the same code. And yes, I am on v2 beta.

Phuket2

@mikael said:

Sorry, I have the proxies code in a different place, but it is the same code. And yes, I am on v2 beta.

If you get time could you please show me the dir structure you have, I have tried modifying it, I just get the same error. I just tried moving the proxies.py up one level. As the init files in both dirs contain the same statement
import('pkg_resources').declare_namespace(name)
But still same error.

mikael

I have proxies.py in the same directory, it does not need anything else. Could of course also be in site packages.

I guess this is partly why it would be so nice to have it "built in".

Phuket2

@mikael , mine is in site-packages. I installed it with stash. It's ok, I don't know what's going on. I will look into it. As I say, nothing is going smoothly today 😜
Have completely shut down my ipad. Didn't help

mikael

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())
mikael

@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?

Phuket2

@mikael , just let you know, I got it working again. For some reason my version of ProxyTypes was corrupt 😂

Phuket2

@mikael

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.

mikael

@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).

mikael

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.

Phuket2

@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')
mikael

I think the button has to have a size before it is visible, not just location. E.g. with size_to_fit().

Phuket2

@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

mikael

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
Phuket2

@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

mikael

@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
mikael

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)
Phuket2

@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')
Phuket2

@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')
TutorialDoctor

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.

mikael

@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.

mikael

@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".

Phuket2

@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.

Phuket2

@TutorialDoctor , yes this thread has been around the would and back.

mikael

@Phuket2, here's the latest. I went a bit further with the meta stuff and created an Extender base class for functionality wrappers.

import types

class Extender(object):
    def __new__(cls, target_instance, *args, **kwargs):
        if isinstance(cls.__init__, types.MethodType):
            cls.__init__.__func__(target_instance, *args, **kwargs)
        extender_instance = super(Extender, cls).__new__(cls)
        for key in dir(extender_instance):
            if key.startswith('__'): continue
            value = getattr(extender_instance, key)
            if callable(value):
                setattr(target_instance, key, types.MethodType(value.__func__, target_instance))
            else:
                setattr(target_instance, key, value)
        return target_instance
mikael

Using the Extender base, an attempt at apples-to-apples comparison code, matching the code you shared. ButtonFactory is perhaps not exactly how I would create it in isolation, but it acts as a useful test case for passing arguments through the custom new.

# coding: utf-8
import ui
from extend import Extender

def MyMother(sender):
    print 'I love my Mums cooking'

class DefaultStyle(Extender):
    border_width = .5
    corner_radius = 3
    background_color = 'teal'
    tint_color = 'white'

class ButtonFactory(Extender):  
    def __init__(self, parent = None, position = (0,0), **kwargs):
        if parent: parent.add_subview(self)
        self.size_to_fit()
        (self.x, self.y) = position
        self.width += 10
        self.action = MyMother
        for key, value in kwargs.iteritems():
            setattr(self, key, value)


view = ui.View(frame = (0,0,500,500))

button = ButtonFactory(
    DefaultStyle(
        ui.Button(title = 'What do I like?')),
    parent = view, tint_color = 'yellow')

view.present('sheet')
mikael

This version of the extension functionality now meets mostnof my design goals:

  • Use standard class syntax for encapsulating data and functionality - in my case, much preferred to the dict syntax
  • Can extend ui component functionality, "almost subclassing"
  • self always refers to the instance being extended, also in __init__
  • Constructor returns the instance being extended instead of the extender, which allows for chaining extenders
  • Since the original UI class instance is returned, can be used in add_subclass and ObjCInstance

These are still a fail:
* Extra __func__ is needed when calling overloaded methods from superclass, most often done in __init__:

`SuperClass.__init__.__func__(self, params)`
Phuket2

@mikael , I didn't get a chance to run your yet. I have been trying to get another thing working.but during the course of getting the other thing working I wanted a copy ui element function. I have only tried the below with a button. I am sure , there are some things in textfields etc that cause exceptions. That's why I made the exclude list. Maybe it's not great, but it's a idea anyway. Many times I just want a copy of a object with a few changes , normally just X and y and maybe title. But copy and deepcopy fail. I haven't tried them for a while actually. Of course that would be better as part of the solution.

def copy_obj(obj , **kwargs):
    new_obj = type(obj)()
    # copy the attrs from the passed object to the new object

    attr_exclude_list = ['left_button_items', 'right_button_items', 'navigation_view', 'on_screen', 'subviews', 'superview']

    # is removed in the list comp because is callable. we add it back :)
    attr_include_list = ['action']

    intrested_attrs = [k for k in dir(obj) if not k.startswith('_') and not callable(getattr(obj, k)) and k not in attr_exclude_list ] + attr_include_list

    for k in intrested_attrs:
        if hasattr(new_obj, k):
            print k, getattr(obj, k)
            setattr(new_obj, k, getattr(obj, k))

    # overwite new attrs in the new object ,passed in **kwargs
    for k, v in kwargs.iteritems():
        if hasattr(new_obj, k):
            setattr(new_obj, k, v)

    return new_obj
Phuket2

@mikael , the copy_obj should be more complete now. There is a test function also. But trying this on ui.NavigationView or ui.ButtonItem ends in tears 😱

But there is a list of elements it can get through. Also a quirky thing about unassigned attrs. Have to check for them. If not already assigned in the source object to copy, some fail. As source attr has a value of None, but the target is expecting a different type. I haven't really handled this well at the moment. I have done something.

Anyway work in progress, the print statements are still in

import warnings

def copy_obj(obj , **kwargs):
    # an idea for ui object copying...

    # a list of object types we accept
    ui_can_copy = [ui.View, ui.Button, ui.Label, ui.TextField, ui.TextView, ui.TableView, ui.ImageView, ui.SegmentedControl, ui.ScrollView]

    if type(obj) not in ui_can_copy:
        warnings.warn('Can not copy object {}'.format(type(obj)))
        return

    new_obj = type(obj)()
    # copy the attrs from the passed object to the new object


    attr_exclude_list = ['left_button_items', 'right_button_items', 'navigation_view', 'on_screen', 'subviews', 'superview', 'content_offset', 'content_size']

    # is removed in the list comp because is callable. we add it back :)
    attr_include_list = ['action']

    intrested_attrs = [k for k in dir(obj) if not k.startswith('_') and not callable(getattr(obj, k)) and k not in attr_exclude_list ] + attr_include_list

    for k in intrested_attrs:
        if hasattr(new_obj, k):
            print k, type(getattr(obj, k))
            attr = getattr(obj, k)
            '''
                This is an important test. if we try to copy a string attr
                that has nit been previously set, it will fail if we do
                a blind copy/assignment
            '''
            if attr:
                setattr(new_obj, k, attr)

    # overwite new attrs in the new object ,passed in **kwargs
    for k, v in kwargs.iteritems():
        if hasattr(new_obj, k):
            setattr(new_obj, k, v)

    return new_obj

def test_copy_obj():
    # notice there us No navigation_view also no ui.ButtonItem
    ui_can_copy = [ui.View, ui.Button, ui.Label, ui.TextField, ui.TextView, ui.TableView, ui.ImageView, ui.SegmentedControl, ui.ScrollView]

    for obj in ui_can_copy:
        o = obj()
        print type(o)
        x = copy_obj(o)
mikael

@Phuket2, you can give getattr a value to be used if the attribute does not exist.

You again have an interesting problem at hand, although I am not sure what you need this for.

I looked at the ui module, and it does have code to instantiate ui objects from the JSON provided by the ui editor. Now if @omz just had some code in his back pocket to generate matching JSON from ui instances, your copy thing would be done, and we would have a method for serializing ui components. Still not sure what we need it for, though.

Phuket2

@mikael , yes I understand you can get define a default return for querying a attr. But it just needs more than what I have done at the moment. Need to know the type of what is expected, which is not that difficult. But requires even more knowledge of what attr you are dealing with. For example you can see that the attr requires a tuple. Well that's wide open.

But you mention why I/we need it. Imagine in the ui.designer you couldn't copy and paste elements. In my mind it's the same thing. In the ui.designer you can also copy and paste attributes. Why should we not have this in code? I just think we should have.

Look I know I have some strange ideas. But in all honesty, I can't see why in a perfect world I can't just copy a object in full and or its attributes as you would in a gui. Especially if the copy is done very low level. Then I change a few attrs of the very fast memory copied object.

Just my 2 cents worth.

mikael

@Phuket2, crazy ideas push the envelope and occasionally advance the state of the art, so I am all for them. And I can understand the analogy to UI designer.

Copying plain Python objects is just that easy, but unfortunately these ones have an ObjectC monster behind the friendly facade. And copying UIKit instances even in ObjectC is not totally trivial, see this link.

mikael

Fixed a simple bug, now self.methods can be called in __init__.

import types

class Extender(object):
    def __new__(extender_subclass, target_instance, *args, **kwargs):
        extender_instance = super(Extender, extender_subclass).__new__(extender_subclass)
        for key in dir(extender_instance):
            if key.startswith('__'): continue
            value = getattr(extender_instance, key)
            if callable(value):
                setattr(target_instance, key, types.MethodType(value.__func__, target_instance))
            else:
                setattr(target_instance, key, value)
        if isinstance(extender_subclass.__init__, types.MethodType):
            extender_subclass.__init__.__func__(target_instance, *args, **kwargs)
        return target_instance
Phuket2

@mikael , i am struggling to get my head around your extender concept. I don't mean the way you have written it. Just a bit fancy for me 😱

If I want to add a new class to chain it so to speak, how would I do that? I tried a few things but failed.

mikael

@Phuket2, here's a quick, silly and familiar-looking example demonstrating both the subclass inheritance chain and chaining extenders around a button instance. If this is of no use, please share some problematic code.

import ui
from extend import Extender

class DefaultStyle(Extender):
    background_color = 'teal'
    tint_color = 'white'

class BorderedStyle(DefaultStyle):
    border_width = .5
    corner_radius = 3

class ButtonAction(Extender):   
    def __init__(self, msg = None):
        self.msg = msg
    def action(self, sender):
        print self.msg

button = BorderedStyle(ButtonAction(ui.Button(title = 'Click me'), 'Clicked'))

button.present()
Phuket2

@mikael , ok. You mentioned chained before. These are nested Calls right? I am not being smart, I am just not sure of the terminology used in this context.
But even is not that practical, I would hate to nest say 5 calls
Edited

mikael

@Phuket2, I see what you mean. This version does not chain as much as it nests, that's true. Let me see if I can do something about that.

mikael

Here's one way to chain, specifically, if you do not mind "polluting" your ui components with an extra method, named e here for brevity.

# coding: utf-8
import ui
from extend import Extender

class ChainingExtender(Extender):
    def e(self, cls, *args, **kwargs):
        return cls(self, *args, **kwargs)

class DefaultStyle(ChainingExtender):
    background_color = 'teal'
    tint_color = 'white'

class BorderedStyle(DefaultStyle):
    def __init__(self, border_width):
        self.border_width = border_width
        self.border_color = 'white'

class ButtonAction(ChainingExtender):   
    def __init__(self, new_title = 'Not defined'):
        self.msg = new_title
    def action(self, sender):
        self.title = self.msg

button = ButtonAction(ui.Button(title = 'Click me'), 'Clicked').e(BorderedStyle, 10)

button.present()
Phuket2

@mikael , 0k I will have to look tomorrow. 1:47am here now 😱 With many black label and sodas 🎉
But I hope you don't mind, my idea is to keep pushing your idea until it breaks. When it can not be broken, not just by me , but then it should be great.