Breve descripción de las reglas de alcance

Resuelto Charles Merriam asked hace 16 años • 9 respuestas

¿Cuáles son exactamente las reglas de alcance de Python?

Si tengo algún código:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

¿ Dónde se xencuentra? Algunas opciones posibles incluyen la siguiente lista:

  1. En el archivo fuente adjunto
  2. En el espacio de nombres de la clase
  3. En la definición de la función
  4. En la variable de índice del bucle for
  5. Dentro del bucle for

También está el contexto durante la ejecución, cuando la función spamse pasa a otro lugar. ¿ Y tal vez las funciones lambda se ejecutan de manera un poco diferente?

Debe haber una referencia o algoritmo simple en alguna parte. Es un mundo confuso para los programadores intermedios de Python.

Charles Merriam avatar Nov 15 '08 08:11 Charles Merriam
Aceptado

En realidad, una regla concisa para la resolución del alcance de Python, de Learning Python, 3rd. Ed. . (Estas reglas son específicas de los nombres de variables, no de los atributos. Si hace referencia a ellas sin un punto, se aplican estas reglas).

Regla LEGB

  • L ocal: nombres asignados de cualquier forma dentro de una función ( defo lambda) y no declarados globales en esa función

  • Función envolvente : nombres asignados en el ámbito local de todas y cada una de las funciones envolventes estáticas ( defo lambda), desde el interior al exterior.

  • G lobal (módulo): nombres asignados en el nivel superior de un archivo de módulo o ejecutando una globalinstrucción defdentro del archivo.

  • Integrado (Python): nombres preasignados en el módulo de nombres integrado: open, range, SyntaxErroretc.

Entonces, en el caso de

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()

El forbucle no tiene su propio espacio de nombres. En orden LEGB, los alcances serían

  • L: Local en def spam(en code3, code4y code5)
  • E: Cualquier función adjunta (si todo el ejemplo estuviera en otro def)
  • G: ¿Hubo alguno xdeclarado globalmente en el módulo (en code1)?
  • B: Cualquier función integrada xen Python.

xnunca se encontrará code2(incluso en los casos en los que se podría esperar que lo haga, consulte la respuesta de Antti o aquí ).

Rizwan Kassim avatar Nov 15 '2008 12:11 Rizwan Kassim

Básicamente, lo único en Python que introduce un nuevo alcance es la definición de una función. Las clases son un caso un poco especial en el sentido de que cualquier cosa definida directamente en el cuerpo se coloca en el espacio de nombres de la clase, pero no se puede acceder directamente a ellas desde los métodos (o clases anidadas) que contienen.

En su ejemplo, solo hay 3 ámbitos donde se buscará x:

  • alcance del spam: contiene todo lo definido en code3 y code5 (así como code4, su variable de bucle)

  • El alcance global: contiene todo lo definido en el código 1, así como Foo (y cualquier cambio posterior).

  • El espacio de nombres incorporado. Un caso un poco especial: contiene varias funciones y tipos integrados de Python, como len() y str(). Generalmente esto no debería ser modificado por ningún código de usuario, así que espere que contenga las funciones estándar y nada más.

Más ámbitos solo aparecen cuando introduces una función anidada (o lambda) en la imagen. Sin embargo, estos se comportarán más o menos como cabría esperar. La función anidada puede acceder a todo lo que esté en el ámbito local, así como a cualquier cosa en el alcance de la función adjunta. p.ej.

def foo():
    x=4
    def bar():
        print x  # Accesses x from foo's scope
    bar()  # Prints 4
    x=5
    bar()  # Prints 5

Restricciones:

Se puede acceder a variables en ámbitos distintos de las variables de la función local, pero no se pueden revertir a nuevos parámetros sin más sintaxis. En cambio, la asignación creará una nueva variable local en lugar de afectar la variable en el ámbito principal. Por ejemplo:

global_var1 = []
global_var2 = 1

def func():
    # This is OK: It's just accessing, not rebinding
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable
    global_var2 = 2 

    local1 = 4
    def embedded_func():
        # Again, this doen't affect func's local1 variable.  It creates a 
        # new local variable also called local1 instead.
        local1 = 5
        print local1

    embedded_func() # Prints 5
    print local1    # Prints 4

Para modificar realmente los enlaces de variables globales desde dentro del alcance de una función, debe especificar que la variable es global con la palabra clave global. P.ej:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1

Actualmente no hay forma de hacer lo mismo con las variables en los ámbitos de funciones adjuntas , pero Python 3 introduce una nueva palabra clave, " nonlocal" que actuará de forma similar a global, pero para ámbitos de funciones anidadas.

Brian avatar Nov 15 '2008 21:11 Brian

No hubo una respuesta completa sobre el tiempo de Python3, así que respondí aquí. La mayor parte de lo que se describe aquí se detalla en 4.2.2 Resolución de nombres de la documentación de Python 3.

Como se proporciona en otras respuestas, hay 4 ámbitos básicos, LEGB, para local, envolvente, global e integrado. Además de estos, existe un ámbito especial, el cuerpo de la clase , que no comprende un ámbito adjunto para los métodos definidos dentro de la clase; cualquier asignación dentro del cuerpo de la clase hace que la variable a partir de ahí esté vinculada al cuerpo de la clase.

Especialmente, sin declaración de bloque, además defy class, crea un alcance variable. En Python 2, una lista por comprensión no crea un alcance variable; sin embargo, en Python 3, la variable de bucle dentro de la lista por comprensión se crea en un nuevo alcance.

Demostrar las peculiaridades del cuerpo de clase.

x = 0
class X(object):
    y = x
    x = x + 1 # x is now a variable
    z = x

    def method(self):
        print(self.x) # -> 1
        print(x)      # -> 0, the global x
        print(y)      # -> NameError: global name 'y' is not defined

inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)

Por lo tanto, a diferencia del cuerpo de la función, puede reasignar la variable al mismo nombre en el cuerpo de la clase, para obtener una variable de clase con el mismo nombre; En su lugar, búsquedas adicionales sobre este nombre se resuelven en la variable de clase.


Una de las mayores sorpresas para muchos recién llegados a Python es que un forbucle no crea un alcance variable. En Python 2, las listas por comprensión tampoco crean un alcance (¡mientras que los generadores y las comprensiones de dictados sí lo hacen!). En lugar de eso, filtran el valor en la función o el alcance global:

>>> [ i for i in range(5) ]
>>> i
4

Las comprensiones se pueden usar como una forma astuta (o horrible si se quiere) de crear variables modificables dentro de expresiones lambda en Python 2: una expresión lambda crea un alcance variable, como lo defharía la declaración, pero dentro de lambda no se permiten declaraciones. Que la asignación sea una declaración en Python significa que no se permiten asignaciones de variables en lambda, pero una lista por comprensión es una expresión...

Este comportamiento se ha solucionado en Python 3: no hay expresiones de comprensión ni variables que filtren los generadores.


Lo global realmente significa el alcance del módulo; el módulo principal de Python es __main__; todos los módulos importados son accesibles a través de la sys.modulesvariable; para obtener acceso a __main__uno se puede utilizar sys.modules['__main__'], o import __main__; es perfectamente aceptable acceder y asignar atributos allí; aparecerán como variables en el alcance global del módulo principal.


Si alguna vez se asigna un nombre en el ámbito actual (excepto en el ámbito de la clase), se considerará que pertenece a ese ámbito; de lo contrario, se considerará que pertenece a cualquier ámbito adjunto que se asigne a la variable (es posible que no se asigne). todavía, o no en absoluto), o finalmente el alcance global. Si la variable se considera local, pero aún no está configurada o se ha eliminado, la lectura del valor de la variable dará como resultado UnboundLocalError, que es una subclase de NameError.

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

El alcance puede declarar que desea modificar explícitamente la variable global (alcance del módulo), con la palabra clave global:

x = 5
def foobar():
    global x
    print(x)
    x += 1

foobar() # -> 5
print(x) # -> 6

Esto también es posible incluso si se sombreó en un ámbito adjunto:

x = 5
y = 13
def make_closure():
    x = 42
    y = 911
    def func():
        global x # sees the global value
        print(x, y)
        x += 1

    return func

func = make_closure()
func()      # -> 5 911
print(x, y) # -> 6 13

En Python 2 no hay una manera fácil de modificar el valor en el ámbito adjunto; normalmente esto se simula teniendo un valor mutable, como una lista con una longitud de 1:

def make_closure():
    value = [0]
    def get_next_value():
        value[0] += 1
        return value[0]

    return get_next_value

get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2

Sin embargo, en Python 3, nonlocalviene al rescate:

def make_closure():
    value = 0
    def get_next_value():
        nonlocal value
        value += 1
        return value
    return get_next_value

get_next = make_closure() # identical behavior to the previous example.

La nonlocaldocumentación dice que

Los nombres enumerados en una declaración no local, a diferencia de los enumerados en una declaración global, deben hacer referencia a enlaces preexistentes en un ámbito adjunto (el ámbito en el que se debe crear un nuevo enlace no se puede determinar sin ambigüedades).

es decir, nonlocalsiempre se refiere al ámbito no global externo más interno al que se ha vinculado el nombre (es decir, asignado, incluido el uso como forvariable de destino, en la withcláusula o como parámetro de función).


Cualquier variable que no se considere local en el ámbito actual, o en cualquier ámbito adjunto, es una variable global. Se busca un nombre global en el diccionario global del módulo; si no se encuentra, se busca el global desde el módulo integrado; el nombre del módulo se cambió de python 2 a python 3; en Python 2 lo era __builtin__y en Python 3 ahora se llama builtins. Si asigna un atributo del módulo incorporado, será visible a partir de entonces para cualquier módulo como una variable global legible, a menos que ese módulo los sombree con su propia variable global con el mismo nombre.


Leer el módulo incorporado también puede resultar útil; Supongamos que desea la función de impresión de estilo Python 3 en algunas partes del archivo, pero otras partes del archivo aún usan la printdeclaración. En Python 2.6-2.7 puedes hacerte con la printfunción Python 3 con:

import __builtin__

print3 = __builtin__.__dict__['print']

En from __future__ import print_functionrealidad, no importa la printfunción en ninguna parte de Python 2; en cambio, simplemente deshabilita las reglas de análisis para printla declaración en el módulo actual, manejándola printcomo cualquier otro identificador de variable y, por lo tanto, permitiendo que la printfunción se busque en las funciones integradas.

Un ejemplo de alcance un poco más completo:

from __future__ import print_function  # for python 2 support

x = 100
print("1. Global x:", x)
class Test(object):
    y = x
    print("2. Enclosed y:", y)
    x = x + 1
    print("3. Enclosed x:", x)

    def method(self):
        print("4. Enclosed self.x", self.x)
        print("5. Global x", x)
        try:
            print(y)
        except NameError as e:
            print("6.", e)

    def method_local_ref(self):
        try:
            print(x)
        except UnboundLocalError as e:
            print("7.", e)
        x = 200 # causing 7 because has same name
        print("8. Local x", x)

inst = Test()
inst.method()
inst.method_local_ref()

producción:

1. Global x: 100
2. Enclosed y: 100
3. Enclosed x: 101
4. Enclosed self.x 101
5. Global x 100
6. global name 'y' is not defined
7. local variable 'x' referenced before assignment
8. Local x 200
brianray avatar Dec 04 '2015 17:12 brianray