Question

Supposons que je veuille exécuter du code, par exemple

    value += 5

à l'intérieur d'un espace de noms de mon propre (si le résultat est essentiellement mydict['value'] += 5). Il y a une exec() fonction, mais je dois passer une chaîne là:

    exec('value += 5', mydict) 

et les états passant comme des chaînes semble étrange (par exemple il n'est pas colorisé de cette façon). Peut-il être fait comme:

    def block():
        value += 5

    ???(block, mydict)

? Le candidat évident pour la dernière ligne a été exec(block.__code__, mydict), mais pas de chance: elle soulève UnboundLocalError au sujet value. Je crois qu'il exécute essentiellement block(), pas le code dans le bloc , si les affectations ne sont pas faciles - est-ce pas

Bien sûr, une autre solution possible serait de démonter block.__code__ ...

Pour votre information, je suis arrivé à la question en raison de ce fil . En outre, c'est pourquoi certains (moi indécis) appel à une nouvelle syntaxe

    using mydict: 
        value += 5

Notez comment cela ne jette pas l'erreur, mais ne change pas mydict soit:

    def block(value = 0):
        value += 5

    block(**mydict)
Était-ce utile?

La solution

Vous pouvez passer bytecode au lieu d'une chaîne à exec, il vous suffit de faire le bon bytecode dans le but:

>>> bytecode = compile('value += 5', '<string>', 'exec')
>>> mydict = {'value': 23}
>>> exec(bytecode, mydict)
>>> mydict['value']
28

Plus précisément, ...:

>>> import dis
>>> dis.dis(bytecode)
  1           0 LOAD_NAME                0 (value)
              3 LOAD_CONST               0 (5)
              6 INPLACE_ADD         
              7 STORE_NAME               0 (value)
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE        

les instructions de chargement et de stockage doivent être de la persuasion _NAME, et ce qui les rend si compile, alors ...:

>>> def f(): value += 5
... 
>>> dis.dis(f.func_code)
  1           0 LOAD_FAST                0 (value)
              3 LOAD_CONST               1 (5)
              6 INPLACE_ADD         
              7 STORE_FAST               0 (value)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

... code dans une fonction est optimisée pour utiliser les versions _FAST, et ceux qui ne fonctionnent pas sur un dict passé à exec. Si vous avez commencé en quelque sorte avec un bytecode en suivant les instructions de _FAST, vous pouvez patcher d'utiliser le _NAME type au lieu, par exemple avec bytecodehacks ou une approche similaire.

Autres conseils

Utilisez le mot-clé global pour forcer la portée dynamique sur toutes les variables que vous souhaitez modifier à partir du bloc:

def block():
    global value
    value += 5

mydict = {"value": 42}
exec(block.__code__, mydict)
print(mydict["value"])

Voici un décorateur fou de créer un tel bloc qui utilise des « locaux personnalisés ». En réalité, il est un hack pour transformer tous les accès variable à l'intérieur de la fonction d'accès global, et d'évaluer le résultat avec les sections locales de dictionnaire personnalisé comme environnement.

import dis
import functools
import types
import string

def withlocals(func):
    """Decorator for executing a block with custom "local" variables.

    The decorated function takes one argument: its scope dictionary.

    >>> @withlocals
    ... def block():
    ...     counter += 1
    ...     luckynumber = 88

    >>> d = {"counter": 1}
    >>> block(d)
    >>> d["counter"]
    2
    >>> d["luckynumber"]
    88
    """
    def opstr(*opnames):
        return "".join([chr(dis.opmap[N]) for N in opnames])

    translation_table = string.maketrans(
            opstr("LOAD_FAST", "STORE_FAST"),
            opstr("LOAD_GLOBAL", "STORE_GLOBAL"))

    c = func.func_code
    newcode = types.CodeType(c.co_argcount,
                             0, # co_nlocals
                             c.co_stacksize,
                             c.co_flags,
                             c.co_code.translate(translation_table),
                             c.co_consts,
                             c.co_varnames, # co_names, name of global vars
                             (), # co_varnames
                             c.co_filename,
                             c.co_name,
                             c.co_firstlineno,
                             c.co_lnotab)

    @functools.wraps(func)
    def wrapper(mylocals):
        return eval(newcode, mylocals)
    return wrapper

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Ceci est juste une adaptation de singe ragréage de recette brillante de quelqu'un pour une goto décorateur

Du commentaire de S. Lott ci-dessus, je pense que je reçois l'idée d'une réponse en utilisant la création d'une nouvelle catégorie.

class _(__metaclass__ = change(mydict)):
    value += 1
    ...

change est un métaclasse dont __prepare__ lit le dictionnaire et dont le dictionnaire des mises à jour __new__.

Pour la réutilisation, l'extrait ci-dessous fonctionnerait, mais il est un peu laid:

def increase_value(d):
    class _(__metaclass__ = change(d)):
        value += 1
        ...

increase_value(mydict)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top