El error NameError en Python ocurre cuando el intérprete CPython
no reconoce un nombre de objeto local o global que se haya proporcionado en el código fuente de Python.
Este error hereda de Exception
que a su vez se extiende BaseException
BaseException -> Exception -> NameError
Emplearemos este código de ejemplo:
import dis
from gw_utility.logging import Logging
def main():
try:
# Create Auto.
auto = Auto("Chevrolet, "Bell Air", 50, datetime.date(1956))
# Log auto object.
Logging.line_separator("log_object(auto)", 60)
log_object(auto)
# Log invalid object.
Logging.line_separator("log_invalid_object(auto)", 60)
log_invalid_object(auto)
# Disassemble both log_ functions.
Logging.line_separator("DISASSEMBLY OF log_object()", 60)
disassemble_object(log_object)
Logging.line_separator("DISASSEMBLY OF log_invalid_object()", 60)
disassemble_object(log_invalid_object)
except NameError as error:
# Salida esperada es NameErrors.
Logging.log_exception(error)
except Exception as exception:
# Salida inesperada es una Excepcion.
Logging.log_exception(exception, False)
def log_object(value):
"""Registro del valor de parametro pasado a consola.
:param value: Value to be logged.
:return: None
"""
try:
Logging.log(value)
except NameError as error:
# Salida esperada es NameErrors
Logging.log_exception(error)
except Exception as exception:
# Salida inesperada es una Excepcion.
Logging.log_exception(exception, False)
def log_invalid_object(value):
"""Intenta registrar el objeto invalido valu en la consola
:param value: Value intended to be logged, but which is instead ignored.
:return: None
"""
try:
Logging.log(valu)
except NameError as error:
# Salida esperada es NameErrors
Logging.log_exception(error)
except Exception as exception:
# Salida inesperada es una Excepcion.
Logging.log_exception(exception, False)
def disassemble_object(value):
"""salida desensamblada del objeto pasado.
:param value: Object to be disassembled.
:return: None
"""
dis.dis(value)
if __name__ == "__main__":
main()
# auto.py
import datetime
class Auto:
marca: str
modelo: int
potencia: str
año de fabricación: datetime.date
potencia: str
def __eq__(self, other):
"""Determina si el objeto pasado, es equivalente al objeto actual"""
return self.__dict__ == other.__dict__
def __init__(self,
marca: str = None,
modelo: str = None,
potencia: int = None,
año_fabricacion: datetime.date = None):
"""Inicializa una instanci de Auto .
:param marca: Nombre de la marca.
:param modelo: Modelo.
:param potencia: Potencia en HP.
:param año_fabricacion: Año de fabricación.
"""
self.marca = marca
self.modelo = modelo
self.potencia = potencia
self.año_fabricacion = año_fabricacion
def __getattr__(self, marca: str):
"""Devuelve el atributo pasado comparandolo con marca."""
# Get internal dict value matching marca.
value = self.__dict__.get(marca)
if not value:
# Lanza AttributeError si el atributo no se encuentra.
raise AttributeError(f'{self.__class__.__marca__}.{marca} is invalid.')
# Devuelve el valor del atributo
return value
def __len__(self):
"""Devuelve la longitud de la marca."""
return len(self.title)
def __str__(self):
"""Retorna una cadena formateada que representa a SAuto."""
año_fabricacion = '' if self.año_fabricacion is None else f', fabricado en {self.año_fabricacion.__format__(" %Y")}'
potencia = '' if self.potencia is None else f' con {self.potencia} de fuerza'
return f'\'{self.modelo}\' de la marca {self.marca} con {potencia} HP, fabricado en {año_fabricacion}.'
Desglosaremos ahora el código para explicar el error
Comenzaremos con parte del código de ejemplo, que vimos antes escrito en Python normal; después de lo cual veremos cómo podemos desensamblar este código en el código de bytes que CPython realmente lee e interpreta.
log_object y log_invalid_object
Vamos a emplear dos funciones, que Python posee para esto log_object(value)
y log_invalid_object(value)
:
def log_object(value):
"""Registra los valores pasados al parametro en consola.
:param value: Valor a registrar.
:return: None
"""
try:
Logging.log(value)
except NameError as error:
# Salida esperada NameErrors.
Logging.log_exception(error)
except Exception as exception:
# Salida inesperada Exceptions.
Logging.log_exception(exception, False)
def log_invalid_object(value):
"""Intenta registrar objetos no validos(value) en consola
:param value: Valor que se ha intentado registrar, pero que en realidad se ha ignorado
:return: None
"""
try:
Logging.log(valu)
except NameError as error:
# Salida esperada NameErrors.
Logging.log_exception(error)
except Exception as exception:
# Salida inesperada Exceptions.
Logging.log_exception(exception, False)
Todo el código en ambas funciones se centra en el manejo de errores.
La funcionalidad principal tiene lugar en la línea: Logging.log (valor)
y Logg
ing.log (valu)
, respectivamente.
En esencia, estas dos funciones lo que hacen es registrar el contenido del parámetro de valor pasado en la consola. Sin embargo, en el caso de log_invalid_object ()
tenemos un leve error tipográfico de valu
en lugar de value
.
Creemos una instancia del objeto Auto y luego la pasaré a ambas funciones:
# Creando Auto.
auto = Auto("Chevreolet", "Bell air", 300hp, datetime.date(1956))
# Log Auto object.
Logging.line_separator("log_object(auto)", 60)
log_object(auto)
# Log invalid object.
Logging.line_separator("log_invalid_object(auto)", 60)
log_invalid_object(auto)
La ejecución de este código produce una salida del objeto Auto
esperada, seguida de un NameError
porque nuestro error tipográfico de valu
no es un nombre reconocido.
--------------------- log_object(auto) ---------------------
'Bellair' de la marca Chevrolet con 30 HP, fabricado en 1956.
{self.modelo}\' de la marca {self.marca} con {potencia} HP, fabricado en {año_fabricacion}.'
----------------- log_invalid_object(auto) -----------------
[EXPECTED] NameError: name 'valu' is not defined
Si aplicaramos el metodo dis, que es un modulo desensamblador integrado en Python, lo cual le permite es un lenguaje poderoso que nos permite mirar profundizar y ver el código de bytes real que cada una de estas funciones log_ que se generan para el intérprete CPython.
Al pasar una referencia de función al método dis.dis (),
se nos proporciona una salida completa del código de bytes desensamblado que CPython interpreta durante la ejecución.
Nuestra función local disassemble_object(value)
, funciona como una pequeña envoltura para este propósito:
def disassemble_object(value):
"""Salida con el objeto desensamblado pasado.
:param value: Object to be disassembled.
:return: None
"""
dis.dis(value)
Para ver el bytecode
de la función log_object (value)
ejecutemos el código:
# Disassemble both log_ functions.
Logging.line_separator("DISASSEMBLY OF log_object()", 60)
disassemble_object(log_object)
This produces the following output:
--------------- DISASSEMBLY OF log_object() ----------------
41 0 SETUP_EXCEPT 14 (to 16)
42 2 LOAD_GLOBAL 0 (Logging)
4 LOAD_ATTR 1 (log)
6 LOAD_FAST 0 (value)
8 CALL_FUNCTION 1
10 POP_TOP
12 POP_BLOCK
14 JUMP_FORWARD 88 (to 104)
43 >> 16 DUP_TOP
18 LOAD_GLOBAL 2 (NameError)
20 COMPARE_OP 10 (exception match)
22 POP_JUMP_IF_FALSE 58
24 POP_TOP
26 STORE_FAST 1 (error)
28 POP_TOP
30 SETUP_FINALLY 16 (to 48)
45 32 LOAD_GLOBAL 0 (Logging)
34 LOAD_ATTR 3 (log_exception)
36 LOAD_FAST 1 (error)
38 CALL_FUNCTION 1
40 POP_TOP
42 POP_BLOCK
44 POP_EXCEPT
46 LOAD_CONST 1 (None)
>> 48 LOAD_CONST 1 (None)
50 STORE_FAST 1 (error)
52 DELETE_FAST 1 (error)
54 END_FINALLY
56 JUMP_FORWARD 46 (to 104)
46 >> 58 DUP_TOP
60 LOAD_GLOBAL 4 (Exception)
62 COMPARE_OP 10 (exception match)
64 POP_JUMP_IF_FALSE 102
66 POP_TOP
68 STORE_FAST 2 (exception)
70 POP_TOP
72 SETUP_FINALLY 18 (to 92)
48 74 LOAD_GLOBAL 0 (Logging)
76 LOAD_ATTR 3 (log_exception)
78 LOAD_FAST 2 (exception)
80 LOAD_CONST 2 (False)
82 CALL_FUNCTION 2
84 POP_TOP
86 POP_BLOCK
88 POP_EXCEPT
90 LOAD_CONST 1 (None)
>> 92 LOAD_CONST 1 (None)
94 STORE_FAST 2 (exception)
96 DELETE_FAST 2 (exception)
98 END_FINALLY
100 JUMP_FORWARD 2 (to 104)
>> 102 END_FINALLY
>> 104 LOAD_CONST 1 (None)
106 RETURN_VALUE
Esto puede parecer un poco abrumador al principio, pero estos datos en realidad son bastante fáciles de interpretar con un poco de conocimiento sobre lo que estamos viendo en cada columna. La primera columna (por ejemplo, 41, 42, 43… 48)
es el número de línea real en la gama de origen para el conjunto de instrucciones correspondiente.
Por lo tanto, podemos ver que todas las siguientes instrucciones
42 2 LOAD_GLOBAL 0 (Logging)
4 LOAD_ATTR 1 (log)
6 LOAD_FAST 0 (value)
8 CALL_FUNCTION 1
10 POP_TOP
12 POP_BLOCK
14 JUMP_FORWARD 88 (to 104)
Todos se generaron a partir de una sola línea de código fuente (# 42):
La columna con múltiplos de dos (0, 2, 4, etc.) es la dirección de memoria en el código de bytes subyacente para la instrucción dada. Python moderno almacena instrucciones usando dos bytes de datos, de ahí los múltiplos de dos.
La siguiente columna contiene el nombre de operación (es decir, instrucción) que debe ejecutarse, todo lo cual se puede encontrar en la documentación oficial.
La columna posterior contiene los argumentos, si corresponde, que utilizará cada instrucción en particular.
La columna final proporciona una versión amigable para los humanos de la instrucción, por lo que podemos visualizar mejor cómo la instrucción del código de bytes se correlaciona con el código fuente.
Por lo tanto, echemos un vistazo al código fuente de una sola línea 42 de Logging.log (value)
y el conjunto de instrucciones de bytecode
generado para ver qué está pasando:
42 2 LOAD_GLOBAL 0 (Logging)
4 LOAD_ATTR 1 (log)
6 LOAD_FAST 0 (value)
8 CALL_FUNCTION 1
10 POP_TOP
12 POP_BLOCK
14 JUMP_FORWARD 88 (to 104)
Comienza con LOAD_GLOBAL
para cargar el nombre global Logging
en la pila.
Luego carga el atributo de registro en la parte superior de la pila (TOS).
LOAD_FAST
empuja una referencia a una variable local llamada value
en la pila y a continuación, CALL_FUNCTION
llama a la función en la pila de argumentos 1, que es el método de registro que se agregó dos instrucciones antes.
POP_TOP
elimina el elemento más reciente agregado a la pila, que es el objeto de valor local.
Cada marco de ejecución contiene una pila de bloques de código, que son las agrupaciones lógicas que vemos y creamos al escribir el código fuente que está agrupado localmente.
Por ejemplo, un bucle anidado o, en este caso, un bloque try-except
, está contenido dentro de un bloque de código separado en la pila.
Dado que la siguiente instrucción a la que estamos saltando con JUMP_FORWARD 88
está saliendo del final del bloque try que se encuentra en nuestro código fuente, POP_BLOCK
se usa para eliminar el bloque superior (actual) de la pila de bloques de código.
Veamos en qué se diferencia este bytecode
compilado para log_object
, de la función log_invalid_object
ligeramente modificada:
Logging.line_separator("DISASSEMBLY OF log_invalid_object()", 60)
disassemble_object(log_invalid_object)
Ignoraremos la mayoría del bytecode
producido aquí, ya que es idéntico al producido por log_object
, pero también tenemos el conjunto de instrucciones de la misma línea de código fuente Logging.log (valu)
correspondiente que examinamos antes:
58 2 LOAD_GLOBAL 0 (Logging)
4 LOAD_ATTR 1 (log)
6 LOAD_GLOBAL 2 (valu)
8 CALL_FUNCTION 1
10 POP_TOP
12 POP_BLOCK
14 JUMP_FORWARD 88 (to 104)
Todo se ve exactamente igual que antes, con dos excepciones: el número de línea 58 es obviamente diferente, ya que estamos compilando una línea diferente de código fuente.
La segunda diferencia es que la tercera instrucción, cambió de LOAD_FAST 0
(value)
a LOAD_GLOBAL2(valu)
.
¿Por qué sucedió esto?. Pues porque el compilador no puede conciliar un objeto local llamado valu
, ya que el parámetro local real pasado a la función es value
.
Por lo tanto, el compilador asume que valu
es un nombre global e intenta cargarlo a través de LOAD_GLOBAL
, y como sabemos al ejecutar la función log_invalid_object
, el intérprete de CPython
no será capaz de ubicar un objeto llamado valu
durante la ejecución, por lo que se genera un NameError
para indicarlo.
.. y todo como el diamante, antes de luz , es carbón.
J. Martí