Mtl Dev
Mtl Dev

Reputation: 1622

How to programmatically create object methods?

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

Answers (2)

Serge Ballesta
Serge Ballesta

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

Kevin
Kevin

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

Related Questions