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