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.





python-decorators