Functions are a more complicated beast -but they can be created in a similar fashion.
First: Test = type("Test",(),{"x":5})
creates a class, not a function.
Second, there is the syntax for functions as expressions, using the keyword lambda
,which can work like:
myfunction = lambda x: x + 5
Which is equivalent to:
def myfunction(x):
return x + 5
But this is not the same as creating a function programmatically. The equivalent of the Type
call is a call to types.FunctionType
.
While the return of that call will give you a function, getting some of the elements for it to work are harder: the most important part is a "code object": a compiled in part of bytecode associated with some metadata parameters that allow it to be executed as a function - and then, the call to FunctionType
will aggregate even more metadata, and you have a working function. It is far less useful than creating a new class with Type
. And usually, actually, new functions are created to modify some behavior on existing functions, so the code object, the core piece, is just copied around from some other existing function, created normally.
The builtin compile
function is an easy way to create a code object, which can be used along with FunctionType - however, if the code you compile with it has no function definition itself, it will be built as if it was top level code: it can't contain a return statement, and can't access parameters.
But, nonetheless, the output of compile
can be fed into FunctionType and you can have a "primitive" function which can change global variables around:
In [12]: import types
In [13]: a = 0
In [15]: mycode = compile("global a\na = 23", filename="", mode="exec")
In [16]: myfunc = types.FunctionType(mycode, globals(), "myfunc")
In [17]: myfunc()
In [18]: a
Out[18]: 23
The more usual pattern however is create a multiline string with a full function body - def name():
and indentation included, and then create it with the exec
statement. It feels far less "magic" than the sequence above, after all, one is just "eval"ing a piece of code - but in practice is the way it is done in "real world" code when this resource is needed. Python's own stdlib use something like that to create collection.namedtuple
objects:
In [19]: source = """
...: def myfunc(a, b):
...: return a + b
...: """
In [20]: exec(source, globals())
In [21]: myfunc(3, 2)
Out[21]: 5
Some use cases exist for that, mainly by making "source" an "f-string" and filing in some values at runtime programmatically.
Just for sake of completeness, as I mentioned above, a call to compile
won't give you enough control on a call to create a fully working function, with access to parameters, able to return a value, etc... However, that can be built by using types.CodeType
- it is a far more complicated call, with up to 18 arguments, the most important of which can not be easily synthesized from Python source code unless one builds a function using any method above and "borrows" it from there: it needs a "codestring" which is a plain bytes
object, with the actual bytecode to be executed: the "machine code that runs in the Python VM". There are ways to use the dis
module, and probably nice 3rdy party packages that would allow one to code in what could be called a "Python VM assembly", coding the Python VM opcodes by name explicitly, and then getting a bytes object with those translated to binary form. But usually, even when building a new code object one will borrow it from an existing function getting the attribute existing_function.__code__.co_code
.
Also, although both FunctionType
and CodeType
can be imported from the types
module, both callables can be obtained from a regular function by using the 1 argument form of type
. Given an existing "myfunction" one can do FunctionType = type(myfunction); CodeType= type(myfunction.__code__)
You probably will like to check the help
output or documentation for compile
, functools.FunctionType
, functools.CodeType
.