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?
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!
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 ==
.
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')
"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
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à).
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