I have a question about methods in python. According to the Python documentation on the special attribute __dict__
:
A dictionary or other mapping object used to store an object's (writable) attributes .
Translation:
A dictionary or other mapping object used to store the (write) attributes of an object.
In a nutshell the attribute __dict__
stores the attributes of an object.
When creating, for example, the following class:
>>> class A:
... a = 1
...
... def method(self):
... print('method')
...
...
>>> A.__dict__
{'__module__': '__main__', 'a': 1, 'method': <function A.method at 0x7f4b2e802950>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
You can see that the "method" method
is in __dict__
the class, as well as the attribute a
.
According to the python documentation, __dict__
it stores the attributes of an object (it should be noted that a class is an object). So a method is actually an attribute?
Could it be said that it method
is an attribute that stores a function? But at no time did I do something like this:
def funcion(self): print(self.a) class A: a = 1 method = funcion print(A().method())
In addition to that it works and the function
function
receives as first parameter (self
), the instance, printing1
as result.Here another question arises, the attribute
method
that I define in the classA
(method = funcion
), is it a method?
So what exactly is a method in python?
It would be of great help if you could clarify this doubt that I have, thanks for your answers.
So a method is actually an attribute?
The short answer is yes , for the level at which the question is focused for practical purposes it is. Technically anything that can be referenced through an object using the
.nombre
dotted expression notation is considered an attribute , that includes methods.Is the attribute
method
that I define in the classA
(method = funcion
) a method?If it is. Although the concept of method is normally restricted to "function defined in the body of a class", for practical and not conceptual purposes, it is a method. You can define the methods outside the class, even in another module, and then bind them like this. Or even after the class definition:
Take! Dynamic language in all its splendor, for better and for worse... :)
That it can be done does not mean that it is a good idea, Python lets you do almost everything, very good ideas and very bad ideas. When something is usually restricted, it is because it endangers the interpreter itself or for a very good reason of design, optimization, etc. Instead of doing this we have inheritance for example, but we can even add methods to a class with created instances and call them from the already created instances without any problem.
So what exactly is a method in Python?
If someone wants to read for a little while with the possibility of not understanding anything at the end, because of me, they have been warned, but here it goes.
In Python practically everything is an object in memory (including imported modules, the script itself, functions, classes, integers, etc), which in turn have methods, functions or anything with a method
__call __()
, as objects that can be called.The attribute resolution system (in which it
__dict__
has a primary role) doesn't care at all whether the object is callable or not. Just try to find that name in the class(es) it derives from by following the MRO. Then you try to call it, you try to assign it or you set it on fire, but its mission ends when it gives you a reference to the attribute or an exception because it couldn't find it.Actually methods are objects that act as wrappers for functions holding references to the instance they belong to and thus linking the function to its instance. They are also created on the fly by accessing the function as an attribute using the aforementioned syntax
.nombre
.If we look at how a method is defined, it doesn't differ at all from defining a function, the only thing that changes is that a reference to the class or the instance is received as the first argument (
cls
/self
by convention nothing else).Obviously there has to be a mechanism that binds the function to the class or instance and allows the method to be referenced with
.nombre
. Python solves this by using non-data descriptors. In the end we will see it above.Keep in mind that Python is a dynamic language, in which a variable/attribute is just a name associated at all times with a reference to an object in memory.
This means that basically all variables/attributes are the same, they are all just ways to find an object in memory, it doesn't matter if the object is callable or not, or if at a given moment the variable points to another object or that several point to the same. The type and properties always belong to the object, not to the variable. It really doesn't matter to the language if
instancia.nombre
it's an integer, a string, a list, or a method, they're all objects.In languages like C++, this does not happen, the data and the methods are clearly separated, with the data (attributes) fixed to a specific type and in which the methods are not objects.
The attribute
__dict__
An object's attribute
__dict__
(if it's also an attribute) behaves like a dictionary, but it's not. It's really an instance of a classDictProxy
whose objects behave like dictionaries but with some differences, for example, we can't add attributes like we do with the keys of a dictionary, this doesn't work:we must use:
either
For example, if we are so unaware of doing this:
is created again...
The reasons for using
DictProxy
it are several, in essence it is done by optimization, forcing the keys to always be strings, which allows the interpreter to perform certain optimizations. Also for security, to prevent things like the one above from mutilating a class or instance and sending the interpreter itself into a tailspin...class "dictionaries" like
__dict__
simply store methods as functions.descriptors
A descriptor is an object that has at least one of the following magic methods in its attributes:
__get__
,__set__
or__delete__
. Its implementation and use is known as the descriptor protocol and basically it is to change an attribute for an object (the descriptor) that intermediates in the access to that attribute, in this way they allow to define and establish the behavior of the attribute of an object.A descriptor that only implements
__get__
is called a non-data descriptor and they are used to access methods as we will see. While if it implements__set__
, they are also data descriptors, which do not interest us much in this case.Descriptors are used for many things related to attributes and methods, they are everywhere in Python even if we don't see them. For example for static methods (
@staticmethod
), class methods (@classmethod
) and properties (@property
) descriptors are used, decorators are nothing more than a way to implement them in a simple way without looking like they are being used.Properties are perhaps a very clear example of this:
But all this goes much further, otherwise I would not have gotten into the "pool" of descriptors. As I mentioned before, to allow a function to be called as methods you need something else. Functions include the method
__get__()
to bind the function with access to tributes via.nombre
, so all functions are non-data descriptors that return bound methods when called from an object.In fact, if we access a method through
__dict__
, it doesn't call__get__
, but just returns a reference to the function below:If we access using
Clase.método
, if it is__get__
called, since, as has been commented, it is the link between the function and the class to allow said syntax, but the underlying function object is also returned without further ado:Instead, when accessed via the instance, the function is wrapped on the fly in a bound method:
This object internally stores the reference to the function and the instance to which it is associated, among other things:
A method, therefore, is nothing more than an attribute bound to a function through a descriptor.