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开发者_Go百科__)
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?
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.
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. :)
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?"
精彩评论