开发者

Which is more fundamental: Python functions or Python object-methods?

开发者 https://www.devze.com 2022-12-15 00:28 出处:网络
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

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?"

0

精彩评论

暂无评论...
验证码 换一张
取 消