Pourquoi avez-vous besoin d'avoir explicitement l'argument «self» dans une méthode Python?


197

Lors de la définition d'une méthode sur une classe en Python, cela ressemble à ceci:

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

Mais dans certains autres langages, tels que C #, vous avez une référence à l'objet auquel la méthode est liée avec le mot clé "this" sans le déclarer comme argument dans le prototype de la méthode.

Était-ce une décision intentionnelle de conception de langage en Python ou y a-t-il des détails d'implémentation qui nécessitent le passage de "self" comme argument?


15
Je parie que vous seriez également intéressé de savoir pourquoi vous devez écrire explicitement selfpour accéder aux membres - stackoverflow.com/questions/910020/…
Piotr Dobrogost

1
Mais cela ressemble un peu à une plaque de chaudière
Raghuveer

Un peu déroutant mais mérite d'être compris stackoverflow.com/a/31367197/1815624
CrandellWS

Réponses:


91

J'aime citer le Zen de Python de Peters. "Explicite vaut mieux qu'implicite."

En Java et C ++, 'this. ' peut être déduit, sauf lorsque vous avez des noms de variables qui ne permettent pas de déduire. Vous en avez donc parfois besoin et parfois pas.

Python choisit de rendre des choses comme cela explicites plutôt que basées sur une règle.

De plus, comme rien n'est implicite ou supposé, des parties de l'implémentation sont exposées. self.__class__, self.__dict__et d'autres structures "internes" sont disponibles de manière évidente.


53
Bien qu'il serait bien d'avoir un message d'erreur moins cryptique lorsque vous l'oublierez.
Martin Beckett

9
Cependant, lorsque vous appelez une méthode, vous n'avez pas à transmettre de variable d'objet, cela ne rompt-il pas la règle de l'explicitness? Si pour garder ce zen, cela doit être quelque chose comme: object.method (object, param1, param2). Semble en quelque sorte incohérent ...
Vedmant

10
"explicite vaut mieux qu'implicite" - Le "style" de Python n'est-il pas construit autour de choses implicites? par exemple, types de données implicites, limites de fonctions implicites (pas de {}), portée de variable implicite ... si des variables globales dans les modules sont disponibles dans les fonctions ... pourquoi ne pas appliquer la même logique / raisonnement aux classes? La règle simplifiée ne serait-elle pas simplement «tout ce qui est déclaré à un niveau supérieur est disponible à un niveau inférieur» tel que déterminé par l'indentation?
Simon

13
"Explicite vaut mieux qu'implicite" Un non-sens détecté
Vahid Amiri

10
avouons-le, c'est juste mauvais. Il n'y a aucune excuse à cela. C'est juste une relique laide mais ça va.
Toskan

63

C'est pour minimiser la différence entre les méthodes et les fonctions. Il vous permet de générer facilement des méthodes dans des métaclasses ou d'ajouter des méthodes au moment de l'exécution à des classes préexistantes.

par exemple

>>> class C(object):
...     def foo(self):
...         print "Hi!"
...
>>>
>>> def bar(self):
...     print "Bork bork bork!"
...
>>>
>>> c = C()
>>> C.bar = bar
>>> c.bar()
Bork bork bork!
>>> c.foo()
Hi!
>>>

Cela (à ma connaissance) facilite également l'implémentation du runtime python.


10
+1 pour C'est pour minimiser la différence entre les méthodes et les fonctions. Cette réponse doit être acceptée
utilisateur

Ceci est également à l'origine de l'explication très liée de guido.
Marcin

1
Cela montre également qu'en Python, lorsque vous faites c.bar (), il vérifie d'abord les instances pour les attributs, puis il vérifie les attributs de classe . Ainsi, vous pouvez «attacher» une donnée ou une fonction (objets) à une classe à tout moment et vous attendre à y accéder dans son instance (c'est-à-dire que dir (instance) le fera). Pas seulement lorsque vous avez "créé" une instance c. C'est très dynamique.
Nishant

10
Je ne l'achète pas vraiment. Même dans les cas où vous avez besoin de la classe parent, vous pouvez toujours la déduire lors de l'exécution. Et l'équivalence entre les méthodes d'instance et les fonctions de classe des instances passées est idiote; Ruby s'en sort très bien sans eux.
zachaysan

2
JavaScript vous permet d'ajouter des méthodes à un objet au moment de l'exécution, et cela ne nécessite pas selfdans la déclaration de fonction (attention, peut-être que cela jette des pierres d'une maison en verre, car JavaScript a une thissémantique de liaison assez délicate )
Jonathan Benn

55

Je suggère que l'on lise le blog de Guido van Rossum sur ce sujet - Pourquoi l'auto explicite doit rester .

Lorsqu'une définition de méthode est décorée, nous ne savons pas si lui donner automatiquement un paramètre «self» ou non: le décorateur pourrait transformer la fonction en une méthode statique (qui n'a pas de «self»), ou une méthode de classe (qui a un drôle de soi qui fait référence à une classe au lieu d'une instance), ou il pourrait faire quelque chose de complètement différent (il est trivial d'écrire un décorateur qui implémente '@classmethod' ou '@staticmethod' en Python pur). Il n'y a aucun moyen sans savoir ce que fait le décorateur de doter la méthode définie d'un argument implicite de «soi» ou non.

Je rejette les hacks comme le casse spéciale '@classmethod' et '@staticmethod'.


16

Python ne vous oblige pas à utiliser "self". Vous pouvez lui donner le nom que vous voulez. Vous devez juste vous rappeler que le premier argument dans un en-tête de définition de méthode est une référence à l'objet.


Par convention, il doit cependant être «self» pour les instances ou «cls» où des types sont impliqués (mmmm métaclasses)
pobk

1
Cela oblige à mettre soi-même comme premier paramètre dans chaque méthode, juste du texte supplémentaire qui n'a pas beaucoup de sens pour moi. D'autres langues fonctionnent très bien avec cela.
Vedmant

ai-je raison ? toujours le premier paramètre est une référence à l'objet.
Mohammad Mahdi KouchakYazdi

@MMKY Non, par exemple avec @staticmethodce n'est pas le cas.
Mark

1
"Vous devez juste vous rappeler que le premier argument dans une définition de méthode ..." J'ai expérimenté en changeant le mot "self" en "kwyjibo" et cela a toujours fonctionné. Donc, comme cela est souvent expliqué, ce n'est pas le mot «soi» qui est important mais la position de tout ce qui occupe cet espace (?)
RBV

7

Vous permet également de faire ceci: (en bref, invoquer Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)retournera 12, mais le fera de la manière la plus folle.

class Outer(object):
    def __init__(self, outer_num):
        self.outer_num = outer_num

    def create_inner_class(outer_self, inner_arg):
        class Inner(object):
            inner_arg = inner_arg
            def weird_sum_with_closure_scope(inner_self, num)
                return num + outer_self.outer_num + inner_arg
        return Inner

Bien sûr, c'est plus difficile à imaginer dans des langages comme Java et C #. En rendant l'auto-référence explicite, vous êtes libre de faire référence à n'importe quel objet par cette auto-référence. De plus, une telle façon de jouer avec des classes à l'exécution est plus difficile à faire dans les langages les plus statiques - ce n'est pas nécessairement une bonne ou une mauvaise chose. C'est juste que le moi explicite permet à toute cette folie d'exister.

De plus, imaginez ceci: nous aimerions personnaliser le comportement des méthodes (pour le profilage, ou de la magie noire folle). Cela peut nous amener à penser: et si nous avions une classeMethod dont nous pouvions outrepasser ou contrôler le comportement?

Et bien voilà:

from functools import partial

class MagicMethod(object):
    """Does black magic when called"""
    def __get__(self, obj, obj_type):
        # This binds the <other> class instance to the <innocent_self> parameter
        # of the method MagicMethod.invoke
        return partial(self.invoke, obj)


    def invoke(magic_self, innocent_self, *args, **kwargs):
        # do black magic here
        ...
        print magic_self, innocent_self, args, kwargs

class InnocentClass(object):
    magic_method = MagicMethod()

Et maintenant: InnocentClass().magic_method()agira comme prévu. La méthode sera liée au innocent_selfparamètre to InnocentClasset magic_selfà l'instance MagicMethod. Bizarre hein? C'est comme avoir 2 mots clés this1etthis2 dans des langages comme Java et C #. La magie comme celle-ci permet aux frameworks de faire des choses qui seraient autrement beaucoup plus verbeuses.

Encore une fois, je ne veux pas commenter l'éthique de ce genre de choses. Je voulais juste montrer des choses qui seraient plus difficiles à faire sans une référence explicite.


4
Quand je considère votre premier exemple, je peux faire la même chose en Java: la classe interne doit appeler OuterClass.thispour obtenir le «soi» de la classe externe, mais vous pouvez toujours l'utiliser thiscomme référence à elle-même; très similaire à ce que vous faites ici en Python. Pour moi, ce n'était pas plus difficile à imaginer. Cela dépend peut-être de sa maîtrise de la langue en question?
klaar

Mais pouvez-vous toujours faire référence à des étendues lorsque vous êtes à l'intérieur d'une méthode d'une classe anonyme, qui est définie à l'intérieur d'une classe anonyme, qui est définie à l'intérieur d'une implémentation anonyme d'interface Something, qui est à son tour définie à l'intérieur d'une autre implémentation anonyme de Something? En python, vous pouvez bien sûr vous référer à n'importe quelle étendue.
vlad-ardelean

Vous avez raison, en Java, vous ne pouvez faire référence à la classe externe qu'en appelant son nom de classe explicite et l'utiliser comme préfixe this. Les références implicites sont impossibru en Java.
klaar

Je me demande si cela fonctionnerait cependant: dans chaque portée (chaque méthode) ont une variable locale, qui fait référence au thisrésultat. Par exemple Object self1 = this;(utilisez Object ou quelque chose de moins générique). Ensuite, si vous avez accès à la variable dans les champs plus, vous pouvez avoir accès à self1, self2... selfn. Je pense que ceux-ci devraient être déclarés définitifs ou quelque chose, mais cela pourrait fonctionner.
vlad-ardelean

3

Je pense que la vraie raison en plus de "Le Zen de Python" est que les Fonctions sont des citoyens de première classe en Python.

Ce qui en fait essentiellement un objet. Maintenant, le problème fondamental est que si vos fonctions sont également des objets, dans le paradigme orienté objet, comment enverriez-vous des messages aux objets lorsque les messages eux-mêmes sont des objets?

Ressemble à un problème d'oeuf de poule, pour réduire ce paradoxe, la seule façon possible est de passer un contexte d'exécution aux méthodes ou de le détecter. Mais comme python peut avoir des fonctions imbriquées, il serait impossible de le faire car le contexte d'exécution changerait pour les fonctions internes.

Cela signifie que la seule solution possible est de passer explicitement «soi» (le contexte d'exécution).

Je pense donc que c'est un problème de mise en œuvre que le Zen est venu beaucoup plus tard.


Salut, je suis nouveau sur python (de fond java) et je n'ai pas tout à fait coulé ce que vous avez dit "comment enverriez-vous des messages aux objets lorsque les messages eux-mêmes sont des objets". Pourquoi est-ce un problème, pouvez-vous élaborer?
Qiulang

1
@Qiulang Aah, en programmation orientée objet, appeler des méthodes sur des objets équivaut à envoyer des messages à des objets avec ou sans charge utile (paramètres de votre fonction). Les méthodes internes seraient représentées comme un bloc de code associé à une classe / un objet et utilisent l'environnement implicite mis à sa disposition via l'objet contre lequel il est invoqué. Mais si vos méthodes sont des objets, elles peuvent exister indépendamment d'être associées à une classe / un objet, ce qui pose la question si vous invoquez cette méthode contre quel environnement s'exécuterait-elle?
pankajdoharey

Ainsi, il doit y avoir un mécanisme pour fournir un environnement, self signifierait l'environnement actuel au point d'exécution, mais vous pouvez également fournir un autre environnement.
pankajdoharey

2

Je pense que cela a à voir avec le PEP 227:

Les noms dans la portée de classe ne sont pas accessibles. Les noms sont résolus dans l'étendue de fonction englobante la plus interne. Si une définition de classe se produit dans une chaîne d'étendues imbriquées, le processus de résolution ignore les définitions de classe. Cette règle empêche les interactions étranges entre les attributs de classe et l'accès aux variables locales. Si une opération de liaison de nom se produit dans une définition de classe, elle crée un attribut sur l'objet de classe résultant. Pour accéder à cette variable dans une méthode, ou dans une fonction imbriquée dans une méthode, une référence d'attribut doit être utilisée, soit via self, soit via le nom de classe.


1

Comme expliqué dans self en Python, Demystified

quelque chose comme obj.meth (args) devient Class.meth (obj, args). Le processus appelant est automatique tandis que le processus récepteur ne l'est pas (son explicite). C'est la raison pour laquelle le premier paramètre d'une fonction en classe doit être l'objet lui-même.

class Point(object):
    def __init__(self,x = 0,y = 0):
        self.x = x
        self.y = y

    def distance(self):
        """Find distance from origin"""
        return (self.x**2 + self.y**2) ** 0.5

Invocations:

>>> p1 = Point(6,8)
>>> p1.distance()
10.0

init () définit trois paramètres mais nous venons d'en passer deux (6 et 8). De même distance () nécessite un mais aucun argument n'a été passé.

Pourquoi Python ne se plaint-il pas de cette différence de nombre d'arguments ?

Généralement, lorsque nous appelons une méthode avec quelques arguments, la fonction de classe correspondante est appelée en plaçant l'objet de la méthode avant le premier argument. Donc, quelque chose comme obj.meth (args) devient Class.meth (obj, args). Le processus appelant est automatique tandis que le processus récepteur ne l'est pas (son explicite).

C'est la raison pour laquelle le premier paramètre d'une fonction en classe doit être l'objet lui-même. L'écriture de ce paramètre en tant que self n'est qu'une convention . Ce n'est pas un mot-clé et n'a pas de signification particulière en Python. Nous pourrions utiliser d'autres noms (comme celui-ci) mais je vous suggère fortement de ne pas le faire. L'utilisation de noms autres que self est mal vue par la plupart des développeurs et dégrade la lisibilité du code ("La lisibilité compte").
...
Dans, le premier exemple self.x est un attribut d'instance tandis que x est une variable locale. Ils ne sont pas identiques et se trouvent dans des espaces de noms différents.

Le moi est là pour rester

Beaucoup ont proposé de faire de soi un mot-clé en Python, comme celui-ci en C ++ et Java. Cela éliminerait l'utilisation redondante de soi explicite de la liste de paramètres formelle dans les méthodes. Bien que cette idée semble prometteuse, elle ne se réalisera pas. Du moins pas dans un avenir proche. La raison principale est la compatibilité descendante . Voici un blog du créateur de Python lui-même expliquant pourquoi le moi explicite doit rester.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.