Contenido del capítulo

Guía completa sobre espacios de nombres y ámbito en Python. Aprende cómo se gestionan las variables y su alcance en diferentes contextos del código.

Duración estimada de todo el contenido:
Duración del vídeo:
Contiene 2 ejercicios Contiene 1 vídeo.
Tabla de contenidos
Logo

    ¿Qué significa el alcance en Python?

    Namespace y tipos de scope en Python (Espacios de nombres y ámbito)
    Namespace y tipos de scope en Python (Espacios de nombres y ámbito)

    El alcance en programación es la zona del código en la cual está accesible un elemento.

    Concretamente, en Python existen dos tipos principales de alcance: el alcance global y el alcance local.

    No obstante, también podemos encontrar el tipo de alcance enclosing (encerrado) y el built-in (predefinido).

    Entonces, nos encontramos ante la regla LEGB. Se trata de una forma de denominar a los diferentes niveles de alcances de Python.

    LEGB Scope Python
    • Local scope.
    • Enclosed scope.
    • Global scope.
    • Built-in scope.

    El alcance, también se conoce con el término “ámbito”.

    El término en inglés, para referirse al alcance o al ámbito, es scope.

    Por ejemplo, para decir “alcance local”, dirás local scope, y para decir “alcance global”, dirás global scope.

    Espacio publicitario

    En Python, los siguientes elementos crean alcances:

    • Módulo
    • Clase
    • Funciones (def y lambda)
    • Comprensiones (list, set y dict)
    • Expresiones generadoras

    También he de comentar que hay alcances especiales como el de los bloques except, que contiene una referencia a la excepción generada por una declaración try.

    Hablaré en otro capítulo sobre las excepciones, así que no le des mucha importancia a esta nota por el momento.

    Espacio publicitario

    Espacios de nombres

    Con el fin de entender correctamente lo que es el alcance, primero debes conocer lo que son los namespaces, conocidos en español como espacios de nombres.

    Los namespaces o espacios de nombres, son contenedores o colecciones de identificadores únicos que permiten organizar el código en áreas, y evitar conflictos de nombres.

    En Python, los espacios de nombres están almacenados en diccionarios. Cada módulo tiene sus propios diccionarios de espacios de nombres.

    En los diferentes apartados que tienes a continuación, te muestro como acceder a cada uno. Considero que ver las cosas en la consola, ayuda a tener una visión clara sobre los conceptos.

    Alcance local

    El alcance local es el alcance que tiene un elemento dentro de una función.

    Este alcance se aplica también en cierto modo a las clases, aunque debo detallar, que es algo diferente. Recuerda el encapsulamiento y los tipos de acceso a miembros (público, protegido (_) y privado (__)).

    Por este tipo de alcance, no podemos acceder a una variable que está dentro de una función.

    A continuación, tienes un ejemplo:

    def imprimir_nombre():
        nombre = "Programación Fácil"
        print(nombre)
    
    imprimir_nombre()
    Resultado en la consola
    Programación Fácil

    Al llamar a esta función, se ejecuta correctamente el print() que lleva en su interior. Por lo tanto, acabamos viendo el valor de la variable nombre en la consola.

    Espacio publicitario

    La variable nombre de este ejemplo, se trata de una variable con alcance local. Esta no puede accederse directamente desde fuera del bloque de código de la función. Está contenida dentro de ella.

    Si intentamos acceder desde fuera, recibiremos un error.

    Aquí tienes un ejemplo:

    def imprimir_nombre():
        nombre = "Programación Fácil"
    
    print(nombre)
    Error en la consola
    NameError: name 'nombre' is not defined.
    Error de nombre: el nombre 'nombre' no está definido.

    El error nos indica que el intérprete de Python no está encontrando la variable nombre, concretamente en la línea del print(). Es como si no estuviera definida en el código.

    Diccionario local

    Ahora, si queremos acceder al diccionario del alcance local, lo haremos mediante la función predefinida de Python llamada locals():

    def funcion():
        a = 10
        b = "Hola"
        c = 10.56
      
        # Imprimir las variables locales
        print(locals())
    
    funcion()
    Resultado en la consola
    {'a': 10, 'b': 'Hola', 'c': 10.56}

    Ten en cuenta que si utilizas locals() fuera de una función, a nivel de módulo, te va a devolver el diccionario global.

    Alcance global

    El alcance global es el alcance que tienen los elementos que se definen fuera de cualquier función. Estos elementos son accesibles, incluso desde dentro de funciones, y bloques de código como los que tienen los condicionales o bucles.

    En el siguiente ejemplo puedes apreciar este tipo de alcance:

    nombre = "PCMaster"
    
    def imprimir_nombre():
        nombre = "Programación Fácil"
        print(nombre)
    
    imprimir_nombre()
    Resultado en la consola
    Programación Fácil

    En primera instancia, la variable nombre ha sido declarada fuera de la función, lo que le da un alcance global. Con ello, es posible “reasignarla” dentro de la función.

    Al menos, esto parece ser lo que está ocurriendo.

    Espacio publicitario

    Al hacer esto, estamos realizando una mala práctica. No hay fallos, pero estamos enmarañando el código.

    Para que comprendas el porqué de lo que estoy diciendo, es necesario que atiendas a la advertencia (warning) que está apareciendo al hacer esa “reasignación” dentro de la función:

    Redefining name 'nombre' from outer scope (line x).

    Redefinición del nombre 'nombre' desde el ámbito exterior (línea x).

    Para ver los errores, advertencias e informaciones en la consola y en el código Python, deberás instalar las extensiones Pylance, Pylint y Error Lens, si estás utilizando Visual Studio Code.

    La advertencia está indicando que se está “redefiniendo” el nombre de variable nombre, desde un alcance externo a la función.

    Esto quiere decir que no se está reasignando la variable, si no, que estamos generando otra distinta dentro de la función.

    Entonces, tenemos dos variables diferentes, que se llaman igual, en el mismo código. En ningún momento se hace una reasignación.

    Esto lo puedes comprobar, imprimiendo desde los dos ámbitos:

    nombre = "PCMaster"
    
    def imprimir_nombre():
        nombre = "Programación Fácil"
        print(nombre) # Variable local
    
    imprimir_nombre() 
    print(nombre) # Variable global
    Resultado en la consola
    Programación Fácil
    PCMaster

    En el ámbito global, accederás a la variable global, y en el local, a la variable local.

    Espacio publicitario

    Diccionario global

    Para poder ver el diccionario global, solo tienes que utilizar la función predefinida de Python denominada globals(), sobre el propio módulo:

    nombre = "Programación Fácil"
    
    # Imprime el diccionario de espacio de nombres global
    print(globals())
    Resultado en la consola
    {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002B54B04BCB0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'e:\\Cursos\\Libro\\test.py', '__cached__': None, 'nombre': 'Programación Fácil'}

    Si te fijas, es un diccionario con claves y valores. Cada clave contiene un nombre del espacio de nombres. Ese nombre tiene siempre un valor asociado, nunca está vacío. Como poco, tendrá un None de valor.

    Si quieres acceder al diccionario de un módulo importado, lo puedes hacer mediante su nombre, y accediendo a la variable especial llamada __dict__:

    import random
    
    print(random.__dict__)

    No pongo la salida en consola debido a la longitud de esta.

    La función predefinida vars()

    La función predefinida vars() devuelve el atributo __dict__ para un módulo, clase, instancia o cualquier otro objeto con un atributo __dict__.

    Entonces, por ejemplo, podemos hacer esto:

    class Usuario:
      def __init__(self, nombre, apellido, edad):
          self.nombre = nombre
          self.apellido = apellido
          self.edad = edad
    
    usuario1 = Usuario("Enrique", "Barros", 32)
    usuario2 = Usuario("Elvira", "Gómez", 27)
    
    # Definición de un atributo diferente a cada objeto
    usuario1.correo = "correo@gmail.com"
    usuario2.telefono = "123456789"
    
    print(vars(Usuario), "\n")
    print(vars(usuario1))
    print(vars(usuario2))
    Resultado en la consola
    {'__module__': '__main__', '__init__': <function Usuario.__init__ at 0x0000025307A4D3A0>, '__dict__': <attribute '__dict__' of 'Usuario' objects>, '__weakref__': <attribute '__weakref__' of 'Usuario' objects>, '__doc__': None}
    
    {'nombre': 'Enrique', 'apellido': 'Barros', 'edad': 32, 'correo': 'correo@gmail.com'}
    {'nombre': 'Elvira', 'apellido': 'Gómez', 'edad': 27, 'telefono': '123456789'}

    Puedes hacerlo de esta forma, o bien acceder a la variable __dict__ de cada elemento, mediante la sintaxis mostrada en un ejemplo anterior.

    Por ejemplo:

    print(usuario2.__dict__)

    Acceder a estos diccionarios te da una visión de todo el espacio de nombres que envuelve el elemento.

    El nombre vars proviene del término variables (en inglés).

    Espacio publicitario

    Alcance encerrado

    El alcance encerrado es un tipo de alcance que permite que una función anidada (función hija) acceda a las variables de la función que la contiene (función padre).

    def funcion_externa():
        a = 10
    
        def funcion_interna():
            b = 20
            print(a, b)
    
        funcion_interna()
    
    funcion_externa()
    Resultado en la consola
    10 20

    La palabra reservada nonlocal

    La palabra reservada nonlocal se utiliza para trabajar con variables dentro de funciones anidadas, donde la variable no debe pertenecer a la función interna.

    En este código tengo dos variables diferentes llamadas a. Son dos variables por el hecho de encontrarse en alcances distintos:

    def funcion_externa():
        a = 10
    
        def funcion_interna():
            a = 100
            print(a)
    
        funcion_interna()
    
        print(a)
    
    funcion_externa()
    Resultado en la consola
    100
    10

    Sin embargo, gracias a la palabra nonlocal, puedo especificar que no se cree una variable local, sino que se utilice la de un ámbito superior.

    def funcion_externa():
        a = 10
    
        def funcion_interna():
            nonlocal a
            a = 100
            print(a)
    
        funcion_interna()
    
        print(a)
    
    
    funcion_externa()
    Resultado en la consola
    100
    100

    En el resultado, puede verse como se ha reasignado la variable de la función principal; no se ha redefinido. Esto indica que hemos podido alcanzarla.

    Espacio publicitario

    Con nonlocal estamos diciéndole al intérprete de Python que no cree una variable local dentro de la función actual (la anidada), sino que utilice la referencia de la variable del alcance superior, la de la función principal, que contiene a la anidada.

    El nombre nonlocal se puede traducir al español como “no local”, refiriéndose a que no se cree una variable local.

    Error por falta de enlace

    Si utilizamos nonlocal para un nombre de variable que no existe en la función principal, recibiremos un error por falta de enlace:

    def funcion_externa():
        a = 10
    
        def funcion_interna():
            nonlocal b
            b = 100
    Error en la consola
    SyntaxError: no binding for nonlocal 'b' found
    Error de sintaxis: no se encontró enlace para el nombre no local 'b'

    Diccionario encerrado

    Podríamos definir el alcance encerrado como un alcance local, dentro de otro alcance local. Entonces, para ver el diccionario encerrado, solo tienes que acceder a su diccionario local con la función locals():

    def funcion_externa():
        a = 10
    
        print(f"Alcance local: {locals()}")
    
        def funcion_interna():
            a = 100
            print(f"Alcance encerrado: {locals()}")
    
        funcion_interna()
    
    funcion_externa()
    Resultado en la consola
    Alcance local: {'a': 10}
    Alcance encerrado: {'a': 100}

    Alcance predefinido

    El espacio de nombres built-in o predefinido contiene los elementos integrados del lenguaje. Estos están por encima incluso del ámbito global.

    Espacio publicitario

    Este espacio de nombres tiene el conjunto de nombres predefinidos que están disponibles en cualquier programa Python, sin la necesidad de importar ningún módulo ni tener referencias explícitas en el propio módulo.

    Diccionario predefinido

    Para acceder al diccionario de este espacio de nombres, podemos hacerlo así:

    print(__builtins__.__dict__)

    O bien, otra forma sería con la función vars():

    print(vars(__builtins__))

    La salida en consola es muy larga, de modo que me abstengo de ponerla.

    Alcance de bloque

    El alcance de bloque, es un tipo de alcance que no se implementa en Python, ya que no existe de la forma tradicional.

    Este alcance se da en muchos lenguajes de programación con las llaves de bloque ({}), elemento que no tiene Python para este propósito.

    En el alcance de bloque, las variables solo están disponibles dentro de los bloques donde se declaran, incluido cualquier otro bloque anidado dentro de ese bloque.

    Entonces, por todo lo visto anteriormente, ya sabes que los alcances en Python funcionan de forma diferente.

    Un ejemplo de lenguaje con alcance de bloque, es JavaScript . Por ejemplo, en Python, el bloque de código de un condicional es global. Mira el siguiente ejemplo:

    if True:
        nombre = "Variable declarada."
    
    print(nombre)
    Resultado en la consola
    Variable declarada.

    Aquí estoy declarando la variable nombre directamente en un bloque de código de un condicional if. La variable es accesible desde fuera de este.

    Esto demuestra que el alcance que hay en el bloque del condicional es global. Por lo tanto, en este caso, estamos trabajando con la misma variable.

    Un valor True en la expresión de un if, hace que la expresión se cumpla siempre. No significa nada más.

    En el ejemplo anterior utilizo esa expresión para que el condicional se ejecute sin tener que poner cualquier otra expresión que coincida.

    En JavaScript , se aplica el alcance de bloque a una variable especificando la palabra reservada let o const, en la declaración de la variable.

    Espacio publicitario

    Aquí tienes el código equivalente del ejemplo anterior, en código JavaScript , con alcance de bloque:

    test.js
    if (true) {
        let nombre = "Nombre reasignado.";
    }
    
    console.log(nombre);
    Error en la consola
    Uncaught ReferenceError: nombre is not defined
    Error Referencia no Capturada: nombre no está definido

    En este caso, se está produciendo un error por variable no definida, ya que no es alcanzable desde fuera.

    Pero con var, declaro la variable sin ámbito de bloque:

    test.js
    if (true) {
        var nombre = "Nombre reasignado.";
    }
    
    console.log(nombre);
    Resultado en la consola
    Nombre reasignado.

    Estos últimos ejemplos son para que puedas ir comparando como funciona Python respecto a otros lenguajes de programación.

    La palabra reservada global

    Gracias a la palabra reservada global, podemos hacer que una variable global, sea utilizable dentro de un ámbito local.

    Mira el ejemplo que he escrito anteriormente:

    nombre = "PCMaster" # Variable global
    
    def imprimir_nombre():
        nombre = "Programación Fácil" # Variable local
        print(nombre)
    
    imprimir_nombre() 
    print(nombre)
    Resultado en la consola
    Programación Fácil
    PCMaster

    Aquí se están utilizando dos variables, y no puedo reasignar la variable global dentro de la función.

    Espacio publicitario

    Entonces, mediante el uso de global, conseguirás que se utilice la misma variable global dentro de la función:

    nombre = "PCMaster"
    
    def imprimir_nombre():
        global nombre
        nombre = "Programación Fácil"
        print(nombre)
    
    imprimir_nombre() 
    print(nombre)
    Resultado en la consola
    Programación Fácil
    Programación Fácil

    En ambos print() se ha imprimido el valor que hay en la función. Esto quiere decir que ha funcionado correctamente. Hemos podido utilizar la misma variable declarada fuera de la función, dentro de ella.

    Además, con global puedes especificar que una variable local, se convierta en global, con el fin de poder acceder a ella desde fuera:

    def funcion():
        global a
        a = 10
    
    funcion()
    
    print(a)
    Resultado en la consola
    10

    Es como el comportamiento de let y var de JavaScript , llevado al terreno de Python.

    La variable sin global, será local, por lo tanto, equivaldría en esta comparación a una variable con let en JavaScript .

    La variable con global, será global, por lo tanto, equivaldría en esta comparación a una variable con var en JavaScript .

    Advertencia con la palabra global

    Si utilizas Pylint en Visual Studio Code, podrás ver la siguiente advertencia, a la hora de utilizar la palabra reservada global. Esta advertencia aparece cuando evitamos una redefinición local.

    Por ejemplo:

    nombre = "PCMaster"
    
    def imprimir_nombre():
        global nombre
        nombre = "Programación Fácil"
        print(nombre)
    Resultado en la consola (Warning)
    Using the global statement

    Utilizando la declaración global

    Using the global statement

    Espacio publicitario

    Investiga los mensajes de información, advertencia y errores

    Cuando tengas avisos de las extensiones, te conviene acceder a los enlaces que proporcionan más detalles. Esto te enseñará muchas cosas que quizás desconocías.

    Para acceder a estos detalles, tienes que abrir el panel de “PROBLEMAS” de Visual Studio Code, y hacer clic en cualquier enlace de cualquier aviso:

    Warning Using global statement Pylint extensión

    En concreto, entrando a la página de más detalles sobre la advertencia Using the global statement, se nos describe a la perfección porque Pylint no recomienda utilizar global en este caso.

    La descripción que nos aparece es la siguiente (está traducida del inglés):

    Ocurre cuando se utiliza la instrucción “global” para actualizar una variable global. Pylint desaconseja su uso.

    ¡Eso no significa que no puedas usarlo!

    Aquí nos expone un código problemático y después otro que considera correcto.

    Código problemático:

    var = 1
    
    def foo():
        global var # [global-statement]
        var = 10
        print(var)
    
    foo()
    print(var)
    Resultado en la consola
    10
    10

    Como puedes ver, en el momento de realizar la llamada, se reasigna el valor de la variable.

    Hasta no realizar la llamada, no se reasigna el valor:

    var = 1
    def foo():
        global var # [global-statement]
        var = 10
        print(var)
    
    # No se llama a la función foo()
    print(var)
    Resultado en la consola
    1

    Con esto, estamos generando un código propenso a arrojar resultados incorrectos o inesperados.

    Espacio publicitario

    La forma que nos sugieren en la documentación de Pylint, es la siguiente:

    Código recomendado:

    var = 1
    
    def foo():
        print(var)
        return 10
    
    var = foo()
    print(var)
    Resultado en la consola
    1
    10

    El primer código (problemático) modifica una variable global dentro de una función, lo cual Pylint desaconseja.

    El segundo código (correcto) no modifica la variable global directamente dentro de la función, sino que esta devuelve el nuevo valor, que luego se asigna manualmente. Se trata de una práctica más recomendada. Evita posibles problemas de depuración y consecuencias inesperadas.

    Esto no quiere decir que no tengas que utilizar global. Dependiendo de como esté escrito el código y las necesidades que tengas, puede que sea oportuno usarla.

    La decisión de usar global debe tomarse caso por caso, teniendo en cuenta las ventajas y desventajas.

    En general, se recomienda evitar usar global y buscar mejores alternativas como las del ejemplo.

    Si decides usar global, asegúrate de hacerlo de manera responsable y documenta tu código para que sea comprensible para otros desarrolladores.

    El tema es algo avanzado, así que no te machaques demasiado si no le acabas de ver los posibles usos. Todavía te falta aprender bastantes cosas.

    Espacio publicitario

    La función predefinida dir()

    La función predefinida dir() nos devuelve una lista de nombres ordenados alfabéticamente. Estos nombres son todos los que hay en el espacio de nombres donde se ejecuta esta función.

    Anteriormente, mostré varias cosas del funcionamiento de dir(). No obstante, no podía faltar en este capítulo de espacios de nombres y alcances.

    Mira un ejemplo:

    from math import acos
    
    print(dir())
    Resultado en la consola
    ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos']

    Primero nos aparecen los módulos integrados de Python que utiliza nuestro archivo, y luego, los elementos propios que tengas en el código.

    Los nombres de módulos integrados aparecen primero, porque el guion bajo, alfabéticamente está antes que las letras.

    Probemos añadiendo más cosas:

    import random
    
    aleatorio = random.randint(10,60)
    print(dir())
    Resultado en la consola
    ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'aleatorio', 'random']

    Si quieres hacer lo mismo, pero a nivel de función, hazlo así:

    def prueba():
        variable_prueba = None
        print(dir())
    
    prueba()
    Resultado en la consola
    ['variable_prueba']

    Además, podrás establecer condiciones basándote en los nombres del espacio:

    import math
    
    espacio_nombres = dir()
    
    if 'math' in espacio_nombres:
        print('math está en el espacio de nombres.')
    else:
        print('math no está en el espacio de nombres.')
    Resultado en la consola
    math está en el espacio de nombres.

    Espacio publicitario




    Espacio publicitario


    Ejercicios de Python para resolver

    49. En este código tenemos un elemento nombrado como ambito. ¿Sabrías decirme, sin ningún print() o return, si se trata de varias variables, o si es la misma?

    ambito = 10
    
    def funcion():
        ambito = 15
    
    ambito = 20

    En este caso, hay dos variables diferentes llamadas ambito. Una es una variable global, y la otra es una variable local de la función.

    Si quieres compruébalo mediante alguna de las diferentes formas que te he mostrado. Por ejemplo, con una función como locals(), o con print().

    50. Indica el ámbito de la variable o las variables, del código anterior.

    La variable que tiene el valor 10 es de ámbito global y la variable que tiene el valor 15, dentro de la función, es de ámbito local.



    Espacio publicitario