What exactly are methods in python? Is a method an attribute?

Question:

I have a question with the methods in python. According to the Python documentation on the__dict__ special attribute:

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 (typo) attributes of an object.

Simply put, the __dict__ attribute 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 the __dict__ of the class, the same as the attribute a .

According to the python documentation, __dict__ stores the attributes of an object (it should be noted that a class is an object). So a method is actually an attribute?

Could you say that method is an attribute that stores a function? But at no point did I do something like this:

def funcion(self): print(self.a) class A: a = 1 method = funcion print(A().method())

Besides that it works and the function function receives as the first parameter ( self ), the instance, printing 1 as a result.

Here I have another question, the method attribute that I define in class A ( method = funcion ), is it a method?


So what exactly is a method in python?

It would be helpful if you clarify this question that I have, thank you for your answers.

Answer:

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.

IMPORTANT

This may seem to contradict the concepts of attribute and method that exist in the OOP paradigm, in which an attribute is roughly a characteristic that describes a certain object while a method is something that that object can do. This separation is conceptually maintained at a high level in Python, conceptually a method and an attribute are of course not the same, but as regards the language and how OOP is implemented in Python the separation is not so clear, rather Quite the contrary , and that is what the question is about and that is what it is intended to answer in this answer.


The method attribute that I define in class A ( method = funcion ), is it 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 non-conceptual purposes, it is a method. You can define the methods outside the class, even in another module and then bind them like that. Or even after the class definition:

class Test:
    def foo(self):
        print(f"Hola, soy un método de {self} definido de forma 'normal'")
    
>>> inst = Test()
>>> inst.foo
<bound method Test.foo of <__main__.Test object at 0x7f13658fac70>>
>>> inst.foo()
Hola, soy un método de <__main__.Test object at 0x7f13658fac70> definido de forma 'normal'

>>> inst.bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'bar'

>>> def bar(self):
        print(f"Soy un método de {self} definido a 'distancia'")

>>> Test.bar = bar
>>> inst.bar
<bound method bar of <__main__.Test object at 0x7f13658fac70>>
>>> inst.bar()
Soy un método de <__main__.Test object at 0x7f13658fac70> definido a 'distancia'

Taking! Dynamic language in all its glory, for the good and for the bad … 🙂

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 on top of that call them from the instances already created 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 are warned, but there it goes.

In Python practically everything is an in-memory object (including imported modules, the script itself, functions, classes, integers, etc), which in turn have methods, functions, or anything with a __call __() method, like objects that can be called.

The attribute resolution system (in which __dict__ has a primary role) does not care at all whether the object is callable or not. Just try looking for that name in the class or classes it is derived from by following the MRO. Then you try to call him, you try to assign him or you hit him on fire, but his mission ends when he gives you a reference to the attribute or an exception because he couldn't find it.

In reality, methods are objects that act as wrappers for functions, keeping references to the instance to which they belong and thus linking the function with its instance. They are also created on the fly by accessing the function as an attribute through the aforementioned .nombre syntax.

If we look at how a method is defined, it does not 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 more).

Obviously there has to be a mechanism that .nombre the function with the class or instance and that allows the method to be referenced with .nombre . Python solves this 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 still just a name associated at all times with a reference to an object in memory.

This means that basically all the variables / attributes are the same, they are all just ways to find an object in memory, it does not matter that the object is callable or not, or that at a given moment the variable points to another object or have several point to it. The type and properties always belong to the object, not to the variable. It doesn't really matter for the language if instancia.nombre is an integer, a string, a list or a method, they are all objects.

In languages ​​like C ++, this does not happen, the data and the methods are clearly separated, with the data (attributes) set to a specific type and in which the methods are not objects.

The __dict__ attribute

The __dict__ attribute of an object (if it is also an attribute) behaves similar to a dictionary, but it is not. It really is an instance of a DictProxy class whose objects behave like dictionaries but with some differences, for example, we cannot add attributes like we do with the keys of a dictionary, this does not work:

 Clase.__dict__["foo"] = 13
 
 

we must use:

Clase.foo = 13

The

setattr(Clase, "foo", 13)

For example, if we are so unaware of doing this:

del instancia.__dict__

is created again …

The reasons for using DictProxy 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 can send the interpreter himself to take wind …

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 a descriptor protocol and that basically is to change an attribute for an object (the descriptor) that mediates 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 the methods as we will see. Whereas if it also implements __set__ are data descriptors, which are not of much interest to us in this case.

Descriptors are used for many things related to attributes and methods, they are all over the place 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 appearing to be using.

Properties are perhaps a very clear example of this:

class Foo:
    def __init__(self):
        self._bar = None
        
    @property
    def bar(self):
        return self._bar
        
    @bar.setter
    def bar(self, valor):
        self._bar = valor
        
>>> inst = Foo()  
>>> inst.bar = 5  
>>> inst.bar  
5

But all this goes much further, if not I would not have gotten into the "puddle" of descriptors. As I mentioned before, to allow a function to be called as methods you need something else. Functions include the __get__() method to bind the function with access to tributes by .nombre , therefore all functions are non-data descriptors that return bound methods when invoked from an object.

In fact, if we access a method through __dict__ , __dict__ is not called, but a reference to the function below is returned without further __get__ :

class Foo:
    def bar(self):
        pass
>>> Foo.__dict__["bar"]
<function Foo.bar at 0x7f0345da3a60>

If we access using Clase.método , if invoked __get__ , since as mentioned is the link between the function and the class to allow this syntax, but also returns the object without underlying function:

>>> Foo.bar
<function Foo.bar at 0x7f0345da3a60>

Instead, when accessed via the instance, the function is wrapped on the fly in a bound method:

>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f0345df0460>>

This object internally stores the reference to the function and the instance to which it is associated, among other things:

>>> Foo().bar.__func__
<function Foo.bar at 0x7f0345da3a60>
>>> Foo().bar.__self__
<__main__.Foo object at 0x7f0345df0460>

A method, therefore, is nothing more than an attribute linked to a function through a descriptor.

Scroll to Top