3

I was learning about meta classes in python and I was fascinated to find out that you could create a function like this: Test = type("Test",(),{"x":5})

Since functions are also objects of the function class, it is possible to create a function in a similar format like this: MyFunc = function("MyFunc", (), {}) I couldn't figure out how to add code inside the function or if this is even possible. Thanks

jsbueno
  • 99,910
  • 10
  • 151
  • 209

3 Answers3

2

Your example doesn't create a function, it creates a type and will allow you to create Test objects.

Test = type("Test",(),{"x":5})
t = Test()
print(t)
print(type(t))

Output:

<__main__.Test object at 0x0000023B1CC0A100>
<class '__main__.Test'>

You could create a function using lambda:

add_one = lambda x: x + 1
print(add_one(41))
print(type(add_one))

Output:

42
<class 'function'>

Generally though, you shouldn't have to do either and it's considered bad form to do so unless you have to for some arcane reason.

The main reason you shouldn't have to is that you generally don't need to modify anything about a function that needs to be changeable at runtime. Users of your programs don't need to know what functions and variables are called. And if you're writing a module for people to import, in most cases you can use composition and inheritance instead.

Edit: user @jsbueno correctly points out that a lambda function doesn't allow you to write a full function proper. They outline how to get started on actually doing that, but if your intent is to take code in text format and compile it on the fly, you can also consider using exec().

exec("""
def add_one(x):
    return x + 1
""")

print(add_one(1))

Note however that it's very dangerous to just go and execute strings in your program that some user may have some level of control over - they could put anything in there. Also note that your editor or IDE will likely not like you calling a function that wasn't defined as part of your code (that's just a string after all, whether it's valid code or not is not something your IDE can get into).

You should tell us more about an actual use case to get better feedback on whether a specific approach is better than another.

Grismar
  • 27,561
  • 4
  • 31
  • 54
2

In fact function is not directly available, so you must get it from the type of a true function:

def foo():
    pass

function = type(foo)
del foo        # remove it from the know symbols since it is no longer useful

Moreover, the constructor of that function type requires a first argument of type code. For trivial operations, you could use a lambda:

MyFunc = function((lambda x: 2*x).__code__, {}, 'MyFunc')

It looks now like more or less like a function:

print(MyFunc(2))
4
print(MyFunc.__name__)
MyFunc

But even if it shows that Python can be an amazing tool, I cannot imagine a serious use case for the above hack...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • What's the second parameter? – Kelly Bundy Apr 11 '23 at 22:23
  • @KellyBundy: it is a global dictionary. Additional arguments are the argdefs, and a closure, by I did not experiment with them, not could I find a clear documentation about that point (I have not seriously searched either, because I really think that it is not worth it...) – Serge Ballesta Apr 11 '23 at 22:27
2

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.

jsbueno
  • 99,910
  • 10
  • 151
  • 209