I\'m wondering about the more intricate differences between functions and callable objects. For example, if you do:
def foo1():
return 2 + 4
class foo2:
Is there a version of type() but for functions, a metafunction?
Sort of. Functions have a type, and that type can be used to construct new functions from, at minimum, a code object and a globals dictionary. (The code object can be constructed using compile() or grabbed from an existing function, or, if you're a masochist, built from a bytecode string and other information using the code type's constructor.) The function type is not a meta-anything because functions are instances, not classes. You can get this type using type(lambda:0) (putting any function in the parentheses), and do help(type(lambda:0)) to see its arguments. The function type is an instance of type.
Say someone wanted to write their own metafunction
You can't subclass the function type, sorry.
class FunkyFunc(type(lambda: 0)): pass
TypeError: Error when calling the metaclass bases
type 'function' is not an acceptable base type
Is the only purpose behind having a callable object to have a function that can store info in it as well?
There are many uses for it. Your example is one (although function instances can have attributes, a class provides better documentation of the attributes); the flip side, making a plain old data object callable, is another (I gave a kind of funky example of that in this answer). You can use classes to write decorators if the instances are callable. You can use __call__ on a metaclass to customize instance construction. And you can use it to write functions with different behavior, sort of as if you could in fact subclass the function type. (If you want C-style "static" variables in a function, you might write it as a class with a __call__ method and use attributes to store the static data.)
A fun thing about function objects is that, because they're callable, they themselves have a __call__ method. But a method is callable, and so that __call__ method also has a __call__ method, and that __call__ method (being callable) also has a __call__ method, and so on ad infinitum. :-)