Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Dynamically adding function to a class, whose name is contained in a string

What is the best way to create a new member function of a class with function name contained as string? Also, this new function is merely as pass-through for another object(helper class), which has the same function name but with variable arguments. I use lambda to achieve this, but I don't know how to handle the scenario, where my pass-through wrapper would be more than one-statement (which is my requirement)

# This is a helper class
class Compensation:
   def bonus(self):
       return 10000
   def salary(self):
       # Do something
   def stack(self):
       # Do something

# This is a employer class
class employee:
   def __init__(self):
       self.compensation = Compensation()

# This is a wrapper that creates the function
def passThru(funcName):
    fn = "employee."+funcName+"=" + "lambda self, *arg: self.compensation." + funcName +"(*arg)"
    exec(fn)

fnNames = ["bonus", "salary", "stocks"]
for items in fnNames: passThru(items)

emp = employee()
emp.bonus() # returns 1000
like image 387
rajachan Avatar asked Jan 20 '26 00:01

rajachan


2 Answers

All that trickery with exec gives me a headache ;-) I'm not exactly clear on what you want to do, but adding a new method with a name given by a string is really quite easy. For example,

class employee:
    pass

# Some multiline-function.
def whatever(self, a, b):
    c = a + b
    return c

e = employee()

# Make `whatever` an `employee` method with name "add".
setattr(employee, "add", whatever)
print e.add(2, 9)

Whenever you're reaching for exec, you're probably missing a straightforward way.

EDIT: an oddity here is that if someone tries to display e.add, they'll get a string claiming its name is whatever. If that bothers you, you can add, e.g.,

whatever.__name__ = "add"

Fleshing it out

Is this closer to what you want? Note that @gnibbler suggested much the same, although more telegraphically:

class Compensation:
    def bonus(self, a):
        return 10000 + a
    def salary(self):
        return 20000
    def stack(self, a=2, b=3):
        return a+b

class employee:
    def __init__(self):
        self.comp = Compensation()


e = employee()

for name in "bonus", "salary", "stack":
    def outer(name):
        def f(self, *args, **kw):
            return getattr(self.comp, name)(*args, **kw)
        f.__name__ = name
        return f
    setattr(employee, name, outer(name))

print e.bonus(9)
print e.salary()
print e.stack(b="def", a="abc")

That displays:

10009
20000
abcdef

All that said, you might want to re-think your architecture. It's strained.

like image 78
Tim Peters Avatar answered Jan 22 '26 12:01

Tim Peters


You want setattr. Let's say you have:

>>> inst = Foo(10)
>>> class Foo(object):
    def __init__(self, x):
        self.x = x

>>> inst = Foo(10)
>>> inst2 = Foo(50)

If you want to add a method to all instances of the class, then setattr on the class. This function will end up being an unbound method on the class, becoming bound in each instance, so it will take the self param:

>>> setattr(inst.__class__, "twice_x", lambda self: self.x * 2)
>>> inst.twice_x()
20
>>> inst2.twice_x()
100

If you want to add the function to just one instance of the class, then setattr on the instance itself. This will be a regular function which will not take the implicit self argument:

>>> setattr(inst, "thrice_x", lambda: inst.x * 3)
>>> inst.thrice_x()
30
>>> inst2.thrice_x()

Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    inst2.thrice_x()
AttributeError: 'Foo' object has no attribute 'thrice_x'    
like image 20
Claudiu Avatar answered Jan 22 '26 13:01

Claudiu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!