Adding method to a class on the fly

John Henry john106henry at hotmail.com
Sat Jun 23 03:02:09 EDT 2007


On Jun 22, 7:36 pm, Steven D'Aprano
<s... at REMOVE.THIS.cybersource.com.au> wrote:
> On Fri, 22 Jun 2007 14:44:54 -0700, John Henry wrote:
> > The above doesn't exactly do I what need.  I was looking for a way to
> > add method to a class at run time.
>
> > What does work, is to define an entire sub-class at run time.  Like:
>
> > class DummyParent:
> >     def __init__(self):
> >         return
>
> >     def method_static(self, text):
> >         print text
> >         return
>
> > text = "class Dummy(DummyParent):"
> > text += "\n\t" + "def __init(self):"
> > text += "\n\t" + "\tDummyParent.__init__(self)"
> > text += "\n\t" + "def method_dynamic(self):"
> > text += "\n\t" + "\tself.method_static(\"it's me\")"
>
> > exec text
>
> (By the way, you misspelled __init__.)
>
> The correct way to add methods to an instance is with the
> instancemethod() function.
>
> class Parrot:
>     def __init__(self):
>         import new
>         # define a function
>         def method_dynamic(self, *args):
>             args.insert(0, "hello, it's me!")
>             return self.method_static(*args)
>         # convert it into an instance method
>         method = new.instancemethod(function, self, self.__class__)
>         # add it to self
>         self.method_dynamic = method
>     def method_static(self, text):
>         return text
>
> And here is how I use it:
>
> >>> p = Parrot()
> >>> p.method_dynamic()  # call from an instance
> "it's me"
> >>> Parrot.method_dynamic  # does it exist in the class?
>
> Traceback (most recent call last):
>   File "<stdin>", line 1, in ?
> AttributeError: class Parrot has no attribute 'method_dynamic'
>
> BUT, having said all that, are you sure this is what you want to do? This
> is probably a better way to get the same results:
>
> class Parrot:
>     def __init__(self):
>         self.text = "hello, it's me!"
>     def method_dynamic(self):
>         return self.method_static(self.text)
>     def method_static(self, text):
>         return text
>
> Earlier in the thread, you said you wanted a CLASS method, which is very
> different. You can use the classmethod built-in function (no need to
> import new) to create class methods:
>
> class Parrot:
>     def method_dynamic(cls):
>         return cls.method_static(cls(), "hello it's me")
>         # or alternatively cls().method_static("hello it's me")
>     method_dynamic = classmethod(method_dynamic)
>     def method_static(self, text):
>         return text
>
> Note: if you are using recent versions of Python, instead of saying
> "method = classmethod(method)" AFTER the block, you can use a decorator
> before the block.
>
> Making method_dynamic a class method and calling an instance method is not
> a good way of doing things, since the class method has to create a new
> instance before calling method_static, only to throw it away afterwards.
> That is wasteful and could be very expensive.
>
> A better way is to change your class so that method_static is a class
> method too, especially since it doesn't use self:
>
> class Parrot:
>     @classmethod
>     def method_dynamic(cls):
>         return cls.method_static("hello it's me")
>     @classmethod
>     def method_static(cls, text):
>         return text
>
> (Actually, since method_static doesn't even use the class, you could use
> staticmethod instead of classmethod. Remember to remove the "cls" argument.)
>
> Hope this helps,
>
> --
> Steven.


Thanks everybody for your responses.  I know my terminology isn't
quite exact.  Hopefully that didn't confuse you too much.  I used that
example hoping to simplify the question.  As you'll see below, it
takes more if I have to explain the entire story.

Of all the answers, I think the new.instancemethod is most
appropriate.  I'll try to explain:

With a PythonCard application, if you want to have a button, normally
you use the layout editor which creates a dictionary representing the
button, and you would have a function for each of the events it has to
handle.  For instance, a simple one button ap might look like this:

#!/usr/bin/python

"""
__version__ = "$Revision: 1.6 $"
__date__ = "$Date: 2004/08/17 19:46:06 $"
"""

from PythonCard import model

rsrc = {'application':{'type':'Application',
          'name':'Minimal',
    'backgrounds': [
    {'type':'Background',
          'name':'bgMin',
          'title':'Minimal PythonCard Application',
          'size':(200, 100),
         'components': [
    {'type':'Button', 'name':'Button1', 'position':(5, 35),
'label':'Button1'},
] # end components
} # end background
] # end backgrounds
} }

class Minimal(model.Background):

    def on_initialize(self, event):
       pass
    def on_Button1_mouseClick(self, event):
       print "Clicked Button1"


if __name__ == '__main__':
    app = model.Application(Minimal, None, rsrc)
    app.MainLoop()


Notice that the event handler for mouseClick to Button1 is done via
the function on_Button1_mouseClick.  This is very simple and works
great - until you try to create the button on the fly.

Creating the button itself is no problem.  You simply do a:

        self.components['Button1'] = {'type':'Button',
                                     'name':'Button1',
                                     'position':(5, 35),
                                     'label':'Button1'}

But then how do I create the on_Button1_mouseClick function?  With the
application I have to come up with, I have a tree on the left, and
then depending which branch the user clicks, I have to create various
controls on the right.  So, it becomes some what of a nightmare for me
(since I have lots of branches and they are all quite different).
Each time the use click a branch, I have to create the buttons, data
entry-fields, and so forth on the fly - along with all of the
functions to handle them.

This is what I came up with so far (before reading your messages):

#!/usr/bin/python

"""
__version__ = "$Revision: 1.6 $"
__date__ = "$Date: 2004/08/17 19:46:06 $"
"""

from PythonCard import model

rsrc = {'application':{'type':'Application',
          'name':'Minimal',
    'backgrounds': [
    {'type':'Background',
          'name':'bgMin',
          'title':'Minimal PythonCard Application',
          'size':(200, 300),
         'components': [

] # end components
} # end background
] # end backgrounds
} }

class Minimal(model.Background):
    def on_initialize(self, event):
        return

nButtons = 7

text="class MinimalChild(Minimal):"
text += "\n\t" + "def on_initialize(self, event):"
text += "\n\t" + "\tMinimal.on_initialize(self,event)"
for iButton in xrange(nButtons):
	name = "Button"+str(iButton+1)
	text += "\n\t" + "\tself.components['"+name+"'] = {" +\
	    "'type':'Button', " +\
	    "'name':'"+name+"', " +\
	    "'label':'"+name+"', "+\
	    "'position':(5, "+str(35+iButton*30)+")}"
	if iButton!=nButtons-1:
		text += "\n\t" + "def on_"+name+"_mouseClick(self, event):"
exec(text)

if __name__ == '__main__':
    app = model.Application(MinimalChild, None, rsrc)
    app.MainLoop()


With this approach, each time I click a button, a new one gets
created, along with a new handler for mouseClick of that new button.

Now, knowing the new.instancemethod way, may be I can simplify the
above somewhat and improve the efficiencies but I still don't see how
one can do it without using the exec function.

Regards,




More information about the Python-list mailing list