We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Simplifying the Function interface
The arguments of a function `f` can be listed: `f.func_code.co_varnames[:f.func_code.co_argcount]`.

Currently, I'm decorating functions like so:

``` Python
def nipype_function(*output_names, **kwargs):
    def decorator(func):
        kwargs.setdefault('output_names', output_names or ['output'])
        kwargs.update({
            'function': func,
            'input_names': func.func_code.co_varnames[:func.func_code.co_argcount]})
        fn = util.Function(**kwargs)
        fn.inputs.function_str = u'\n'.join(
            fn.inputs.function_str.split(u'\n')[1:])
        return fn
    return decorator

@nipype_decorator('out_list')
def mergelists(lists):
    return [elem for lst in lists for elem in lst]
```

So two questions:
1. Would something like this decorator be useful to include in nipype?
2. Would it be useful to permit `nipype.interfaces.utility.Function` to derive `input_names` from the function or function string? I would probably also set a default output name. Thus you could generally write:

``` Python
def func(arg1, arg2):
    return arg1 + arg2

fi = Function(function=func)
fi.inputs.arg1 = 2
fi.inputs.arg2 = 3
res = fi.run()
assert res.outputs.out == 5
```

I know I'm kind of throwing a flurry of things at y'all without polishing each first, so thanks for your continued attention.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nipype/interfaces/utility/wrappers.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
3 # vi: set ft=python sts=4 ts=4 sw=4 et:
4 """Various utilities
5 
6     Change directory to provide relative paths for doctests
7     >>> import os
8     >>> filepath = os.path.dirname(os.path.realpath(__file__))
9     >>> datadir = os.path.realpath(os.path.join(filepath,
10     ...                            '../../testing/data'))
11     >>> os.chdir(datadir)
12 
13 
14 """
15 from __future__ import print_function, division, unicode_literals, absolute_import
16 
17 from future import standard_library
18 standard_library.install_aliases()
19 
20 from builtins import str, bytes
21 
22 from nipype import logging
23 from ..base import (traits, DynamicTraitedSpec, Undefined, isdefined, runtime_profile,
24                     BaseInterfaceInputSpec)
25 from ..io import IOBase, add_traits
26 from ...utils.filemanip import filename_to_list
27 from ...utils.misc import getsource, create_function_from_source
28 
29 logger = logging.getLogger('interface')
30 if runtime_profile:
31     try:
32         import psutil
33     except ImportError as exc:
34         logger.info('Unable to import packages needed for runtime profiling. '\
35                     'Turning off runtime profiler. Reason: %s' % exc)
36         runtime_profile = False
37 
38 
39 class FunctionInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):
40     function_str = traits.Str(mandatory=True, desc='code for function')
41 
42 
43 class Function(IOBase):
44     """Runs arbitrary function as an interface
45 
46     Examples
47     --------
48 
49     >>> func = 'def func(arg1, arg2=5): return arg1 + arg2'
50     >>> fi = Function(input_names=['arg1', 'arg2'], output_names=['out'])
51     >>> fi.inputs.function_str = func
52     >>> res = fi.run(arg1=1)
53     >>> res.outputs.out
54     6
55 
56     """
57 
58     input_spec = FunctionInputSpec
59     output_spec = DynamicTraitedSpec
60 
61     def __init__(self, input_names, output_names, function=None, imports=None,
62                  **inputs):
63         """
64 
65         Parameters
66         ----------
67 
68         input_names: single str or list
69             names corresponding to function inputs
70         output_names: single str or list
71             names corresponding to function outputs.
72             has to match the number of outputs
73         function : callable
74             callable python object. must be able to execute in an
75             isolated namespace (possibly in concert with the ``imports``
76             parameter)
77         imports : list of strings
78             list of import statements that allow the function to execute
79             in an otherwise empty namespace
80         """
81 
82         super(Function, self).__init__(**inputs)
83         if function:
84             if hasattr(function, '__call__'):
85                 try:
86                     self.inputs.function_str = getsource(function)
87                 except IOError:
88                     raise Exception('Interface Function does not accept '
89                                     'function objects defined interactively '
90                                     'in a python session')
91             elif isinstance(function, (str, bytes)):
92                 self.inputs.function_str = function
93             else:
94                 raise Exception('Unknown type of function')
95         self.inputs.on_trait_change(self._set_function_string,
96                                     'function_str')
97         self._input_names = filename_to_list(input_names)
98         self._output_names = filename_to_list(output_names)
99         add_traits(self.inputs, [name for name in self._input_names])
100         self.imports = imports
101         self._out = {}
102         for name in self._output_names:
103             self._out[name] = None
104 
105     def _set_function_string(self, obj, name, old, new):
106         if name == 'function_str':
107             if hasattr(new, '__call__'):
108                 function_source = getsource(new)
109             elif isinstance(new, (str, bytes)):
110                 function_source = new
111             self.inputs.trait_set(trait_change_notify=False,
112                                   **{'%s' % name: function_source})
113 
114     def _add_output_traits(self, base):
115         undefined_traits = {}
116         for key in self._output_names:
117             base.add_trait(key, traits.Any)
118             undefined_traits[key] = Undefined
119         base.trait_set(trait_change_notify=False, **undefined_traits)
120         return base
121 
122     def _run_interface(self, runtime):
123         # Get workflow logger for runtime profile error reporting
124         from nipype import logging
125         logger = logging.getLogger('workflow')
126 
127         # Create function handle
128         function_handle = create_function_from_source(self.inputs.function_str,
129                                                       self.imports)
130 
131         # Wrapper for running function handle in multiprocessing.Process
132         # Can catch exceptions and report output via multiprocessing.Queue
133         def _function_handle_wrapper(queue, **kwargs):
134             try:
135                 out = function_handle(**kwargs)
136                 queue.put(out)
137             except Exception as exc:
138                 queue.put(exc)
139 
140         # Get function args
141         args = {}
142         for name in self._input_names:
143             value = getattr(self.inputs, name)
144             if isdefined(value):
145                 args[name] = value
146 
147         # Profile resources if set
148         if runtime_profile:
149             from nipype.interfaces.base import get_max_resources_used
150             import multiprocessing
151             # Init communication queue and proc objs
152             queue = multiprocessing.Queue()
153             proc = multiprocessing.Process(target=_function_handle_wrapper,
154                                            args=(queue,), kwargs=args)
155 
156             # Init memory and threads before profiling
157             mem_mb = 0
158             num_threads = 0
159 
160             # Start process and profile while it's alive
161             proc.start()
162             while proc.is_alive():
163                 mem_mb, num_threads = \
164                     get_max_resources_used(proc.pid, mem_mb, num_threads,
165                                            pyfunc=True)
166 
167             # Get result from process queue
168             out = queue.get()
169             # If it is an exception, raise it
170             if isinstance(out, Exception):
171                 raise out
172 
173             # Function ran successfully, populate runtime stats
174             setattr(runtime, 'runtime_memory_gb', mem_mb / 1024.0)
175             setattr(runtime, 'runtime_threads', num_threads)
176         else:
177             out = function_handle(**args)
178 
179         if len(self._output_names) == 1:
180             self._out[self._output_names[0]] = out
181         else:
182             if isinstance(out, tuple) and (len(out) != len(self._output_names)):
183                 raise RuntimeError('Mismatch in number of expected outputs')
184 
185             else:
186                 for idx, name in enumerate(self._output_names):
187                     self._out[name] = out[idx]
188 
189         return runtime
190 
191     def _list_outputs(self):
192         outputs = self._outputs().get()
193         for key in self._output_names:
194             outputs[key] = self._out[key]
195         return outputs
196
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nipype/interfaces/utility/wrappers.py b/nipype/interfaces/utility/wrappers.py
--- a/nipype/interfaces/utility/wrappers.py
+++ b/nipype/interfaces/utility/wrappers.py
@@ -58,18 +58,19 @@
     input_spec = FunctionInputSpec
     output_spec = DynamicTraitedSpec
 
-    def __init__(self, input_names, output_names, function=None, imports=None,
-                 **inputs):
+    def __init__(self, input_names=None, output_names='out', function=None,
+                 imports=None, **inputs):
         """
 
         Parameters
         ----------
 
-        input_names: single str or list
+        input_names: single str or list or None
             names corresponding to function inputs
+            if ``None``, derive input names from function argument names
         output_names: single str or list
-            names corresponding to function outputs.
-            has to match the number of outputs
+            names corresponding to function outputs (default: 'out').
+            if list of length > 1, has to match the number of outputs
         function : callable
             callable python object. must be able to execute in an
             isolated namespace (possibly in concert with the ``imports``
@@ -88,10 +89,18 @@
                     raise Exception('Interface Function does not accept '
                                     'function objects defined interactively '
                                     'in a python session')
+                else:
+                    if input_names is None:
+                        fninfo = function.__code__
             elif isinstance(function, (str, bytes)):
                 self.inputs.function_str = function
+                if input_names is None:
+                    fninfo = create_function_from_source(
+                        function, imports).__code__
             else:
                 raise Exception('Unknown type of function')
+            if input_names is None:
+                input_names = fninfo.co_varnames[:fninfo.co_argcount]
         self.inputs.on_trait_change(self._set_function_string,
                                     'function_str')
         self._input_names = filename_to_list(input_names)
@@ -106,10 +115,18 @@
         if name == 'function_str':
             if hasattr(new, '__call__'):
                 function_source = getsource(new)
+                fninfo = new.__code__
             elif isinstance(new, (str, bytes)):
                 function_source = new
+                fninfo = create_function_from_source(
+                    new, self.imports).__code__
             self.inputs.trait_set(trait_change_notify=False,
                                   **{'%s' % name: function_source})
+            # Update input traits
+            input_names = fninfo.co_varnames[:fninfo.co_argcount]
+            new_names = set(input_names) - set(self._input_names)
+            add_traits(self.inputs, list(new_names))
+            self._input_names.extend(new_names)
 
     def _add_output_traits(self, base):
         undefined_traits = {}

{"golden_diff": "diff --git a/nipype/interfaces/utility/wrappers.py b/nipype/interfaces/utility/wrappers.py\n--- a/nipype/interfaces/utility/wrappers.py\n+++ b/nipype/interfaces/utility/wrappers.py\n@@ -58,18 +58,19 @@\n     input_spec = FunctionInputSpec\n     output_spec = DynamicTraitedSpec\n \n-    def __init__(self, input_names, output_names, function=None, imports=None,\n-                 **inputs):\n+    def __init__(self, input_names=None, output_names='out', function=None,\n+                 imports=None, **inputs):\n         \"\"\"\n \n         Parameters\n         ----------\n \n-        input_names: single str or list\n+        input_names: single str or list or None\n             names corresponding to function inputs\n+            if ``None``, derive input names from function argument names\n         output_names: single str or list\n-            names corresponding to function outputs.\n-            has to match the number of outputs\n+            names corresponding to function outputs (default: 'out').\n+            if list of length > 1, has to match the number of outputs\n         function : callable\n             callable python object. must be able to execute in an\n             isolated namespace (possibly in concert with the ``imports``\n@@ -88,10 +89,18 @@\n                     raise Exception('Interface Function does not accept '\n                                     'function objects defined interactively '\n                                     'in a python session')\n+                else:\n+                    if input_names is None:\n+                        fninfo = function.__code__\n             elif isinstance(function, (str, bytes)):\n                 self.inputs.function_str = function\n+                if input_names is None:\n+                    fninfo = create_function_from_source(\n+                        function, imports).__code__\n             else:\n                 raise Exception('Unknown type of function')\n+            if input_names is None:\n+                input_names = fninfo.co_varnames[:fninfo.co_argcount]\n         self.inputs.on_trait_change(self._set_function_string,\n                                     'function_str')\n         self._input_names = filename_to_list(input_names)\n@@ -106,10 +115,18 @@\n         if name == 'function_str':\n             if hasattr(new, '__call__'):\n                 function_source = getsource(new)\n+                fninfo = new.__code__\n             elif isinstance(new, (str, bytes)):\n                 function_source = new\n+                fninfo = create_function_from_source(\n+                    new, self.imports).__code__\n             self.inputs.trait_set(trait_change_notify=False,\n                                   **{'%s' % name: function_source})\n+            # Update input traits\n+            input_names = fninfo.co_varnames[:fninfo.co_argcount]\n+            new_names = set(input_names) - set(self._input_names)\n+            add_traits(self.inputs, list(new_names))\n+            self._input_names.extend(new_names)\n \n     def _add_output_traits(self, base):\n         undefined_traits = {}\n", "issue": "Simplifying the Function interface\nThe arguments of a function `f` can be listed: `f.func_code.co_varnames[:f.func_code.co_argcount]`.\n\nCurrently, I'm decorating functions like so:\n\n``` Python\ndef nipype_function(*output_names, **kwargs):\n    def decorator(func):\n        kwargs.setdefault('output_names', output_names or ['output'])\n        kwargs.update({\n            'function': func,\n            'input_names': func.func_code.co_varnames[:func.func_code.co_argcount]})\n        fn = util.Function(**kwargs)\n        fn.inputs.function_str = u'\\n'.join(\n            fn.inputs.function_str.split(u'\\n')[1:])\n        return fn\n    return decorator\n\n@nipype_decorator('out_list')\ndef mergelists(lists):\n    return [elem for lst in lists for elem in lst]\n```\n\nSo two questions:\n1. Would something like this decorator be useful to include in nipype?\n2. Would it be useful to permit `nipype.interfaces.utility.Function` to derive `input_names` from the function or function string? I would probably also set a default output name. Thus you could generally write:\n\n``` Python\ndef func(arg1, arg2):\n    return arg1 + arg2\n\nfi = Function(function=func)\nfi.inputs.arg1 = 2\nfi.inputs.arg2 = 3\nres = fi.run()\nassert res.outputs.out == 5\n```\n\nI know I'm kind of throwing a flurry of things at y'all without polishing each first, so thanks for your continued attention.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n# vi: set ft=python sts=4 ts=4 sw=4 et:\n\"\"\"Various utilities\n\n    Change directory to provide relative paths for doctests\n    >>> import os\n    >>> filepath = os.path.dirname(os.path.realpath(__file__))\n    >>> datadir = os.path.realpath(os.path.join(filepath,\n    ...                            '../../testing/data'))\n    >>> os.chdir(datadir)\n\n\n\"\"\"\nfrom __future__ import print_function, division, unicode_literals, absolute_import\n\nfrom future import standard_library\nstandard_library.install_aliases()\n\nfrom builtins import str, bytes\n\nfrom nipype import logging\nfrom ..base import (traits, DynamicTraitedSpec, Undefined, isdefined, runtime_profile,\n                    BaseInterfaceInputSpec)\nfrom ..io import IOBase, add_traits\nfrom ...utils.filemanip import filename_to_list\nfrom ...utils.misc import getsource, create_function_from_source\n\nlogger = logging.getLogger('interface')\nif runtime_profile:\n    try:\n        import psutil\n    except ImportError as exc:\n        logger.info('Unable to import packages needed for runtime profiling. '\\\n                    'Turning off runtime profiler. Reason: %s' % exc)\n        runtime_profile = False\n\n\nclass FunctionInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):\n    function_str = traits.Str(mandatory=True, desc='code for function')\n\n\nclass Function(IOBase):\n    \"\"\"Runs arbitrary function as an interface\n\n    Examples\n    --------\n\n    >>> func = 'def func(arg1, arg2=5): return arg1 + arg2'\n    >>> fi = Function(input_names=['arg1', 'arg2'], output_names=['out'])\n    >>> fi.inputs.function_str = func\n    >>> res = fi.run(arg1=1)\n    >>> res.outputs.out\n    6\n\n    \"\"\"\n\n    input_spec = FunctionInputSpec\n    output_spec = DynamicTraitedSpec\n\n    def __init__(self, input_names, output_names, function=None, imports=None,\n                 **inputs):\n        \"\"\"\n\n        Parameters\n        ----------\n\n        input_names: single str or list\n            names corresponding to function inputs\n        output_names: single str or list\n            names corresponding to function outputs.\n            has to match the number of outputs\n        function : callable\n            callable python object. must be able to execute in an\n            isolated namespace (possibly in concert with the ``imports``\n            parameter)\n        imports : list of strings\n            list of import statements that allow the function to execute\n            in an otherwise empty namespace\n        \"\"\"\n\n        super(Function, self).__init__(**inputs)\n        if function:\n            if hasattr(function, '__call__'):\n                try:\n                    self.inputs.function_str = getsource(function)\n                except IOError:\n                    raise Exception('Interface Function does not accept '\n                                    'function objects defined interactively '\n                                    'in a python session')\n            elif isinstance(function, (str, bytes)):\n                self.inputs.function_str = function\n            else:\n                raise Exception('Unknown type of function')\n        self.inputs.on_trait_change(self._set_function_string,\n                                    'function_str')\n        self._input_names = filename_to_list(input_names)\n        self._output_names = filename_to_list(output_names)\n        add_traits(self.inputs, [name for name in self._input_names])\n        self.imports = imports\n        self._out = {}\n        for name in self._output_names:\n            self._out[name] = None\n\n    def _set_function_string(self, obj, name, old, new):\n        if name == 'function_str':\n            if hasattr(new, '__call__'):\n                function_source = getsource(new)\n            elif isinstance(new, (str, bytes)):\n                function_source = new\n            self.inputs.trait_set(trait_change_notify=False,\n                                  **{'%s' % name: function_source})\n\n    def _add_output_traits(self, base):\n        undefined_traits = {}\n        for key in self._output_names:\n            base.add_trait(key, traits.Any)\n            undefined_traits[key] = Undefined\n        base.trait_set(trait_change_notify=False, **undefined_traits)\n        return base\n\n    def _run_interface(self, runtime):\n        # Get workflow logger for runtime profile error reporting\n        from nipype import logging\n        logger = logging.getLogger('workflow')\n\n        # Create function handle\n        function_handle = create_function_from_source(self.inputs.function_str,\n                                                      self.imports)\n\n        # Wrapper for running function handle in multiprocessing.Process\n        # Can catch exceptions and report output via multiprocessing.Queue\n        def _function_handle_wrapper(queue, **kwargs):\n            try:\n                out = function_handle(**kwargs)\n                queue.put(out)\n            except Exception as exc:\n                queue.put(exc)\n\n        # Get function args\n        args = {}\n        for name in self._input_names:\n            value = getattr(self.inputs, name)\n            if isdefined(value):\n                args[name] = value\n\n        # Profile resources if set\n        if runtime_profile:\n            from nipype.interfaces.base import get_max_resources_used\n            import multiprocessing\n            # Init communication queue and proc objs\n            queue = multiprocessing.Queue()\n            proc = multiprocessing.Process(target=_function_handle_wrapper,\n                                           args=(queue,), kwargs=args)\n\n            # Init memory and threads before profiling\n            mem_mb = 0\n            num_threads = 0\n\n            # Start process and profile while it's alive\n            proc.start()\n            while proc.is_alive():\n                mem_mb, num_threads = \\\n                    get_max_resources_used(proc.pid, mem_mb, num_threads,\n                                           pyfunc=True)\n\n            # Get result from process queue\n            out = queue.get()\n            # If it is an exception, raise it\n            if isinstance(out, Exception):\n                raise out\n\n            # Function ran successfully, populate runtime stats\n            setattr(runtime, 'runtime_memory_gb', mem_mb / 1024.0)\n            setattr(runtime, 'runtime_threads', num_threads)\n        else:\n            out = function_handle(**args)\n\n        if len(self._output_names) == 1:\n            self._out[self._output_names[0]] = out\n        else:\n            if isinstance(out, tuple) and (len(out) != len(self._output_names)):\n                raise RuntimeError('Mismatch in number of expected outputs')\n\n            else:\n                for idx, name in enumerate(self._output_names):\n                    self._out[name] = out[idx]\n\n        return runtime\n\n    def _list_outputs(self):\n        outputs = self._outputs().get()\n        for key in self._output_names:\n            outputs[key] = self._out[key]\n        return outputs\n", "path": "nipype/interfaces/utility/wrappers.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n# vi: set ft=python sts=4 ts=4 sw=4 et:\n\"\"\"Various utilities\n\n    Change directory to provide relative paths for doctests\n    >>> import os\n    >>> filepath = os.path.dirname(os.path.realpath(__file__))\n    >>> datadir = os.path.realpath(os.path.join(filepath,\n    ...                            '../../testing/data'))\n    >>> os.chdir(datadir)\n\n\n\"\"\"\nfrom __future__ import print_function, division, unicode_literals, absolute_import\n\nfrom future import standard_library\nstandard_library.install_aliases()\n\nfrom builtins import str, bytes\n\nfrom nipype import logging\nfrom ..base import (traits, DynamicTraitedSpec, Undefined, isdefined, runtime_profile,\n                    BaseInterfaceInputSpec)\nfrom ..io import IOBase, add_traits\nfrom ...utils.filemanip import filename_to_list\nfrom ...utils.misc import getsource, create_function_from_source\n\nlogger = logging.getLogger('interface')\nif runtime_profile:\n    try:\n        import psutil\n    except ImportError as exc:\n        logger.info('Unable to import packages needed for runtime profiling. '\\\n                    'Turning off runtime profiler. Reason: %s' % exc)\n        runtime_profile = False\n\n\nclass FunctionInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):\n    function_str = traits.Str(mandatory=True, desc='code for function')\n\n\nclass Function(IOBase):\n    \"\"\"Runs arbitrary function as an interface\n\n    Examples\n    --------\n\n    >>> func = 'def func(arg1, arg2=5): return arg1 + arg2'\n    >>> fi = Function(input_names=['arg1', 'arg2'], output_names=['out'])\n    >>> fi.inputs.function_str = func\n    >>> res = fi.run(arg1=1)\n    >>> res.outputs.out\n    6\n\n    \"\"\"\n\n    input_spec = FunctionInputSpec\n    output_spec = DynamicTraitedSpec\n\n    def __init__(self, input_names=None, output_names='out', function=None,\n                 imports=None, **inputs):\n        \"\"\"\n\n        Parameters\n        ----------\n\n        input_names: single str or list or None\n            names corresponding to function inputs\n            if ``None``, derive input names from function argument names\n        output_names: single str or list\n            names corresponding to function outputs (default: 'out').\n            if list of length > 1, has to match the number of outputs\n        function : callable\n            callable python object. must be able to execute in an\n            isolated namespace (possibly in concert with the ``imports``\n            parameter)\n        imports : list of strings\n            list of import statements that allow the function to execute\n            in an otherwise empty namespace\n        \"\"\"\n\n        super(Function, self).__init__(**inputs)\n        if function:\n            if hasattr(function, '__call__'):\n                try:\n                    self.inputs.function_str = getsource(function)\n                except IOError:\n                    raise Exception('Interface Function does not accept '\n                                    'function objects defined interactively '\n                                    'in a python session')\n                else:\n                    if input_names is None:\n                        fninfo = function.__code__\n            elif isinstance(function, (str, bytes)):\n                self.inputs.function_str = function\n                if input_names is None:\n                    fninfo = create_function_from_source(\n                        function, imports).__code__\n            else:\n                raise Exception('Unknown type of function')\n            if input_names is None:\n                input_names = fninfo.co_varnames[:fninfo.co_argcount]\n        self.inputs.on_trait_change(self._set_function_string,\n                                    'function_str')\n        self._input_names = filename_to_list(input_names)\n        self._output_names = filename_to_list(output_names)\n        add_traits(self.inputs, [name for name in self._input_names])\n        self.imports = imports\n        self._out = {}\n        for name in self._output_names:\n            self._out[name] = None\n\n    def _set_function_string(self, obj, name, old, new):\n        if name == 'function_str':\n            if hasattr(new, '__call__'):\n                function_source = getsource(new)\n                fninfo = new.__code__\n            elif isinstance(new, (str, bytes)):\n                function_source = new\n                fninfo = create_function_from_source(\n                    new, self.imports).__code__\n            self.inputs.trait_set(trait_change_notify=False,\n                                  **{'%s' % name: function_source})\n            # Update input traits\n            input_names = fninfo.co_varnames[:fninfo.co_argcount]\n            new_names = set(input_names) - set(self._input_names)\n            add_traits(self.inputs, list(new_names))\n            self._input_names.extend(new_names)\n\n    def _add_output_traits(self, base):\n        undefined_traits = {}\n        for key in self._output_names:\n            base.add_trait(key, traits.Any)\n            undefined_traits[key] = Undefined\n        base.trait_set(trait_change_notify=False, **undefined_traits)\n        return base\n\n    def _run_interface(self, runtime):\n        # Get workflow logger for runtime profile error reporting\n        from nipype import logging\n        logger = logging.getLogger('workflow')\n\n        # Create function handle\n        function_handle = create_function_from_source(self.inputs.function_str,\n                                                      self.imports)\n\n        # Wrapper for running function handle in multiprocessing.Process\n        # Can catch exceptions and report output via multiprocessing.Queue\n        def _function_handle_wrapper(queue, **kwargs):\n            try:\n                out = function_handle(**kwargs)\n                queue.put(out)\n            except Exception as exc:\n                queue.put(exc)\n\n        # Get function args\n        args = {}\n        for name in self._input_names:\n            value = getattr(self.inputs, name)\n            if isdefined(value):\n                args[name] = value\n\n        # Profile resources if set\n        if runtime_profile:\n            from nipype.interfaces.base import get_max_resources_used\n            import multiprocessing\n            # Init communication queue and proc objs\n            queue = multiprocessing.Queue()\n            proc = multiprocessing.Process(target=_function_handle_wrapper,\n                                           args=(queue,), kwargs=args)\n\n            # Init memory and threads before profiling\n            mem_mb = 0\n            num_threads = 0\n\n            # Start process and profile while it's alive\n            proc.start()\n            while proc.is_alive():\n                mem_mb, num_threads = \\\n                    get_max_resources_used(proc.pid, mem_mb, num_threads,\n                                           pyfunc=True)\n\n            # Get result from process queue\n            out = queue.get()\n            # If it is an exception, raise it\n            if isinstance(out, Exception):\n                raise out\n\n            # Function ran successfully, populate runtime stats\n            setattr(runtime, 'runtime_memory_gb', mem_mb / 1024.0)\n            setattr(runtime, 'runtime_threads', num_threads)\n        else:\n            out = function_handle(**args)\n\n        if len(self._output_names) == 1:\n            self._out[self._output_names[0]] = out\n        else:\n            if isinstance(out, tuple) and (len(out) != len(self._output_names)):\n                raise RuntimeError('Mismatch in number of expected outputs')\n\n            else:\n                for idx, name in enumerate(self._output_names):\n                    self._out[name] = out[idx]\n\n        return runtime\n\n    def _list_outputs(self):\n        outputs = self._outputs().get()\n        for key in self._output_names:\n            outputs[key] = self._out[key]\n        return outputs\n", "path": "nipype/interfaces/utility/wrappers.py"}]}