Alcance de bloque en Python

93

Cuando codifica en otros lenguajes, a veces creará un alcance de bloque, como este:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

Uno de los propósitos (de muchos) es mejorar la legibilidad del código: mostrar que ciertas declaraciones forman una unidad lógica o que ciertas variables locales se usan solo en ese bloque.

¿Existe una forma idiomática de hacer lo mismo en Python?

Johan Råde
fuente
2
One purpose (of many) is to improve code readability- El código Python, escrito correctamente (es decir, siguiendo el zen de Python ) no necesitaría tal guarnición para ser legible. De hecho, es una de las (muchas) cosas que me gustan de Python.
Burhan Khalid
He tratado de jugar con __exit__y withdeclaración, cambiando el globals()pero fallado.
Ruggero Turra
1
Sería muy útil definir la vida útil variable, relacionada con la adquisición del recurso
Ruggero Turra
25
@BurhanKhalid: Eso no es cierto. El zen de Python no le impide contaminar un ámbito local con una variable temporal aquí y allá. Si convierte cada uso de una única variable temporal en, por ejemplo, definir una función anidada que se llama inmediatamente, el zen de Python tampoco estará contento. Limitar explícitamente el alcance de una variable es una herramienta para mejorar la legibilidad, porque responde directamente "¿se utilizan estos identificadores a continuación?" - una pregunta que puede surgir leyendo incluso el código Python más elegante.
bluenote10
18
@BurhanKhalid Está bien no tener una función. Pero llamar a eso "zen" es simplemente repugnante.
Phil

Respuestas:

82

No, no hay soporte de idioma para crear un alcance de bloque.

Las siguientes construcciones crean alcance:

  • módulo
  • clase
  • función (incl. lambda)
  • expresión generadora
  • comprensiones (dict, set, list (en Python 3.x))
ThomasH
fuente
38

La forma idiomática en Python es mantener sus funciones cortas. Si cree que lo necesita, ¡refactorice su código! :)

Python crea un nuevo alcance para cada módulo, clase, función, expresión generadora, comprensión de dictados, comprensión de conjuntos y en Python 3.x también para cada comprensión de lista. Aparte de estos, no hay ámbitos anidados dentro de las funciones.

Sven Marnach
fuente
12
"Lo más importante en la programación es la capacidad de dar un nombre a algo. La segunda cosa más importante es que no se le pida que le dé un nombre a algo". En su mayor parte, Python requiere que los ámbitos (para variables, etc.) reciban nombres. En este sentido, las variables de Python son la segunda prueba más importante.
Krazy Glew
19
Lo más importante en la programación es la capacidad de administrar las dependencias de su aplicación y administrar los alcances de los bloques de código. Los bloques anónimos le permiten limitar la vida útil de las devoluciones de llamada, mientras que, de lo contrario, sus devoluciones de llamada solo se usan una vez, pero se mantienen en vivo durante la duración del programa, esto causa un desorden de alcance global y daña la legibilidad del código.
Dmitry
Acabo de notar que las variables también son locales para dictar / establecer comprensiones. Probé Python 2.7 y 3.3, pero no estoy seguro de si depende de la versión.
wjandrea
1
@wjandrea Tienes razón - agregado a la lista. No debería haber ninguna diferencia entre las versiones de Python para estos.
Sven Marnach
4
Volvería a redactar la última oración, ya que puedes crear funciones dentro de funciones. Entonces, hay ámbitos anidados dentro de funciones.
ThomasH
19

Puede hacer algo similar al alcance de un bloque de C ++ en Python declarando una función dentro de su función y luego llamándola inmediatamente. Por ejemplo:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

Si no está seguro de por qué podría querer hacer esto, este video podría convencerlo.

El principio básico es abarcar todo lo más estrictamente posible sin introducir ninguna 'basura' (tipos / funciones adicionales) en un alcance más amplio de lo que se requiere absolutamente.Nada más quiere usar el do_first_thing()método, por ejemplo, por lo que no debe tener un alcance fuera del función de llamada.

Ben
fuente
También es la forma en que los desarrolladores de Google lo utilizan en los tutoriales de TensorFlow, como se ve, por ejemplo, aquí
Nino Filiu
13

Estoy de acuerdo en que no hay alcance de bloque. Pero un lugar en Python 3 lo hace PARECER como si tuviera un alcance de bloque.

¿Qué pasó lo que le dio esta mirada? Esto funcionaba correctamente en Python 2. pero para hacer que la fuga de variables se detenga en Python 3, han hecho este truco y este cambio hace que parezca que tiene un alcance de bloque aquí.

Dejame explicar.


Según la idea de alcance, cuando introducimos variables con los mismos nombres dentro del mismo alcance, su valor debe modificarse.

esto es lo que está sucediendo en Python 2

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

Pero en Python 3, aunque se introduce la variable con el mismo nombre, no se anula, la comprensión de la lista actúa como una caja de arena por alguna razón y parece crear un nuevo alcance en ella.

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

y esta respuesta va en contra de la declaración del answerer @ Thomas El único medio para crear un alcance son funciones, clases o módulos porque esto parece otro lugar para crear un nuevo alcance.

Harish Kayarohanam
fuente
0

Los módulos (y paquetes) son una excelente forma Pythonic de dividir su programa en espacios de nombres separados, lo que parece ser un objetivo implícito de esta pregunta. De hecho, mientras estaba aprendiendo los conceptos básicos de Python, me sentí frustrado por la falta de una función de alcance de bloque. Sin embargo, una vez que entendí los módulos de Python, pude realizar mis objetivos anteriores de manera más elegante sin la necesidad de un alcance de bloque.

Como motivación, y para señalar a las personas en la dirección correcta, creo que es útil proporcionar ejemplos explícitos de algunas de las construcciones de alcance de Python. Primero explico mi intento fallido de usar clases de Python para implementar el alcance del bloque. A continuación, explico cómo logré algo más útil usando módulos de Python. Al final, describo una aplicación práctica de paquetes para cargar y filtrar datos.

Intentando bloquear el alcance con clases

Por unos momentos pensé que había logrado el alcance del bloque al pegar código dentro de una declaración de clase:

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

Desafortunadamente, esto se rompe cuando se define una función:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

Eso es porque las funciones definidas dentro de una clase usan un alcance global. La forma más fácil (aunque no la única) de solucionar esto es especificar explícitamente la clase:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

Esto no es tan elegante porque uno debe escribir funciones de manera diferente dependiendo de si están o no incluidas en una clase.

Mejores resultados con los módulos de Python

Los módulos son muy similares a las clases estáticas, pero los módulos son mucho más limpios en mi experiencia. Para hacer lo mismo con los módulos, hago un archivo llamado my_module.pyen el directorio de trabajo actual con el siguiente contenido:

x = 10
print(x) # (A)

def printx():
    global x
    print(x) # (B)

Luego, en mi archivo principal o sesión interactiva (por ejemplo, Jupyter), hago

x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5

Como explicación, cada archivo de Python define un módulo que tiene su propio espacio de nombres global. Importar un módulo le permite acceder a las variables en este espacio de nombres con la .sintaxis.

Si está trabajando con módulos en una sesión interactiva, puede ejecutar estas dos líneas al principio

%load_ext autoreload
%autoreload 2

y los módulos se recargarán automáticamente cuando se modifiquen sus archivos correspondientes.

Paquetes para cargar y filtrar datos

La idea de paquetes es una pequeña extensión del concepto de módulos. Un paquete es un directorio que contiene un __init__.pyarchivo (posiblemente en blanco) , que se ejecuta al importar. Se puede acceder a los módulos / paquetes dentro de este directorio con la .sintaxis.

Para el análisis de datos, a menudo necesito leer un archivo de datos grande y luego aplicar varios filtros de forma interactiva. Leer un archivo lleva varios minutos, por lo que solo quiero hacerlo una vez. Basado en lo que aprendí en la escuela sobre programación orientada a objetos, solía creer que uno debería escribir el código para filtrar y cargar como métodos en una clase. Una gran desventaja de este enfoque es que si luego vuelvo a definir mis filtros, la definición de mi clase cambia, por lo que tengo que recargar toda la clase, incluidos los datos.

Hoy en día con Python, defino un paquete llamado my_dataque contiene submódulos llamados loady filter. Dentro de filter.pypuedo hacer una importación relativa:

from .load import raw_data

Si modifico filter.py, autoreloaddetectaré los cambios. No se recarga load.py, así que no necesito recargar mis datos. De esta manera puedo crear un prototipo de mi código de filtrado en un cuaderno de Jupyter, ajustarlo como una función y luego cortar y pegar desde mi cuaderno directamente en filter.py. Descubrir esto revolucionó mi flujo de trabajo y me convirtió de un escéptico a un creyente en el "Zen de Python".

Ben Mares
fuente