¿Es posible modificar una variable en python que se encuentra en un ámbito externo (encerrado), pero no global?

9 minutos de lectura

avatar de usuario de grigoryvp
grigoryvp

Considere este ejemplo:

def A():
    b = 1
    def B():
        # I can access 'b' from here.
        print(b)
        # But can i modify 'b' here?
    B()
A()

Para el código en el B función, la variable b está en un ámbito no global, envolvente (externo). como puedo modificar b desde dentro B? Me sale un error si lo pruebo directamente, y usando global no soluciona el problema ya que b no es mundial.


Implementos de Python léxico, no dinámico alcance – como casi todos los idiomas modernos. Las técnicas aquí no permitir el acceso a las variables de la persona que llama, a menos que la persona que llama también sea una función envolvente, porque la persona que llama no está dentro del alcance. Para obtener más información sobre este problema, consulte ¿Cómo puedo acceder a las variables de la persona que llama, incluso si no es un ámbito cerrado (es decir, implementar un ámbito dinámico)?.

  • Puedes mientras b es mutable. una asignación a b enmascarará el alcance exterior.

    – JimB

    9 de diciembre de 2011 a las 15:54

  • Es una de las vergüenzas de Python que nonlocal no se ha adaptado a 2.x. Es una parte intrínseca del soporte de cierre.

    –Glenn Maynard

    9 de diciembre de 2011 a las 18:28

  • Parece que usar listas no locales o usar listas, como se explicó anteriormente, no funciona bien con las clases. Python asume erróneamente que la variable estaría en la clase de alcance, en lugar de estar internamente en una de las clases de funciones.

    – GarouDan

    9 dic 2020 a las 19:45

  • El 3.x nonlocal La palabra clave se explica aquí: stackoverflow.com/questions/1261875/python-nonlocal-statement. La pregunta actual es mejor canónica la mayor parte del tiempo, ya que la mayoría de los que preguntan tendrán un problema que se resuelve con el nonlocal palabra clave, y no ya ser consciente de ello. Sin embargo, esa pregunta es una referencia útil, por ejemplo, para las personas que se han encontrado nonlocal en el código de otra persona.

    – Karl Knechtel

    3 de julio a las 19:08

Avatar de usuario de Adam Wagner
Adán Wagner

En Pitón 3utilizar el nonlocal palabra clave:

los nonlocal La declaración hace que los identificadores enumerados se refieran a variables enlazadas previamente en el ámbito envolvente más cercano, excluyendo los globales. Esto es importante porque el comportamiento predeterminado para el enlace es buscar primero en el espacio de nombres local. La declaración permite que el código encapsulado vuelva a enlazar variables fuera del ámbito local además del ámbito global (módulo).

def foo():
    a = 1
    def bar():
        nonlocal a
        a = 2
    bar()
    print(a)  # Output: 2

En Pitón 2use un objeto mutable (como una lista o un dictado) y mute el valor en lugar de reasignar una variable:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

Salidas:

[1, 1]

  • Una buena manera de hacer esto es class nonlocal: pass en el ámbito exterior. Después nonlocal.x se puede asignar a en el ámbito interno.

    – tipo todo

    10 de febrero de 2014 a las 21:54

  • @kindall muy ordenado, muchas gracias 🙂 probablemente necesite un nombre diferente porque rompe la compatibilidad con versiones posteriores. En python 3 es un conflicto de palabras clave y causará un SyntaxError. Quizás NonLocal ?

    – Adán Terrey

    26 de agosto de 2016 a las 0:36


  • o, dado que es técnicamente una clase, Nonlocal? 🙂

    – tipo todo

    08/09/2016 a las 21:22

  • Código de ejemplo aquí: stackoverflow.com/questions/1261875/python-nonlocal-statement

    – Sr-IDE

    6 de marzo de 2018 a las 14:54

avatar de usuario de chrisk
cristo

Puede usar una clase vacía para contener un alcance temporal. Es como el mutable pero un poco más bonito.

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

Esto produce el siguiente resultado interactivo:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

  • Es extraño que la clase con sus campos sea “visible” en una función interna pero las variables no, a menos que defina una variable externa con la palabra clave “no local”.

    – Celdor

    2 de febrero de 2018 a las 13:01

  • Establecer una clave en un diccionario inicializado en el ámbito externo también funciona.

    – Boris Verjovskiy

    23 de agosto de 2021 a las 8:43

Avatar de usuario de Mike Edwards
mike edwards

Soy un poco nuevo en Python, pero he leído un poco sobre esto. Creo que lo mejor que obtendrá es similar a la solución alternativa de Java, que es envolver su variable externa en una lista.

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

Editar: supongo que esto probablemente era cierto antes de Python 3. Parece que nonlocal es tu respuesta

  • Erm, ahora es verdad. Para Python 3.9, por ejemplo.

    – Yevgeni Kosmak

    30 de diciembre de 2021 a las 2:01

avatar de usuario de zchenah
zchenah

No, no se puede, al menos de esta manera.

Porque la “operación de configuración” creará un nuevo nombre en el ámbito actual, que cubre el exterior.

avatar de usuario de eyquem
eyquem

No sé si hay un atributo de una función que da el __dict__ del espacio exterior de la función cuando este espacio exterior no es el espacio global == el módulo, que es el caso cuando la función es una función anidada, en Python 3.

Pero en Python 2, que yo sepa, no existe tal atributo.

Entonces, las únicas posibilidades de hacer lo que quieres son:

1) usando un objeto mutable, como dijeron otros

2)

def A() :
    b = 1
    print 'b before B() ==', b

    def B() :
        b = 10
        print 'b ==', b
        return b

    b = B()
    print 'b after B() ==', b

A()

resultado

b before B() == 1
b == 10
b after B() == 10

.

No un

La solución de Cédric Julien tiene un inconveniente:

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

resultado

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

Lo global b después de la ejecución de A() ha sido modificado y puede que no lo desee

Ese es el caso solo si hay un objeto con identificador b en el espacio de nombres global

La respuesta corta que funcionará automáticamente

Creé una biblioteca de Python para resolver este problema específico. Se publica bajo la licencia, así que úsalo como quieras. Puedes instalarlo con pip install seapie o echa un vistazo a la página de inicio aquí 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()

salidas

2

los argumentos tienen el siguiente significado:

  • El primer argumento es el ámbito de ejecución. 0 significaría local B()1 significa padre A() y 2 significaría abuelo <module> también conocido como mundial
  • El segundo argumento es una cadena o un objeto de código que desea ejecutar en el ámbito dado
  • También puede llamarlo sin argumentos para shell interactivo dentro de su programa

la respuesta larga

Esto es más complicado. Seapie funciona editando los marcos en la pila de llamadas usando la API de CPython. CPython es el estándar de facto, por lo que la mayoría de las personas no tienen que preocuparse por eso.

Las palabras mágicas que probablemente te interesen más si estás leyendo esto son las siguientes:

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

Este último obligará a las actualizaciones a pasar al ámbito local. Sin embargo, los ámbitos locales se optimizan de manera diferente al ámbito global, por lo que la introducción de nuevos objetos tiene algunos problemas cuando intenta llamarlos directamente si no están inicializados de ninguna manera. Copiaré algunas formas de eludir estos problemas desde la página de github

  • Asigne, importe y defina sus objetos de antemano
  • Asignar marcador de posición a tus objetos de antemano
  • Reasigne el objeto a sí mismo en el programa principal para actualizar la tabla de símbolos: x = locales ()[“x”]
  • Use exec() en el programa principal en lugar de llamar directamente para evitar la optimización. En lugar de llamar a x haz: exec(“x”)

Si sientes que usar exec() no es algo con lo que quieras ir, puedes emular el comportamiento actualizando el verdadero diccionario local (no el devuelto por los locales()). Copio un ejemplo de https://cpython-más-rápido.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()

Producción:

hack!

Avatar de usuario de Sideshow Bob
Bob acto secundario

no creo que tu debería quiero hacer esto Las funciones que pueden alterar las cosas en su contexto circundante son peligrosas, ya que ese contexto puede escribirse sin el conocimiento de la función.

Podría hacerlo explícito, ya sea haciendo que B sea un método público y C un método privado en una clase (probablemente la mejor manera); o usando un tipo mutable como una lista y pasándolo explícitamente a C:

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

A()

  • ¿Cómo puedes escribir una función sin conocer las funciones anidadas dentro de ella? Las funciones anidadas y los cierres son una parte intrínseca de la función en la que están encerrados.

    –Glenn Maynard

    9 de diciembre de 2011 a las 18:26

  • Necesita conocer la interfaz de las funciones incluidas en la suya, pero no debería saber qué sucede dentro de ellas. Además, no se puede esperar que sepa lo que sucede en las funciones ellos llamar, etc! Si una función modifica un miembro no global o de clase, normalmente debería hacerlo explícito a través de su interfaz, es decir, tomarlo como un parámetro.

    – Bob acto secundario

    12 de diciembre de 2011 a las 11:19


  • Python no te obliga a ser tan bueno, por supuesto, de ahí el nonlocal palabra clave, pero depende de usted usarla con mucha precaución.

    – Bob acto secundario

    12 de diciembre de 2011 a las 11:20

  • @Bob: Nunca he encontrado que el uso de cierres como este sea peligroso en absoluto, aparte de las peculiaridades del idioma. Piense en los locales como una clase temporal y las funciones locales como métodos en la clase, y no es más complicado que eso. YMMV, supongo.

    –Glenn Maynard

    12 de diciembre de 2011 a las 23:23

¿Ha sido útil esta solución?