Reputation: 1622
Is it possibly to programmatically create object methods in python?
Due to working with the existing structure of a command line parser, I require an arbitrary number (N) of methods, where (N) is established at runtime.
def activatetool_>N<():
print 'activating tool number: >N<'
I managed to get close with:
class TestClass:
def __init__(self, toolcount):
for i in range(toolcount):
exec('def activatetool_{}(): print \'activating tool {}\''.format(i,i)) in globals()
However this defines global functions, not class methods. By design of the existing code I am working with, I need to be able to call them in the following form:
obj=TestClass(5)
obj.activatetool3()
obj.activatetool1()
Clarification: due to the structure of the existing parser I am working with, solutions that are refactoring to the form of obj.activatetool(N)
are not workable.
Upvotes: 1
Views: 445
Reputation: 149195
A python method is just an attribute of the class which happens to be a function accepting an instance of the class as its first argument. So you can just use setattr
to bind a new method to an existing class.
From your example, you could create a function to add tools:
def addTool(cls, n):
def tool(self):
print ('activating tool number >{}<'.format(n))
setattr(cls, "activatetool{}".format(n), tool)
You can then create a class, an instance of it, add a tool and successfully use the tool:
class TestClass:
pass
t = TestClass()
addTool(TestClass, 3)
t.activatetool3()
you get as expected:
activating tool number >3<
The magic of Python is that as the dynamic method is an attribute of the class, it is accessible to all instances of the class even if they have been created before the method has been added.
Upvotes: 3
Reputation: 76264
Just pass N
as a parameter to activatetool
.
class TestClass:
def activatetool(self, N):
print "activating tool number: {}".format(N)
obv=TestClass()
obv.activatetool(3)
obv.activatetool(1)
Result:
activating tool number: 3
activating tool number: 1
If you're completely dead-set on keeping the digits outside of the parentheses, you can get approximately the behavior you want by overriding __getattr__
:
import re
class TestClass:
def __getattr__(self, name):
m = re.match("activatetool(\d*)$", name)
if m:
N = int(m.group(1))
def activatetoolN():
print("Activating tool number: {}".format(N))
return activatetoolN
else:
raise AttributeError
obv=TestClass()
obv.activatetool3()
obv.activatetool1()
Result:
Activating tool number: 3
Activating tool number: 1
However, obv.activatetool3 will have the type function
, whereas ordinary instance methods have the type instancemethod
. Most of the time, you won't be able to tell the difference, but unusually strict users of your class may notice the discrepancy.
There's also going to be a small-to-moderate performance penalty, since activatetoolN
will be created from scratch each time you access obv.activatetool<N>
.
Upvotes: 1