Adding method to a class on the fly

J

John Henry

Hi list,

I have a need to create class methods on the fly. For example, if I
do:

class Dummy:
def __init__(self):
exec '''def method_dynamic(self):\n\treturn
self.method_static("it's me")'''
return

def method_static(self, text):
print text
return

I like that to be the same as:

class Dummy:
def __init__(self):
return

def method_dynamic(self):
return self.method_static("it's me")

def method_static(self, text):
print text
return

so that I can do:

dum=Dummy.method_dynamic()

and see "it's me" printed.

Can that be done?

Thanks,
 
J

John Henry

Found a message on the web that says I need to use setattr to add the
method to the class at run time. But how do I do that?

Regards,
 
A

askel

Hi list,

I have a need to create class methods on the fly. For example, if I
do:

class Dummy:
def __init__(self):
exec '''def method_dynamic(self):\n\treturn
self.method_static("it's me")'''
return

def method_static(self, text):
print text
return

I like that to be the same as:

class Dummy:
def __init__(self):
return

def method_dynamic(self):
return self.method_static("it's me")

def method_static(self, text):
print text
return

so that I can do:

dum=Dummy.method_dynamic()

and see "it's me" printed.

Can that be done?

Thanks,

class Dummy:
def method(self, arg):
print arg

def method2(self, arg):
self.method(arg)

Dummy.method2 = method2
Dummy.method2('Hello, world!')
 
7

7stud

class Dummy:
def method(self, arg):
print arg

def method2(self, arg):
self.method(arg)

Dummy.method2 = method2
Dummy.method2('Hello, world!')

Traceback (most recent call last):
File "test1.py", line 8, in ?
Dummy.method2('Hello, world!')
TypeError: unbound method method2() must be called with Dummy instance
as first argument (got str instance
instead)


I like that to be the same as:

class Dummy:
def __init__(self):
return

def method_dynamic(self):
return self.method_static("it's me")

def method_static(self, text):
print text
return


so that I can do:

dum=Dummy.method_dynamic()

and see "it's me" printed.

When are you able to see that?
 
A

askel

Traceback (most recent call last):
File "test1.py", line 8, in ?
Dummy.method2('Hello, world!')
TypeError: unbound method method2() must be called with Dummy instance
as first argument (got str instance
instead)








When are you able to see that?

sorry, of course last line should be:
Dummy().method2('Hello, world!')
 
A

askel

Traceback (most recent call last):
File "test1.py", line 8, in ?
Dummy.method2('Hello, world!')
TypeError: unbound method method2() must be called with Dummy instance
as first argument (got str instance
instead)








When are you able to see that?

there is no way to call instance method from static one. but in your
case you can make something like:

class Dummy:
@staticmethod
def method(arg):
print arg

def method2(arg):
Dummy.method(arg)

Dummy.method2 = staticmethod(method2)
Dummy.method2('Hello, world!')

- OR -

def method2(cls, arg):
cls.method(arg)

Dummy.method2 = classmethod(method2)
Dummy.method2('Hello, world!')
 
J

John Henry

there is no way to call instance method from static one. but in your
case you can make something like:

class Dummy:
@staticmethod
def method(arg):
print arg

def method2(arg):
Dummy.method(arg)

Dummy.method2 = staticmethod(method2)
Dummy.method2('Hello, world!')

- OR -

def method2(cls, arg):
cls.method(arg)

Dummy.method2 = classmethod(method2)
Dummy.method2('Hello, world!')



Thanks for the response.

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

dum=Dummy().method_dynamic()


Thanks again.
 
A

attn.steven.kuo

(snipped)


The above doesn't exactly do I what need. I was looking for a way to
add method to a class at run time.


I'm not sure what you mean by this. Bind an attribute -- a method --
to class Dummy if and only if an instance of this class is created?


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

dum=Dummy().method_dynamic()

Thanks again.


I tend to avoid exec if possible. Also, you
seem to be a bit inexact with regard to the
term "static".


class Dummy(object):
def __init__(self):
new_method_name = 'method_dynamic'
try:
getattr(Dummy, new_method_name)
except AttributeError:
print "Creating an instance method..."
def newf(self):
"""Something Descriptive Here"""
return self.method_static("it's me")
newf.__name__ = new_method_name
setattr(Dummy, new_method_name, newf)
def method_static(self, text):
"""I hate this name. Do not confuse this with a staticmethod;
what you probably meant was that this is an attribute (a
method)
bound within the class body as opposed to elsewhere"""
print text
return # is this necessary?

d1 = Dummy()
d1.method_dynamic()
d2 = Dummy()
d2.method_dynamic()
print d1.method_dynamic.im_func.__name__
print d1.method_dynamic.im_func.__dict__
print d1.method_dynamic.im_func.__doc__
print d1.method_dynamic.im_func.__module__
print d1.method_dynamic.im_self
 
J

James Stroud

John said:
Hi list,

I have a need to create class methods on the fly. For example, if I
do:

class Dummy:
def __init__(self):
exec '''def method_dynamic(self):\n\treturn
self.method_static("it's me")'''
return

def method_static(self, text):
print text
return

Where is the text for the exec statement coming from? A file? User
input? What you are doing above makes absolutely no sense. You confuse
everyone who attempts to understand what you want to do with the above
because no one in his right mind would do anything like it.
I like that to be the same as:

class Dummy:
def __init__(self):
return

def method_dynamic(self):
return self.method_static("it's me")

def method_static(self, text):
print text
return

Are you looking to fill in text and create new methods for Dummy based
on the text and method_static() such that these will become true
instance methods for instances of Dummy?

def add_dynamic(cls_name, f, name, *args, **kwargs):
cls = globals()[cls_name]
def _f(self):
return getattr(cls, f)(self, *args, **kwargs)
setattr(cls, name, _f)

e.g.

py> class Dummy:
.... def method_static(self, text):
.... print text
.... return
....
py> def add_dynamic(cls_name, f, name, *args, **kwargs):
.... cls = globals()[cls_name]
.... def _f(self):
.... return getattr(cls, f)(self, *args, **kwargs)
.... setattr(cls, name, _f)
....
py> add_dynamic('Dummy', 'method_static', 'method_dynamic', 'abc xyz')
py> Dummy.method_dynamic
<unbound method Dummy._f>
py> d = Dummy()
py> d.method_dynamic()
abc xyz

Note that this is "completely dynamic" in that all arguments to
add_dynamic() are strings. This may or may not be what you want--in such
a case, you will want to study the code to see how it works and fix it
yourself. Note also that methods added to classes after instances are
made will be available to said instances:


py> add_dynamic('Dummy', 'method_static', 'method2_dynamic', 'asdf jkl')
py> d.method2_dynamic()
asdf jkl

so that I can do:

dum=Dummy.method_dynamic()

Here you confuse everyone. This last line is not the same as you
describe in the above example. Here you imply the dynamic creation of a
"static method" (not a "method_static"--don't be confused by the names
you invent as they may already have a meaning to everyone else). Static
methods are different from unbound class methods that are later bound to
instances of a class upon instantiation.

Here is an analagous solution for static methods:


def add_static(cls_name, f, name, *args, **kwargs):
cls = globals()[cls_name]
def _f():
return getattr(cls, f)(*args, **kwargs)
setattr(cls, name, staticmethod(_f))

class Dummy:
@staticmethod
def method_static(text):
print text


e.g.:


py> def add_static(cls_name, f, name, *args, **kwargs):
.... cls = globals()[cls_name]
.... def _f():
.... return getattr(cls, f)(*args, **kwargs)
.... setattr(cls, name, staticmethod(_f))
....
py> class Dummy:
.... @staticmethod
.... def method_static(text):
.... print text
....
py> add_static('Dummy', 'method_static', 'real_static', 'aaa bbb')
py> Dummy.real_static
<function _f at 0x406bf684>
py> Dummy.real_static()
aaa bbb



Again, this will do what you want, but if it doesn't do *exactly* what
you want, you need to study and modify the code. Also, creating static
methods from unbound methods requires trickery. If this is what you
want, you should be very clear about it.

and see "it's me" printed.

Can that be done?


Yes. Anything is possible with python. That's why I use it.


James
 
S

Steven D'Aprano

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:
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,
 
J

John Henry

(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:


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,


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,
 
S

Steven D'Aprano

On Sat, 23 Jun 2007 00:02:09 -0700, John Henry wrote:

[snip]
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?

That depends on what it is supposed to do, but in general you want a
factory function -- a function that returns functions. Here's a simple
example:

def mouseclick_factory(arg):
def on_mouseClick(self, event):
print "You clicked '%s'." % arg
return on_mouseClick

func1 = mouseclick_factory("Button 1")
func2 = mouseclick_factory("this button")
func3 = mouseclick_factory("something")


Now let's try them out, faking the "self" and "event" parameters:

You clicked 'something'.


Obviously in a real application, self and event are important and can't be
faked with None.

Now, there are two ways of using that factory function in a class. Here
is an example of both.

class Parrot:
def __init__(self, arg):
self.bar = mouseclick_factory(arg)
foo = mouseclick_factory("Foo")

p = Parrot("bar")

If you do it like this, there is a slight Gotcha to watch out for: as
provided, foo is an instance method (and so has the self argument
supplied automatically) but bar is not (and so needs the self argument to
be supplied manually.
You clicked 'bar'.

If this is a problem -- and believe me, it will be -- you can use
new.instancemethod to convert bar.


[snip]
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.

Rule 1:
Never use exec.

Exception for experts:
If you know enough to never need exec, you can use it.

Rule 1 is actually not quite true, but as an approximation to the truth,
it is quite good.
 
J

John Henry

That depends on what it is supposed to do, but in general you want a
factory function -- a function that returns functions. Here's a simple
example:
<snip>

Steven,

May be I didn't explain it clearly: the PythonCard package expects to
see a function by the name of on_Button1_mouseClick. I don't do
anything to register the callback function. The package assumes that
there is a function by that name whenever I create a button named
Button1. So, if I don't use exec, how can I create a function by that
exact name?
 
S

Steven D'Aprano

<snip>

Steven,

May be I didn't explain it clearly: the PythonCard package expects to
see a function by the name of on_Button1_mouseClick. I don't do
anything to register the callback function. The package assumes that
there is a function by that name whenever I create a button named
Button1. So, if I don't use exec, how can I create a function by that
exact name?


def mouseclick_factory(name):
def function(self, event):
print "You clicked '%s'." % name
function.name = "on_%s_mouseClick" % name
return function



class Parrot:
def __init__(self, name):
function = mouseclick_factory(name) # as before
method = new.instancemethod(function, self, self.__class__)
setattr(self, function.name, method)


And here it is in action:
You clicked 'Button1'.
 
J

John Henry

def mouseclick_factory(name):
def function(self, event):
print "You clicked '%s'." % name
function.name = "on_%s_mouseClick" % name
return function

class Parrot:
def __init__(self, name):
function = mouseclick_factory(name) # as before
method = new.instancemethod(function, self, self.__class__)
setattr(self, function.name, method)

And here it is in action:


You clicked 'Button1'.


Thank you. I think that should work perfectly. By using
mouseclick_factory, you've avoided using exec and result in a much
more readable code. The part I really didn't know how is the use of
the new.instancemethod followed by setattr. I'll go try it now.

Thanks again.
 
J

John Henry

def mouseclick_factory(name):
def function(self, event):
print "You clicked '%s'." % name
function.name = "on_%s_mouseClick" % name
return function

class Parrot:
def __init__(self, name):
function = mouseclick_factory(name) # as before
method = new.instancemethod(function, self, self.__class__)
setattr(self, function.name, method)

And here it is in action:


You clicked 'Button1'.


Wouldn't it be nice if it works right away? :=)

I tried the above method and this is what I have:

#!/usr/bin/python

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

import new

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
} }

def mouseclick_factory(parent, name):
id_num=int(name[-1:])
parent.components[name] = {'type':'Button',
'name':name,
'label':name,
'position':(5, 5+id_num*30),
'text':name}
def function(self, event):
print "You clicked '%s'." % name
function.name = "on_%s_mouseClick" % name
return function

class Minimal(model.Background):
def on_initialize(self, event):
self.components['field1'] =
{'type':'TextField','name':'field1','position':(5, 5),'size':(150,
-1),'text':'Hello PythonCard'}
name = "Button1"
function = mouseclick_factory(self, name) # as before
method = new.instancemethod(function, self, self.__class__)
setattr(self, function.name, method)

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

When I click on the button, nothing happens. However, if I call the
function directly (like right after the setattr line:

self.on_Button1_mouseClick(event)

it works fine but PythonCard isn't calling this function when I
clicked on the button.
 
S

Steven D'Aprano

it works fine but PythonCard isn't calling this function when I
clicked on the button.


I think you need to take this question onto a PythonCard list. I have no
idea how PythonCard decides which method to call.
 
J

John Henry

I think you need to take this question onto a PythonCard list. I have no
idea how PythonCard decides which method to call.


I did. I am not sure I'll get an answer though.

Thanks for the help.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top