3

I am trying to assign a function to another function the left hand side of the assignment is available to me as a String. For example the body of the method I am looking for is

def change_function_defintion(name_of_function = 'module1.function1'
       , function_object):
    # Can I do eval(name_of_function) = function_object ? will that work?
    pass

Questions:

  1. How do I achieve this? Obviously if I call the above method and then call the module.function1 I expect the new function to be picked up.
  2. I am doing this in the context of unit testing i.e, Mock several functions, run the test and then basically "unmock" them. Are there any problems with the said approach?
kiriloff
  • 25,609
  • 37
  • 148
  • 229
Kannan Ekanath
  • 16,759
  • 22
  • 75
  • 101
  • 1
    possible duplicate of [How to dynamically load a Python class](http://stackoverflow.com/questions/547829/how-to-dynamically-load-a-python-class) – Martijn Pieters May 09 '13 at 10:20
  • 2
    Like classes, functions are first-class objects. You can dynamically import the module, then retrieve the function as an attribute. – Martijn Pieters May 09 '13 at 10:22
  • Please note this is definitely different from the "How to dynamically load a Python class" question. I have explicitly asked for any improvements/problems with the eval approach and quite rightly I have been pointed to the MOCK framework of python which is included by default in python 3.3 – Kannan Ekanath May 09 '13 at 11:15
  • There isn't anything special about mocking. The problem you were facing is one of importing and referencing an object (a function in this case, but that is no different from referencing a class) by a string specifying the import path. The other question answers that specific problem for you. The `mock` library does the exact same thing (see the [source code](https://code.google.com/p/mock/source/browse/mock.py#1114)) in that regard. – Martijn Pieters May 09 '13 at 11:19

6 Answers6

5

I think it would be better to use a mocking library like Mock. Using patch you can change the function's behaviour within the scope of a context manager or a function and have it change back to normal afterwards. For example:

from mock import patch

with patch('module1.function1') as function1:
    function1.side_effect = function_object
    # Do stuff

if function1 is called inside the with block it will be replaced with function_object.

Similarly, patching within a function:

@patch('module1.function1')
def my_test(function1):
    function1.side_effect = function_object
    # Do stuff
Matti John
  • 19,329
  • 7
  • 41
  • 39
1

My approach:

import importlib

def change_function_defintion(name_of_function = 'module1.function1'
   , function_object):
   my_mod = importlib.import_module('module1')
   setattr(my_mod, function1, function_object)

Now the longer rant:

That approach will probably work, if module1 is already imported in the local namespace, for instance, you can do something like:

>>> a = eval('str')
>>> a
<type 'str'>
>>> a(123)
'123'

In the context of mocking for unit tests, there might be a better way of doing so.

You can check here: http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy#MockTestingTools for some libraries that will allow you to have more control around mocking objects in your unit tests.

EDIT:

You can do something like this, to dynamically import modules:

>>> import importlib
>>> my_mod = importlib.import_module('mymodule1')

Then, you can access the available functions inside the module, or get them via eval/getattr:

my_function = getattr(my_mod,'somefunction')

Of, if you want to swap that function to something else:

my_mod.functionName = newFunction
pcalcao
  • 15,789
  • 1
  • 44
  • 64
1

There are some problems with mocking and you might consider a different testing approach if possible:

Jean-Paul Calderone
  • 47,755
  • 6
  • 94
  • 122
1

I think the following will do what you want (but you might want more robust parsing):

def set_named_fun(funname, fun) :
    import sys
    modname, funname =  funname.rsplit('.')
    mod = sys.modules[modname]
    setattr(mod, funname, fun)

The assumptions here are:

  • the module owning the function is already imported, and
  • you need to refer to the target function as a fully qualified name

The two assumptions are slightly in tension. There must be a great many cases where you can simply do:

import legend
legend.monkey = lambda : "great sage, equal of heaven"
Adrian Ratnapala
  • 5,485
  • 2
  • 29
  • 39
1

Python function decorator

First, the notion you are talking about is the notion of function decorator. A function decorator is applied to a function definition by placing it on the line before that function definition begins (symbol @). It is a tool to modify the behavior of a function, or do operate composition of functions. Here is an example

class entryExit(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print "Entering", self.f.__name__
        self.f()
        print "Exited", self.f.__name__

@entryExit # decorator
def func1(): # decorated function
    print "inside func1()"

@entryExit
def func2():
    print "inside func2()"

I i run

func1()
func2()

i get

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2

Python unittest.mock.patch()

patch acts as a function decorator, class decorator or a context manager. Inside the body of the function or with statement, the target is patched with a new object. When the function/with statement exits the patch is undone.

Patch lets you modify the function behavior within the with statement.

Here is an example where patch() is used as a context manager with a with statement.

>>> with patch.object(ProductionClass, 'method', return_value=None) 
  as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)
kiriloff
  • 25,609
  • 37
  • 148
  • 229
0

Can use "getattr" to get the function using the string name of the function (A function is an object). Then you can change the name and call / call something else (original named function) in the new named call.

pranshus
  • 187
  • 6