10

I am trying to get a conceptual understanding of the nature of Python functions and methods. I get that functions are actually objects, with a method that is called when the function is executed. But is that function-object method actually another function?

For example:

def fred():
    pass

If I look at dir(fred), I see it has an attribute named __call__. But dir(fred.__call__) also has an attribute named __call__. So does fred.__call__.__call__ and so on. The ids of this chain of __call__ objects suggest they are all distinct. Are they really objects or is this some low-level trick of the interpreter?

Which is more fundamental: functions or object-methods?

1
  • The ids of the call object suggest they're generated as requested: sometimes I see duplicates at different levels, and sometimes I see different ids at the same level. Commented Dec 31, 2009 at 15:10

3 Answers 3

4

Short answer: both are fundamental, .__call__() on functions is just a virtual trick.


The rest of this answer is a bit complicated. You don't have to understand it, but I find the subject interesting. Be warned that I'm going to present a series of lies, progressively fixing them.

Long answer

At the most fundamental level, Python can be said to have just 2 operations:

  • attribute access: obj.attr
  • function call: callable(args)

Method calls - obj.method(args) - are not fundamental. They consist of 2 steps: fetching the attribute obj.method (which gives a callable "bound method" object) and calling that with args.

Other operators are defined in terms of them. E.g. x + y tries x.__add__(y), falling back to other similar combinations if that doesn't work.

Infinitely Long Answer?

So far so good. But calling and attribute access themselves are also defined in terms of obj.__call__(args) and obj.__getattribute__(name)?!?
Is it turtles all the way down?!?

The trick is that operations on an object are defined by calling methods of its type: type(obj).__call__(obj, args) and type(obj).__getattribute__(obj, name). Which BTW means that I lied to you, and there is a third fundamental operation:

  • getting the type of an object: type(obj)

OK, this is still not helpful. type(obj).__call__(...) still involves an attribute access and a call, so this should continue ad infinitum? The rub is that eventually you hit a builtin type - usually a function, object or type - and for them attribute access and function calls are fundamental.

So when you call a instance of a custom class, that's implemented through its __call__ method indeed. But its __call__ method is probably a normal function - which can be called directly. End of mystery.

Similarly about __getattribute__ - you can provide it to define attribute access for your class, but the class itself implement attribute access fundamentally (unless it has a custom metaclass).

The Curtain in Front of the Man

So why does even a function has a fred.__call__ method? Well that's just smoke and mirrors that Python pulls to blur the difference between builtin types and custom classes. This method exists on all callable objects, but calling a normal function doesn't have to go through it - functions are fundamentally callable.

Similarly, all objects have obj.__getattribute__ and obj.__class__, but for built-in types it just exposes the fundamental operations instead of defining it.

Small Print

The first claim that Python had 2 fundamental operations was actually a complete lie. Technically, all Python operators have a "fundamental" operation at the C level, exposed for consistency through a method, and custom classes can redefine these operations through similar methods.

But the story I told you could have been true, and it reduces the question its center: why .__call__() and .__getattribute__() are not an infinite recursion.

Sign up to request clarification or add additional context in comments.

Comments

2

Not specifically a Python answer, but at the lowest level the processor understands only actions and variables. From that we extrapolate functions, and from variables and functions we extrapolate objects. So from a low-level programming perspective I'd say that the more fundamental thing is the function.

That's not necessarily true of Python in the Pythonic sense, and is probably a good example of why it's not always beneficial to look deeply into the implementation of the language as a user of it. :) Thinking of a function as an object is certainly the better answer in Python itself.

At first I thought your calls were tracking into the Python library, but the .call method has the same properties as any other method. Thus it's recursively exploring itself, I think, having played with the python CLI for a few minutes; I think that is a painful way of exploring the architecture and while not necessarily a bug a property of how Python handles objects under the covers. :)

Comments

1

Which is more fundamental: functions or object-methods?

I think the best answer might be "neither". See the Execution model part of the Python reference, where it refers to "blocks". This is what actually gets executed. The __call__ thing you were getting hung up on in the infinite search for an end is just a wrapper which knows how to execute the code block (see the various func_xxx attributes of your function instead, with the actual bytecode being stored as func_code).

Also relevant, the Function definitions section, which refers to "a function object [being] (a wrapper around the executable code for the function)". Lastly, there's the term callable, which might also be an answer to "which is more fundamental?"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.