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?
Functions can be called only by its name, as it is defined independently. But methods can't be called by its name only, we need to invoke the class by a reference of that class in which it is defined, i.e. method is defined within a class and hence they are dependent on that class.
Method and a function are the same, with different terms. A method is a procedure or function in object-oriented programming. A function is a group of reusable code which can be called anywhere in your program. This eliminates the need for writing the same code again and again.
Python object() method Python object() function returns the empty object, and the Python object takes no parameters. In python, each variable to which we assign a value/container is treated as an object. Object in itself is a class.
Some of the most useful functions in Python are print(), abs(), round(), min(), max(), sorted(), sum(), and len().
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.
At the most fundamental level, Python can be said to have just 2 operations:
obj.attr
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.
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:
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).
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.
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. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With