2

I want to add a method to an object during runtime as thus.

class C(object):
  def __init__(self, value)
    self.value = value

obj = C('test')

def f(self):
  print self.value

setattr(obj, 'f', f)
obj.f()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (0 given)

But it seems that setattr does not bind the method to the object. Is it possible to do this?

felipsmartins
  • 13,269
  • 4
  • 48
  • 56
Irvan
  • 439
  • 4
  • 19
  • Also, I wrote a blog post about this topic in python: https://creatism.wordpress.com/2015/10/26/creating-function-and-class-objects-in-python/ – Josh Weinstein Nov 27 '15 at 05:44

3 Answers3

5

You can use MethodType from types module:

import types

obj.f = types.MethodType(f, obj)    
obj.f()

But are you really need this? Look for decorators (for example), it's more elegant way to add needed functionality to class.

Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • I'm designing a factory function that generates objects based on what kind of parameter is supplied. So, if I want to define a horse, I just call AnimalFactory(properties=[HasFourLegs, CanRun]). I forgot what this paradigm is called. – Irvan Nov 27 '15 at 06:18
1

There is way, that I discovered, using the locals() binding space and the exec() command. First, you have to create a function object from the exec() builtin and extract it:

def make_function(name, parameter, commands):
    func = 'def {name}({parameter}):'.format(name=name,     parameter=parameter)
    for line in commands:
        func += '\n\t' + line
    exec(func)
    return locals()[name]

>>> func = make_function('help', 'pay', ['pay+=2', 'pay*=5', 'credit = pay//3', 'return credit'])
>>> func(8)
16

Then, you need another function that creates an instance object using the dict attribute. The dict of an object is like it's internal dictionary of attributes that can be accessed via the . commands, it makes a python object similar to a javascript object.

def create_object(names, values):
    assert len(names) == len(values)
    exec('class one: pass')
    obj = locals()['one']()
    for i in range(len(names)):
       obj.__dict__[names[i]] = values[i]
    return obj

>>> func = make_function('help', 'pay', ['pay+=2', 'pay*=5', 'credit = pay//3', 'return credit'])
>>> test_obj = create_object(['help', 'interest', 'message'], [func, 0.5, 'please pay by thursday.'])
>>> test_obj.help(7)
15
>>> test_obj.interest
0.5
>>> test_obj.message
'please pay by thursday.'

This function essentially zips to separate lists together, to create a set of attributes for your instance object.

Josh Weinstein
  • 2,788
  • 2
  • 21
  • 38
  • using locals() looks like an overkill though... I guess there really is no simple way such as setattr(obj, 'f', classmethod(f)) (does not work for instantiated object, works for classes). – Irvan Nov 27 '15 at 05:46
  • 1
    you could try using obj.__dict__['f'] = classmethod(f) – Josh Weinstein Nov 27 '15 at 05:48
1

In fact, seattr will not to pass self/object to method being attached.
So what you could to do is something like this:

class C(object):
    def __init__(self, value):
        self.value = value

obj = C('some thing')


def make_method(_object, *args, **kwargs):
    def f(self=_object):
        print self.value
    return f

setattr(obj, 'f', make_method(obj))
obj.f()

It works because f() has access to make_method() scope, as it is a inner/nested/closure function.

felipsmartins
  • 13,269
  • 4
  • 48
  • 56