95 votes

Comment passer une valeur d'argument par défaut d'un membre d'instance à une méthode ?

Je veux passer un argument par défaut à une méthode d'instance en utilisant la valeur d'un attribut de l'instance :

class C:
    def __init__(self, format):
        self.format = format

    def process(self, formatting=self.format):
        print(formatting)

En essayant cela, j'obtiens le message d'erreur suivant :

NameError: name 'self' is not defined

Je veux que la méthode se comporte comme ceci :

C("abc").process()       # prints "abc"
C("abc").process("xyz")  # prints "xyz"

Quel est le problème ici, pourquoi cela ne fonctionne-t-il pas ? Et comment puis-je le faire fonctionner ?

109voto

Adam Wagner Points 7232

Vous ne pouvez pas vraiment définir cette valeur comme valeur par défaut, puisque la valeur par défaut est évaluée lorsque la méthode est définie, c'est-à-dire avant l'existence de toute instance. Le modèle habituel est de faire quelque chose comme ceci à la place :

class C:
    def __init__(self, format):
        self.format = format

    def process(self, formatting=None):
        if formatting is None:
            formatting = self.format
        print(formatting)

self.format ne sera utilisé que si formatting es None .


Pour illustrer le fonctionnement des valeurs par défaut, voyez cet exemple :

def mk_default():
    print("mk_default has been called!")

def myfun(foo=mk_default()):
    print("myfun has been called.")

print("about to test functions")
myfun("testing")
myfun("testing again")

Et la sortie ici :

mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.

Remarquez comment mk_default n'a été appelé qu'une seule fois, et ce, avant même que la fonction ne soit appelée !

11voto

Karl Knechtel Points 24349

En Python, le nom self es no spécial. Il s'agit simplement d'une convention pour le nom du paramètre, c'est pourquoi il y a une balise self dans __init__ . (En fait, __init__ n'est pas non plus très spécial, et en particulier, il fait no créer réellement l'objet... c'est une plus longue histoire)

C("abc").process() crée un C cherche le process dans la méthode C et appelle cette méthode avec la classe C comme premier paramètre. Elle se retrouvera donc dans le fichier self si vous l'avez fourni.

Cependant, même si vous aviez ce paramètre, vous ne seriez pas autorisé à écrire quelque chose comme def process(self, formatting = self.formatting) parce que self n'est pas encore en vigueur au moment où vous définissez la valeur par défaut. En Python, la valeur par défaut d'un paramètre est calculée lors de la compilation de la fonction, et "collée" à la fonction. (C'est la même raison pour laquelle, si vous utilisez une valeur par défaut comme [] cette liste se souviendra des changements entre les appels à la fonction).

Comment pourrais-je faire en sorte que ça marche ?

La méthode traditionnelle consiste à utiliser None comme valeur par défaut, et vérifier cette valeur pour la remplacer dans la fonction. Vous pouvez trouver qu'il est un peu plus sûr de créer une valeur spéciale à cet effet (une fonction object est tout ce dont vous avez besoin, tant que vous la masquez pour que le code appelant n'utilise pas la même instance) au lieu de None . Dans tous les cas, vous devez vérifier cette valeur avec is pas == .

6voto

a_guest Points 5059

Puisque vous voulez utiliser self.format comme argument par défaut, cela implique que la méthode doit être spécifique à l'instance (c'est-à-dire qu'il n'y a aucun moyen de la définir au niveau de la classe). Au lieu de cela, vous pouvez définir la méthode spécifique lors de la création de la classe. __init__ par exemple. C'est là que vous avez accès aux attributs spécifiques de l'instance.

Une approche consiste à utiliser functools.partial afin d'obtenir une version actualisée (spécifique) de la méthode :

from functools import partial

class C:
    def __init__(self, format):
        self.format = format
        self.process = partial(self.process, formatting=self.format)

    def process(self, formatting):
        print(formatting)

c = C('default')
c.process()
# c.process('custom')  # Doesn't work!
c.process(formatting='custom')

Notez qu'avec cette approche vous ne pouvez passer l'argument correspondant que par mot-clé, car si vous le fournissiez par position, cela créerait un conflit dans partial .

Une autre approche consiste à définir et à paramétrer la méthode dans le fichier __init__ :

from types import MethodType

class C:
    def __init__(self, format):
        self.format = format

        def process(self, formatting=self.format):
            print(formatting)

        self.process = MethodType(process, self)

c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')

Cela permet également de passer l'argument par position, cependant l'ordre de résolution de la méthode devient moins apparent (ce qui peut affecter l'inspection de l'IDE par exemple, mais je suppose qu'il existe des solutions de contournement spécifiques à l'IDE pour cela).

Une autre approche consisterait à créer un type personnalisé pour ce type de "défaut d'attribut d'instance", ainsi qu'un décorateur spécial qui exécute les fonctions correspondantes de l'attribut d'instance. getattr le remplissage des arguments :

import inspect

class Attribute:
    def __init__(self, name):
        self.name = name

def decorator(method):
    signature = inspect.signature(method)

    def wrapper(self, *args, **kwargs):
        bound = signature.bind(*((self,) + args), **kwargs)
        bound.apply_defaults()
        bound.arguments.update({k: getattr(self, v.name) for k, v in bound.arguments.items()
                                if isinstance(v, Attribute)})
        return method(*bound.args, **bound.kwargs)

    return wrapper

class C:
    def __init__(self, format):
        self.format = format

    @decorator
    def process(self, formatting=Attribute('format')):
        print(formatting)

c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')

2voto

amolk Points 767

Vous ne pouvez pas accéder à self dans la définition de la méthode. Ma solution de contournement est la suivante .

class Test:
  def __init__(self):
    self.default_v = 20

  def test(self, v=None):
    v = v or self.default_v
    print(v)

Test().test()
> 20

Test().test(10)
> 10

1voto

Yajushi Points 1144

"self" doit être passé comme premier argument à toutes les fonctions de classe si vous voulez qu'elles se comportent comme des méthodes non statiques.

il s'agit de l'objet lui-même. Vous ne pouvez pas passer "self" comme argument par défaut car sa position est fixée comme premier argument.

Dans votre cas, au lieu de "formatting=self.format", utilisez "formatting=None", puis attribuez la valeur du code comme ci-dessous :

[EDIT]

class c:
        def __init__(self, cformat):
            self.cformat = cformat

        def process(self, formatting=None):
            print "Formating---",formatting
            if formatting == None:
                formatting = self.cformat
                print formatting
                return formatting
            else:
                print formatting
                return formatting

c("abc").process()          # prints "abc"
c("abc").process("xyz")     # prints "xyz"

Note : n'utilisez pas "format" comme nom de variable, car il s'agit d'une fonction intégrée dans Python.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X