In principle, you could directly modify the ast ("abstract syntax tree") of the function, though in practice it might get quite hairy. Anyway, here is how to do it for your simple example:
This creates from the source an ast and derives from the NodeTransformer class to modify the ast in-place. The node transformer has a generic visit method that traverses a node and its subtree delegating to node specific visitors in derived classes. Here we change all names np to sp and afterwards change those attributes to former np now sp that spell differently. You'd have to add all such differences to the translate dict.
Finally, we compile back from the ast to a code object and execute it to make the modified function available.
import ast, inspect
import numpy as np
import sympy as sp
def f(a):
return np.array([np.sin(a), np.cos(a)])
z = ast.parse(inspect.getsource(f))
translate = {'array': 'Array'}
class np_to_sp(ast.NodeTransformer):
def visit_Name(self, node):
if node.id=='np':
node = ast.copy_location(ast.Name(id='sp', ctx=node.ctx), node)
return node
def visit_Attribute(self, node):
self.generic_visit(node)
if node.value.id=='sp' and node.attr in translate:
fields = {k: getattr(node, k) for k in node._fields}
fields['attr'] = translate[node.attr]
node = ast.copy_location(ast.Attribute(**fields), node)
return node
np_to_sp().visit(z)
exec(compile(z, '', 'exec'))
x = sp.Symbol('x')
print(f(x))
Output:
[sin(x), cos(x)]
UPDATE simple enhancement: modify functions called by function:
import ast, inspect
import numpy as np
import sympy as sp
def f(a):
return np.array([np.sin(a), np.cos(a)])
def f2(a):
return np.array([1, np.sin(a)])
def f3(a):
return f(a) + f2(a)
translate = {'array': 'Array'}
class np_to_sp(ast.NodeTransformer):
def visit_Name(self, node):
if node.id=='np':
node = ast.copy_location(ast.Name(id='sp', ctx=node.ctx), node)
return node
def visit_Attribute(self, node):
self.generic_visit(node)
if node.value.id=='sp' and node.attr in translate:
fields = {k: getattr(node, k) for k in node._fields}
fields['attr'] = translate[node.attr]
node = ast.copy_location(ast.Attribute(**fields), node)
return node
from types import FunctionType
for fn in f3.__code__.co_names:
fo = globals()[fn]
if not isinstance(fo, FunctionType):
continue
z = ast.parse(inspect.getsource(fo))
np_to_sp().visit(z)
exec(compile(z, '', 'exec'))
x = sp.Symbol('x')
print(f3(x))
Prints:
[sin(x) + 1, sin(x) + cos(x)]
sympyhas the ability to generatenumpyexpressions from its own expressions. Butsympyis already working with symbols. But yournp_funis a Python function, even if it callsnumpyfunctions. In the.pyfile it is a string, but in the running session it is already parsed by the interpreter. You can only get at the string version withinspecttools. - hpauljnp.fun(x)callsnp.sin(x), which becomesnp.sin(np.array(x)).np.array(x)is a 0d array with an object element, theSymbol. Faced with that type of array,np.sin()tries to dox.sin(). That's the source of your attribute error.sympysymbols can be used in somenumpymath, but not ones likenp.sin. - hpaulj