web-dev-qa-db-fra.com

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 pourrais-je faire fonctionner cela?

51
Yugal Jindle

Vous ne pouvez pas vraiment définir cela comme valeur par défaut, car la valeur par défaut est évaluée lorsque la méthode est définie, c'est-à-dire avant que des instances n'existent. Le schéma 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 est None.


Pour illustrer le fonctionnement des valeurs par défaut, consultez 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 cela s'est produit avant que la fonction ne soit appelée!

63
Adam Wagner

En Python, le nom self est pas spécial. C'est juste une convention pour le nom du paramètre, c'est pourquoi il y a un paramètre self dans __init__. (En fait, __init__ N'est pas très spécial non plus, et en particulier, il pas crée réellement l'objet ... c'est une histoire plus longue)

C("abc").process() crée une instance C, recherche la méthode process dans la classe C et appelle cette méthode avec l'instance C comme premier paramètre. Il se retrouvera donc dans le paramètre self si vous l'avez fourni.

Même si vous aviez ce paramètre, vous ne seriez pas autorisé à écrire quelque chose comme def process(self, formatting = self.formatting), car self n'est pas encore dans la portée au point 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 modifications entre les appels à la fonction.)

Comment pourrais-je faire fonctionner cela?

La méthode traditionnelle consiste à utiliser None par défaut, à vérifier cette valeur et à 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 instance object est tout ce dont vous avez besoin, tant que vous la cachez afin 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 avec ==.

8
Karl Knechtel

Puisque vous souhaitez 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 pendant la classe '__init__ par exemple. C'est là que vous avez accès aux attributs spécifiques à l'instance.

Une approche consiste à utiliser functools.partial afin d'obtenir une version mise à jour (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 définir la méthode dans __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, mais l'ordre de résolution de la méthode devient moins apparent (ce qui peut affecter l'inspection IDE par exemple, mais je suppose qu'il y a IDE = solutions de contournement spécifiques pour cela).

Une autre approche serait de créer un type personnalisé pour ce type de "valeurs par défaut d'attribut d'instance" avec un décorateur spécial qui effectue le remplissage d'argument getattr correspondant:

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')
3
a_guest

"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 se réfère à l'objet lui-même. Vous ne pouvez pas passer "self" comme argument par défaut car sa position est fixe comme premier argument.

Dans votre cas, au lieu de "formatage = self.format", utilisez "formatage = Aucun", puis attribuez une valeur à partir du code comme suit:

[ÉDITER]

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"

Remarque: n'utilisez pas "format" comme nom de variable, car il s'agit d'une fonction intégrée en python

1
Yajushi

Au lieu de créer une liste d'if-thens qui couvrent vos arguments par défaut, on peut utiliser un dictionnaire "par défaut" et créer de nouvelles instances d'une classe en utilisant eval ():

class foo():
    def __init__(self,arg):
        self.arg = arg

class bar():
    def __init__(self,*args,**kwargs):
        #default values are given in a dictionary
        defaults = {'foo1':'foo()','foo2':'foo()'}
        for key in defaults.keys():

            #if key is passed through kwargs, use that value of that key
            if key in kwargs: setattr(self,key,kwargs[key]) 

            #if no key is not passed through kwargs
            #create a new instance of the default value
            else: setattr(self,key, eval(defaults[key]))

Je jette ceci au début de chaque classe qui instancie une autre classe comme argument par défaut. Cela évite python évaluant la valeur par défaut lors de la compilation ... J'adorerais une approche Pythonique plus propre, mais voilà).

0
noPounch

Vous ne pouvez pas accéder à self dans la définition de méthode. Ma solution 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
0
amolk