Home > Article > Backend Development > What is the self parameter in Python?
Let’s start with what we already know: self - the first parameter in the method - refers to the class instance:
class MyClass: ┌─────────────────┐ ▼ │ def do_stuff(self, some_arg): │ print(some_arg)▲│ ││ ││ ││ ││ instance = MyClass() ││ instance.do_stuff("whatever") │ │ │ └───────────────────────────────┘
Also, this argument doesn't actually have to be called self - it's just a convention. For example, you can use it as is common in other languages.
The above code may be natural and obvious because you have been using it, but we only gave .do_stuff() one argument (some_arg), but the method declares two (self and, some_arg) , it doesn’t seem to make sense. The arrow in the snippet shows that self is translated into an instance, but how is it actually passed?
instance = MyClass() MyClass.do_stuff(instance, "whatever")
What Python does internally is convert instance.do_stuff("whatever") to MyClass.do_stuff(instance, "whatever"). We could call it "Python magic" here, but if we want to really understand what's going on behind the scenes, we need to understand what Python methods are and how they relate to functions.
In Python, there is no such thing as a "method" object - in fact methods are just regular functions. The difference between functions and methods is that methods are defined in the namespace of a class, making them properties of that class.
These attributes are stored in the class dictionary __dict__ and we can access them directly or using the vars built-in function:
MyClass.__dict__["do_stuff"] # <function MyClass.do_stuff at 0x7f132b73d550> vars(MyClass)["do_stuff"] # <function MyClass.do_stuff at 0x7f132b73d550>
The most common way to access them is the "class method ” Way:
print(MyClass.do_stuff) # <function MyClass.do_stuff at 0x7f132b73d550>
Here we access the function using the class attribute and as expected print do_stuff is a function of MyClass. However, we can also access it using instance properties:
print(instance.do_stuff) # <bound method MyClass.do_stuff of <__main__.MyClass object at 0x7ff80c78de50>
But in this case, we get a "bound method" instead of the original function. What Python does for us here is that it binds class attributes to instances, creating what are called "binding methods". This "bound method" is a wrapper around the underlying function, which already inserts the instance as the first argument (self).
Thus, methods are ordinary functions with a class instance (self) appended to their other parameters.
To understand how this happens, we need to look at the descriptor protocol.
Descriptors are the mechanism behind methods, they are objects (classes) that define __get__(), __set__(), or __delete__() methods. To understand how self works, let's just consider __get__(), which has a signature:
descr.__get__(self, instance, type=None) -> value
But what does the __get__() method actually do? It allows us to customize property lookup in a class - or in other words - customize what happens when class properties are accessed using dot notation. This is very useful considering that methods are really just properties of the class. This means we can use the __get__ method to create a "bound method" of a class.
To make it easier to understand, let's demonstrate this by implementing a "method" using a descriptor. First, we create a pure Python implementation of a function object:
import types class Function: def __get__(self, instance, objtype=None): if instance is None: return self return types.MethodType(self, instance) def __call__(self): return
The Function class above implements __get__ , which makes it a descriptor. This special method receives the class instance in the instance parameter - if this parameter is None, we know that the __get__ method was called directly from a class (e.g. MyClass.do_stuff), so we just return self. However, if it is called from a class instance, such as instance.do_stuff, then we return types.MethodType, which is a way of manually creating a "bound method".
In addition, we also provide the __call__ special method. __init__ is called when a class is called to initialize an instance (e.g. instance = MyClass()), while __call__ is called when an instance is called (e.g. instance()). We need to use this because self in types.MethodType(self, instance) must be callable.
Now that we have our own function implementation, we can use it to bind methods to the class:
class MyClass: do_stuff = Function() print(MyClass.__dict__["do_stuff"])# __get__ not invoked # <__main__.Function object at 0x7f229b046e50> print(MyClass.do_stuff)# __get__ invoked, but "instance" is None, "self" is returned print(MyClass.do_stuff.__get__(None, MyClass)) # <__main__.Function object at 0x7f229b046e50> instance = MyClass() print(instance.do_stuff)#__get__ invoked and "instance" is not None, "MethodType" is returned print(instance.do_stuff.__get__(instance, MyClass)) # <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>
By giving MyClass an attribute do_stuff of type Function, we Roughly emulates what Python does when defining methods in a class's namespace.
To sum up, when accessing attributes such as instance.do_stuff, do_stuff is searched in the attribute dictionary (__dict__) of the instance. If do_stuff defines a __get__ method, do_stuff.__get__ is called, ultimately calling:
# For class invocation: print(MyClass.__dict__['do_stuff'].__get__(None, MyClass)) # <__main__.Function object at 0x7f229b046e50> # For instance invocation: print(MyClass.__dict__['do_stuff'].__get__(instance, MyClass)) # Alternatively: print(type(instance).__dict__['do_stuff'].__get__(instance, type(instance))) # <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>
As we know now - a bound method will be returned - a callable wrapped around the original function Wrapper whose parameters are preceded by self!
If you want to explore this further, static and class methods can be implemented similarly (https://docs.python.org/3.7/howto/descriptor.html#static-methods-and-class-methods)
We now know how it works, but there is a more philosophical question - "Why does it have to appear in the method definition?"
The explicit self method parameter is controversial design choice, but it's one that favors simplicity.
Python self-embodies the "worse is better" design philosophy - described here . The priority of this design concept is "simplicity", which is defined as:
The design must be simple, including implementation and interfaces. It's more important that the implementation is simple than the interface...
This is exactly the case with self - a simple implementation at the expense of the interface, where the method signature doesn't match its invocation.
There are of course more reasons why we should write self explicitly, or why it must be preserved, some of which are described in a blog post by Guido van Rossum (http://neopythonic.blogspot.com/ 2008/10/why-explicit-self-has-to-stay.html), the article responded to a request for its removal.
Python abstracts away a lot of complexity, but in my opinion digging into the low-level details and complexity is extremely valuable for better understanding of how the language works, when things break and advanced troubleshooting/debugging When not enough, it can come in handy.
Also, understanding descriptors can actually be quite practical since they have some use cases. While most of the time you really only need @property descriptors, there are some cases where custom descriptors make sense, such as those in SLQAlchemy or e.g. custom validators.
The above is the detailed content of What is the self parameter in Python?. For more information, please follow other related articles on the PHP Chinese website!