Denis Kotov
Denis Kotov

Reputation: 882

Python How to create method of class in runtime

I am curious how to create a custom method for a class at runtime...

I mean for example with name of method, name of parameters, body of method read from database and assign this method to a class or to an instance.

I have a found possibility to add method that is already written:

class A:
    def __init__(self):
        pass

def method(self):
    return True

A.method = method
a = A()
print(a.method())

but I am interested in completely assembling a new method from scratch:

name = "method"
params = ["self"] # Params in list should be a strings
body = "return True"
# To create method from pieces

Is it possible using __dict__ ? Or how else this be done?

Upvotes: 0

Views: 1828

Answers (3)

Stephen Rauch
Stephen Rauch

Reputation: 49784

Methods are another attribute on the object that is the class. They can be added like other attributes:

Code:

class A:
    def __init__(self):
        pass

    def method(self):
        return True

def another_method(self):
    return False

setattr(A, 'another_method', another_method)

Test Code:

a = A()
print(a.another_method())

Results:

False

Methods from a string:

Add if you really need to get your methods from a database or such you can use exec like:

method_string = """

def yet_another_method(self):
    return type(self).__name__

"""

exec(method_string)
setattr(A, 'yet_another_method', yet_another_method)

a = A()
print(a.yet_another_method())

Results:

A

Upvotes: 2

Denis Kotov
Denis Kotov

Reputation: 882

One of the best solutions that I have found is the following:

def import_code(code, name, add_to_sys_modules=0):
    """
    Import dynamically generated code as a module. code is the
    object containing the code (a string, a file handle or an
    actual compiled code object, same types as accepted by an
    exec statement). The name is the name to give to the module,
    and the final argument says wheter to add it to sys.modules
    or not. If it is added, a subsequent import statement using
    name will return this module. If it is not added to sys.modules
    import will try to load it in the normal fashion.

    import foo

    is equivalent to

    foofile = open("/path/to/foo.py")
    foo = importCode(foofile,"foo",1)

    Returns a newly generated module.
    """
    import sys,imp

    module = imp.new_module(name)

    exec(code,module.__dict__)
    if add_to_sys_modules:
        sys.modules[name] = module

    return module

class A:
    def __init__(self):
        pass

name = "method"
params = ["self"] # Params in list should be a strings
body = "return True"

scratch = "def {0}({1}):\n\t{2}".format(name, ','.join(params), body)

new_module = import_code(scratch, "test")
A.method = new_module.method
a = A()
print(a.method())

Original function import_code by the following link http://code.activestate.com/recipes/82234-importing-a-dynamically-generated-module/

Using this solution I can dynamically create methods, load them in runtime and link to whatever I want object !!

Upvotes: 0

MSeifert
MSeifert

Reputation: 152637

This answer has to be treated with care, using exec or eval can run arbitary code and may compromise your system. So if you rely on user-input to create the function you mustn't use this!!!


The warning aside you can simply create anything using exec:

exec("""
def method():
    return True
""")

>>> method()
True

So what you basically need is just a way to get your requirements in there:

functionname = 'funfunc'
parameters = ['a', 'b']
body = 'return a + b'

exec("""
def {functionname}({parameters}):
{body}
""".format(
    functionname=functionname,
    parameters=', '.join(parameters),
    body='\n'.join(['    {line}'.format(line=line) for line in body.split('\n')])))

The body will be indented so that it's valid syntax and the parameter list will be joined using ,. And the test:

>>> funfunc(1, 2)
3

Upvotes: 2

Related Questions