Maison  >  Article  >  développement back-end  >  Qu'est-ce que le paramètre self en Python ?

Qu'est-ce que le paramètre self en Python ?

王林
王林avant
2023-05-08 17:49:081080parcourir

Python 的"self"参数是什么?

Commençons par ce que nous savons déjà : self - le premier paramètre d'une méthode - fait référence à l'instance de classe : 🎜🎜#

class MyClass:
┌─────────────────┐
▼ │
def do_stuff(self, some_arg): │
print(some_arg)▲│
 ││
 ││
 ││
 ││
instance = MyClass() ││
instance.do_stuff("whatever") │
│ │
└───────────────────────────────┘

De plus, cet argument ne doit pas nécessairement être appelé soi - c'est juste une convention. Par exemple, vous pouvez l’utiliser comme cela est courant dans d’autres langues.

Le code ci-dessus peut être naturel et évident parce que vous l'avez utilisé, mais nous n'avons donné à .do_stuff() qu'un seul argument (some_arg), mais la méthode en déclare deux (self et, some_arg), il cela ne semble pas avoir de sens. La flèche dans l'extrait montre que self est traduit en instance, mais comment est-il réellement transmis ?

instance = MyClass()
MyClass.do_stuff(instance, "whatever")

Ce que Python fait en interne, c'est convertir instance.do_stuff("whatever") en MyClass.do_stuff(instance, "whatever"). Nous pourrions appeler cela « la magie Python » ici, mais si nous voulons vraiment comprendre ce qui se passe dans les coulisses, nous devons comprendre ce que sont les méthodes Python et comment elles sont liées aux fonctions.

Propriétés/Méthodes de classe

En Python, il n'existe pas d'objet "méthode" - en fait, les méthodes ne sont que des fonctions normales. La différence entre les fonctions et les méthodes réside dans le fait que les méthodes sont définies dans l'espace de noms d'une classe, ce qui en fait des propriétés de cette classe.

Ces attributs sont stockés dans le dictionnaire de classe __dict__, on peut y accéder directement ou utiliser la fonction intégrée vars :

MyClass.__dict__["do_stuff"]
# <function MyClass.do_stuff at 0x7f132b73d550>
vars(MyClass)["do_stuff"]
# <function MyClass.do_stuff at 0x7f132b73d550>

Access leur approche la plus courante est l'approche "méthode de classe":

print(MyClass.do_stuff)
# <function MyClass.do_stuff at 0x7f132b73d550>

Ici, nous accédons à la fonction en utilisant un attribut de classe, qui imprime do_stuff en fonction de MyClass comme attendu. Cependant, nous pouvons également y accéder en utilisant les propriétés de l'instance :

print(instance.do_stuff)
# <bound method MyClass.do_stuff of <__main__.MyClass object at 0x7ff80c78de50>

Mais dans ce cas, nous obtenons une "méthode liée" au lieu de la fonction d'origine. Ce que Python fait pour nous ici, c'est qu'il lie les attributs de classe aux instances, créant ce qu'on appelle des « méthodes de liaison ». Cette "méthode liée" est un wrapper autour de la fonction sous-jacente, qui insère déjà l'instance comme premier argument (self).

Ainsi, les méthodes sont des fonctions ordinaires avec une instance de classe (self) ajoutée à leurs autres paramètres.

Pour comprendre comment cela se produit, nous devons jeter un œil au protocole du descripteur.

Descriptor Protocol

Les descripteurs sont le mécanisme derrière les méthodes, ce sont des objets (classes) qui définissent les méthodes __get__(), __set__() ou __delete__(). Pour comprendre comment fonctionne self, considérons simplement __get__(), qui a une signature :

descr.__get__(self, instance, type=None) -> value

Mais que fait réellement la méthode __get__() ? Cela nous permet de personnaliser la recherche de propriétés dans une classe - ou en d'autres termes - de personnaliser ce qui se passe lorsque les propriétés de classe sont accessibles à l'aide de la notation par points. Ceci est très utile étant donné que les méthodes ne sont en réalité que des propriétés de la classe. Cela signifie que nous pouvons utiliser la méthode __get__ pour créer une « méthode liée » d’une classe.

Pour faciliter la compréhension, démontrons cela en implémentant une « méthode » utilisant des descripteurs. Tout d'abord, nous créons une implémentation Python pure d'un objet fonction :

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

La classe Function ci-dessus implémente __get__ , ce qui en fait un descripteur. Cette méthode spéciale reçoit l'instance de classe dans le paramètre d'instance - si ce paramètre est None, nous savons que la méthode __get__ a été appelée directement depuis une classe (par exemple MyClass.do_stuff), nous renvoyons donc simplement self. Cependant, s'il est appelé à partir d'une instance de classe, telle que instance.do_stuff, nous renvoyons types.MethodType, qui est un moyen de créer manuellement une « méthode liée ».

De plus, nous proposons également la méthode spéciale __call__. __init__ est appelé lorsqu'une classe est appelée pour initialiser une instance (par exemple instance = MyClass()), tandis que __call__ est appelé lorsqu'une instance est appelée (par exemple instance()). Nous devons l'utiliser car self dans types.MethodType(self, instance) doit être appelable.

Maintenant que nous avons notre propre implémentation de fonction, nous pouvons l'utiliser pour lier des méthodes à des classes :

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>

En donnant à MyClass un attribut de fonction do_stuff de type, nous simulons grossièrement ce que fait Python lors de la définition d'une méthode dans l'espace de noms d'une classe.

Pour résumer, lors de l'accès à des attributs tels que instance.do_stuff, do_stuff est recherché dans le dictionnaire d'attributs (__dict__) de l'instance. Si do_stuff définit une méthode __get__, do_stuff.__get__ est appelé, appelant finalement : Un wrapper appelable pour une fonction dont les arguments sont précédés de self !

Si vous souhaitez approfondir cela, vous pouvez implémenter des méthodes statiques et de classe de la même manière (https://docs.python.org/3.7/howto/descriptor.html#static-methods-and-class - méthodes)

Pourquoi le soi est-il dans la définition de la méthode ?

Nous savons maintenant comment cela fonctionne, mais il y a une question plus philosophique : "Pourquoi doit-il apparaître dans la définition de la méthode ?" choix de conception controversé, mais qui privilégie la simplicité.

Python incarne la philosophie de conception « le pire est le mieux » - décrite ici . La priorité de cette philosophie de conception est la « simplicité », définie comme :

La conception doit être simple, y compris la mise en œuvre et les interfaces. Il est plus important que l'implémentation soit simple que l'interface...

C'est exactement le cas de self - une implémentation simple au détriment de l'interface, où la signature de la méthode ne correspond pas à son invocation.

Il y a bien sûr d'autres raisons pour lesquelles nous devrions nous écrire explicitement, ou pourquoi il faut le préserver, dont certaines sont décrites dans un article de blog de Guido van Rossum (http://neopythonic.blogspot.com/2008/10/ pourquoi-explicit-self-has-to-stay.html), l'article répondait à une demande de suppression.

Python élimine beaucoup de complexité, mais à mon avis, plonger dans les détails et la complexité de bas niveau est extrêmement utile pour mieux comprendre le fonctionnement du langage, lorsque les choses tombent en panne et le dépannage/débogage avancé n'est pas assez utile.

De plus, comprendre les descripteurs peut en fait être très pratique car ils ont des cas d'utilisation. Bien que la plupart du temps vous n'ayez besoin que de descripteurs @property, il existe des cas où les descripteurs personnalisés ont du sens, comme ceux de SLQAlchemy ou par exemple les validateurs personnalisés.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer