1
votes

`__new__` et` __init__` sur la classe et l'objet

Dans Python 3, lors de la définition d'une sous-classe, pourquoi avez-vous besoin d'utiliser cls comme premier argument de __new__ , mais pas d'utiliser self code> comme premier argument de __init__ ?

Un exemple:

>>> cls = object
>>> self = cls()
>>> cls.__new__ is self.__new__
True
>>> cls.__init__ is self.__init__
False
>>> self.__init__()
>>> cls.__init__()
Traceback (most recent call last): ...

Quand j'ai comparé ces fonctions, je suis devenu plus confus: p >

class MyClass(object):
    def __new__(cls, *args, **kwargs):
        return super(MyClass, cls).__new__(cls, *args, **kwargs) # with `cls`
    def __init__(self, *args, **kwargs):
        return super(MyClass, self).__init__(*args, **kwargs) # without `self`

Alors, quelles sont les différences entre __new__ et __init__ derrière ces résultats? Quelles méthodes sont liées et lesquelles sont gratuites? Pourquoi pouvez-vous appeler self .__ init __ () mais pas cls .__ init __ () ? cls .__ init__ est-il une méthode définie dans cls lui-même ou dans sa métaclasse?


3 Réponses :


1
votes

cls représente la classe elle-même, tandis que self représente l'objet lui-même. Ce ne sont que des conventions.
La méthode __new__ est appelée avant la création de l'objet, en fait, __new__ doit créer l'objet et le renvoyer. Par conséquent, il a besoin d'une classe pour créer un objet. Après cela, le __init__ est appelé pour initialiser l'objet, il a donc besoin de l'objet comme premier argument.

Par exemple:

cls.__init__(self)

__new__ est une méthode statique, donc la classe et l'instance partagent la même méthode __new__ . __init__ est la méthode d'instance. Si vous voulez l'appeler via la classe, vous devez passer l'instance comme premier argument explicitement.

class MyClass:
    def __new__(cls, *args, **kwargs):
        # cls == MyClass
        return super().__new__(cls, *args, **kwargs)
        # cls here is because __new__ is staticmethods so you have to pass the cls explicitly

        # You can't use cls() here because it will call this methods again and again
        # causing recusion error

    def __init__(self, *args, **kwargs):
        # Here self is the instance(or object) of MyClass
        # So you can initialize it by self.xxx
        self.xxx = 'xxx'

Tout en Python est objet, y compris la classe elle-même. Donc, pour la classe, il a ses propres __new__ et __init__ qui sont utilisés par la métaclasse pour créer la classe et initialiser la classe .
Ce sont des métaprogrammations de Python, je suggère de lire le neuvième chapitre de Python Cookbook.


0 commentaires

4
votes

La plus grande partie de l'image qui vous manque probablement est que __new__ est une méthode statique, special-cased pour en être un même si vous n'utilisez pas le décorateur @staticmethod .

Lors de l'appel d'une méthode via super () , super () effectue le même type de liaison d'argument qui serait effectuée normalement pour ce type de méthode (en utilisant le a href = "https://docs.python.org/3/reference/datamodel.html#implementing-descriptors" rel = "nofollow noreferrer"> protocole de descripteur ). Pour une méthode statique comme __new__ , cela signifie qu'aucun argument n'est automatiquement lié, donc cls doit être passé explicitement. Pour une méthode d'instance comme __init__ , cela signifie que self est lié automatiquement, c'est pourquoi vous n'avez pas à passer self à super () .__ init__ .


6 commentaires

Je me demandais pourquoi __new__ est devenu une méthode statique mais pas une méthode de classe, alors que vous devez encore passer un argument de classe.


@Cyker: Avec une méthode statique, vous pouvez faire des choses comme object .__ new __ (peu importe) . Il serait beaucoup plus difficile de reproduire cette fonctionnalité avec une méthode de classe.


Mais pourquoi object .__ nouveau __ (peu importe) alors que vous pouvez n'importe quoi .__ nouveau __ () ?


@Cyker: Parce que vous voulez utiliser la méthode __new__ de object au lieu de celle de Whatever .


Alors pourquoi ne voulez-vous pas utiliser la méthode __init__ de object au lieu de celle de Whatever ?


@Cyker: object .__ new __ (peu importe) n'appelle aucune méthode __init__ du tout. Si vous voulez appeler object .__ init__ , vous pouvez l'appeler manuellement sur l'objet résultant, mais object .__ init__ n'effectue aucune initialisation. Dans tous les cas, la raison habituelle d'appeler une superclasse __new__ manuellement en dehors d'une sous-classe __new__ est que la sous-classe __new__ refuserait de créer l'objet .



1
votes

L'objectif principal de __new__ consiste à allouer une nouvelle instance de la classe, tandis que __ init__ est de configurer une instance existante.

D'après la documentation:

__new __ () est une méthode statique (avec une casse spéciale, vous n'avez donc pas besoin de la déclarer comme telle)

__init__ d'autre part, est une méthode d'instance appropriée. Il peut être appelé plusieurs fois sur la même instance, au fait.

Cela devrait suffire à expliquer votre session de terminal:

>>> cls.__init__(self)

Vous venez d'appeler object .__ call__ , ce qui fait essentiellement

>>> cls.__init__()

Notez que la valeur de retour de __new__ n'est pas obligatoirement une instance de la classe à laquelle il appartient, mais __init__ n'est appelé que si c'est le cas. Dans votre cas, c'est le cas.

>>> self.__init__()

__new__ est une méthode statique, donc tenter de la lier à l'instance ne fait rien: cela reste une classe méthode. C'est la même raison pour laquelle vous devez passer cls explicitement lors de l'appel de super () .__ new__ : c'est la même fonction gratuite, indépendante de la classe ou de l'instance. P >

 >>> cls.__init__ is self.__init__
 False

Non seulement ce ne sont pas la même chose, mais leurs types sont différents. cls .__ init__ est une fonction régulière. self .__ init__ est une méthode liée qui n'a pas le premier paramètre de cls.__init__.

>>> cls.__new__ is self.__new__
True

Cela a déjà été appelé, mais pour objet , c'est un no-op que vous pouvez appeler autant de fois que vous le souhaitez. Notez que le premier paramètre n'est pas passé, car il s'agit d'une méthode liée.

self = cls.__new__()
if isinstance(self, cls):
    cls.__init__(self)
return self

Ceci appelle la fonction brute __init__ , qui nécessite que le paramètre self soit passé. Comme vous ne le faites pas, il déclenche. Essayez plutôt ceci:

>>> cls = object
>>> self = cls()


4 commentaires

Si self .__ init __ () invoque la méthode __init__ définie dans type (self) , pourquoi cls .__ init __ () < / code> invoquer la méthode __init__ définie dans type (cls) ? Un objet de classe n'est-il pas aussi un objet?


@Cyker. Point intéressant. Étant donné qu'une méthode est un descripteur sans données, tout attribut de l'instance portant le même nom le remplacera. Si vous faites self .__ init__ = some_other_function , vous verrez que l'autre fonction est appelée lorsque vous faites self .__ init__ . Si vous n'aviez pas défini __init__ dans la classe, il utiliserait celui de la métaclasse. Il est intéressant de noter que vous ne pouvez pas remplacer les méthodes dunder comme celle-ci au niveau de l'instance car les méthodes dunder sont toujours appelées comme type (self) .__ dunder __ (self, ...) lorsqu'elles sont utilisées comme opérateurs.


Je pense que si je ne définis pas __init__ dans la classe, il utiliserait celui de sa superclasse. Seulement si la superclasse n'a pas cette méthode, elle utiliserait celle de sa métaclasse. Mais object .__ init__ existe toujours donc celui de sa métaclasse ne sera pas utilisé.


@Cyker correct. Object n'a pas de superclasse, ce qui simplifie ce cas.