221 votes

Est-il possible de modifier une variable en python qui est dans une portée externe, mais pas globale ?

Le code suivant est donné :

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

Pour le code dans B() fonction variable b est dans la portée externe, mais pas dans la portée globale. Est-il possible de modifier b variable de l'intérieur B() fonction ? Je peux sûrement le lire d'ici et print() mais comment le modifier ?

1voto

La réponse courte qui fonctionnera automatiquement.

J'ai créé une bibliothèque python pour résoudre ce problème spécifique. Elle est publiée sous licence libre, vous pouvez donc l'utiliser comme bon vous semble. Vous pouvez l'installer avec pip install seapie ou consultez la page d'accueil ici https://github.com/hirsimaki-markus/SEAPIE

user@pc:home$ pip install seapie

from seapie import Seapie as seapie
def A():
    b = 1

    def B():
        seapie(1, "b=2")
        print(b)

    B()
A()

sorties

2

les arguments ont la signification suivante :

  • Le premier argument est la portée de l'exécution. 0 signifie local B() , 1 signifie parent A() et 2 signifierait grand-parent <module> aka global
  • Le second argument est une chaîne de caractères ou un objet de code que vous voulez exécuter dans la portée donnée.
  • Vous pouvez également l'appeler sans arguments pour un shell interactif dans votre programme

La réponse longue

C'est plus compliqué. Seapie fonctionne en éditant les cadres dans la pile d'appels en utilisant l'api CPython. CPython est le standard de facto, donc la plupart des gens n'ont pas à s'en soucier.

Les mots magiques qui vous intéressent probablement le plus si vous lisez ceci sont les suivants :

frame = sys._getframe(1)          # 1 stands for previous frame
parent_locals = frame.f_locals    # true dictionary of parent locals
parent_globals = frame.f_globals  # true dictionary of parent globals

exec(codeblock, parent_globals, parent_locals)

ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only

Cette dernière forcera les mises à jour à passer dans la portée locale. Les portées locales sont cependant optimisées différemment de la portée globale, de sorte que l'introduction de nouveaux objets a quelques problèmes lorsque vous essayez de les appeler directement s'ils ne sont pas initialisés de quelque manière que ce soit. Je vais copier quelques façons de contourner ces problèmes à partir de la page github.

  • Assortir, importer et définir vos objets au préalable
  • Attribution préalable d'un placeholder à vos objets
  • Réassignez l'objet à lui-même dans le programme principal pour mettre à jour la table des symboles : x = locals()["x"]
  • Utilisez exec() dans le programme principal au lieu de l'appeler directement pour éviter l'optimisation. Au lieu d'appeler x, faites : exec("x")

Si vous pensez qu'utiliser exec() n'est pas quelque chose que vous voulez faire, vous pouvez émuler le comportement en mettant à jour le vrai dictionnaire local (pas celui retourné par locals()). Je vais copier un exemple de https://faster-cpython.readthedocs.io/mutable.html

import sys
import ctypes

def hack():
    # Get the frame object of the caller
    frame = sys._getframe(1)
    frame.f_locals['x'] = "hack!"
    # Force an update of locals array from locals dict
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                          ctypes.c_int(0))

def func():
    x = 1
    hack()
    print(x)

func()

Sortie :

hack!

0voto

Sideshow Bob Points 1649

Je ne pense pas que vous debe veulent faire ça. Les fonctions qui peuvent modifier les choses dans leur contexte environnant sont dangereuses, car ce contexte peut être écrit à l'insu de la fonction.

Vous pouvez le rendre explicite, soit en faisant de B une méthode publique et de C une méthode privée dans une classe (la meilleure façon probablement) ; soit en utilisant un type mutable tel qu'une liste et en le passant explicitement à C :

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()

-1voto

Michael Giba Points 71

Pour tous ceux qui envisagent cette solution beaucoup plus tard, il existe une solution plus sûre mais plus lourde. Sans avoir besoin de passer des variables comme paramètres.

def outer():
    a = [1]
    def inner(a=a):
        a[0] += 1
    inner()
    return a[0]

-2voto

Cédric Julien Points 24177

Vous pouvez le faire, mais vous devrez utiliser la fonction déclaration globale (ce n'est pas une très bonne solution, comme toujours lorsqu'on utilise des variables globales, mais cela fonctionne) :

def A():
    global b
    b = 1

    def B():
      global b
      print( b )
      b = 2

    B()
A()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X