python - variable - pyplot title plot
Comment injecter variable dans la portée avec un décorateur? (6)
[Avertissement: il y a peut-être plus de façons pythoniques de faire ce que je veux faire, mais je veux savoir comment fonctionne la portée de python ici]
J'essaie de trouver un moyen de faire un décorateur qui fait quelque chose comme injecter un nom dans le cadre d'une autre fonction (de sorte que le nom ne fuit pas en dehors du cadre du décorateur). Par exemple, si j’ai une fonction qui dit d’imprimer une variable nommée var
qui n’a pas été définie, j’aimerais la définir dans un décorateur où elle s’appelle. Voici un exemple qui casse:
c = 'Message'
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
var = value
res = f(*args, **kwargs)
return res
return inner_dec
return msg_decorator
@decorator_factory(c)
def msg_printer():
print var
msg_printer()
Je voudrais qu'il affiche " Message
", mais cela donne:
NameError: global name 'var' is not defined
Le traceback indique même où var
est défini:
<ipython-input-25-34b84bee70dc> in inner_dec(*args, **kwargs)
8 def inner_dec(*args, **kwargs):
9 var = value
---> 10 res = f(*args, **kwargs)
11 return res
12 return inner_dec
Donc, je ne comprends pas pourquoi il ne peut pas trouver var
.
Y a-t-il un moyen de faire quelque chose comme ça?
En supposant que les fonctions python soient des objets, vous pouvez faire ...
#!/usr/bin/python3
class DecorClass(object):
def __init__(self, arg1, arg2):
self.a1 = arg1
self.a2 = arg2
def __call__(self, function):
def wrapped(*args):
print('inside class decorator >>')
print('class members: {0}, {1}'.format(self.a1, self.a2))
print('wrapped function: {}'.format(args))
function(*args, self.a1, self.a2)
return wrapped
@DecorClass(1, 2)
def my_function(f1, f2, *args):
print('inside decorated function >>')
print('decorated function arguments: {0}, {1}'.format(f1, f2))
print('decorator class args: {}'.format(args))
if __name__ == '__main__':
my_function(3, 4)
et le résultat est:
inside class decorator >>
class members: 1, 2
wrapped function: (3, 4)
inside decorated function >>
decorated function arguments: 3, 4
decorator class args: (1, 2)
plus d'explications ici http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html
Il existe un moyen propre de faire ce que vous voulez sans utiliser de variable globale. Si vous voulez être sans état et que les threads sont en sécurité, vous n'avez pas vraiment le choix.
Utilisez la variable "kwargs":
c = 'Message'
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
kwargs["var"] = value
res = f(*args, **kwargs)
return res
return inner_dec
return msg_decorator
@decorator_factory(c)
def msg_printer(*args, **kwargs):
print kwargs["var"]
msg_printer()
Tu ne peux pas. Les noms étendus (fermetures) sont déterminés au moment de la compilation, vous ne pouvez pas en ajouter d'autres au moment de l'exécution.
Le mieux que vous puissiez espérer est d’ajouter des noms globaux , en utilisant le propre espace de noms global de la fonction:
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
g = f.__globals__ # use f.func_globals for py < 2.6
sentinel = object()
oldvalue = g.get('var', sentinel)
g['var'] = value
try:
res = f(*args, **kwargs)
finally:
if oldvalue is sentinel:
del g['var']
else:
g['var'] = oldvalue
return res
return inner_dec
return msg_decorator
f.__globals__
est l'espace de noms global de la fonction f.__globals__
, donc cela fonctionne même si le décorateur habite dans un module différent. Si var
été défini comme global, il est remplacé par la nouvelle valeur et, après avoir appelé la fonction, les globales sont restaurés.
Cela fonctionne parce que tout nom dans une fonction non affectée et introuvable dans une portée environnante est marqué comme global.
Démo:
>>> c = 'Message'
>>> @decorator_factory(c)
... def msg_printer():
... print var
...
>>> msg_printer()
Message
>>> 'var' in globals()
False
Mais au lieu de décorer, j'aurais tout aussi bien pu définir directement var
dans la portée globale.
Notez que la modification des paramètres globaux n’est pas thread-safe, et tout appel transitoire à d’autres fonctions dans le même module conservera également le même résultat global.
Tu ne peux pas. Python a une portée lexicale . Cela signifie que la signification d'un identifiant est déterminée uniquement en fonction des portées qui l'entourent physiquement lorsque vous examinez le code source.
Voici une démonstration simple d'utilisation d'un décorateur pour ajouter une variable dans l'étendue d'une fonction.
>>> def add_name(name):
... def inner(func):
... # Same as defining name within wrapped
... # function.
... func.func_globals['name'] = name
...
... # Simply returns wrapped function reference.
... return func
...
... return inner
...
>>> @add_name("Bobby")
... def say_hello():
... print "Hello %s!" % name
...
>>> print say_hello()
Hello Bobby!
>>>
def merge(d1, d2):
d = d1.copy()
d.update(d2)
return d
# A decorator to inject variables
def valueDecorator(*_args, **_kargs):
def wrapper(f):
def wrapper2(*args, **kargs):
return f(*args, **kargs)
wrapper2.__name__ = f.__name__
wrapper2.__doc__ = f.__doc__
oldVars = getattr(f, 'Vars', [])
oldNamedVars = getattr(f, 'NamedVars', {})
wrapper2.Vars = oldVars + list(_args)
wrapper2.NamedVars = merge(oldNamedVars, _kargs)
return wrapper2
return wrapper
@valueDecorator(12, 13, a=2)
@valueDecorator(10, 11, a=1)
def func():
print(func.Vars)
print(func.NamedVars)
Au lieu de réviser la portée globale, il est plus raisonnable de modifier la fonction annotée elle-même.