El tipo de dato «None»

El tipo de dato «None»



Ya sabemos que Python es un lenguaje de “tipado” dinámico, es decir, no es necesario inidicar de qué tipo de dato es una variable (siguiendo la nomenclatura de los lenguajes tradicionales; lo correcto en Python sería hablar de objetos: véase Diferencia entre variables en Python y otros lenguajes) al momento de crearla ni tampoco que la misma mantenga ese tipo de dato a lo largo de la ejecución del programa: puede comenzar siendo un entero, luego una cadena, luego un número de coma flotante, etc. No obstante, todas las variables tienen algún tipo de dato (para ser más precisos, en realidad, son instancias de alguna clase; pero esto no nos interesa para el presente artículo) que podemos conocer vía la función incorporada type():

>>> pi = 3.14
>>> type(pi)
<class 'float'>

Los cuatro tipos de dato básicos son el número entero (int), el número de coma flotante (float), la cadena de caracteres (str) y el booleano (bool). Sin embargo, Python incorpora un quinto tipo de dato que estrictamente hablando se llama NoneType y cuyo único valor posible es None (pronunciado llanamente “nan”).

>>> a = None
>>> type(a)
<class 'NoneType'>

A menudo None es utilizado cuando se quiere crear una variable (puesto que Python no distingue la creación de la asignación: crear una variable es simplemente darle un valor) pero aún no se le quiere asignar ningún valor en particular; aunque, en definitiva, como dijimos, None es también un valor. No debe ser interpretado como el valor NULL de lenguajes como C y C++, que solo se aplica a punteros; antes bien, None puede ser asignado a cualquier objeto.

# Una variable puede empezar siendo `None` y luego ser asignada con otro valor.
>>> a = None
>>> type(a)
<class 'NoneType'>
>>> a = "Hola mundo"
>>> type(a)
<class 'str'>
# O viceversa, empezar con un valor y luego ser asignada con `None`.
>>> b = 5
>>> b = None

Nótese que cuando escribimos el nombre de una variable en la consola interactiva cuyo contenido es None, no se mostrará nada.

>>> a = None
>>> a
>>>

Dado que se trata de un tipo de dato como cualquier otro, con la peculiaridad de que tiene un único valor posible, podemos realizar las comparaciones habituales.

a = None
if a == None:
    print("a es None.")
else:
    print("a no es None.")

Si bien esto es válido, la forma recomendada de hacer comparaciones con este tipo de dato es utilizando la palabra reservada is (véase más abajo la explicación técnica de esto).

a = None
# Método recomendado de comparación.
if a is None:
    print("a es None.")
else:
    print("a no es None.")

O bien para chequear si una variable no es None:

a = 1
b = 2

# Válido.
if not a is None:
    print("a no es None.")
# Válido y más legible.
if b is not None:
    print("b no es None.")

Las dos comparaciones son similares, pero la segunda es la más recomendada.

Ejemplos

¿En qué casos es útil emplear None? Ya dijimos que es particularmente provechoso cuando queremos crear un variable pero no asignarle ─por el momento─ ningún valor. Por ejemplo, el siguiente código busca la cadena "Python" en una lista de lenguajes e imprime su posición, en caso de encontrarla:

lenguajes = ["C", "C++", "Python", "Java", "Go"]
posicion = None
for i, elemento in enumerate(lenguajes):
    if elemento == "Python":
        posicion = i
        break

if posicion is None:
    print("No se encontró el elemento.")
else:
    print("El elemento está en la posición:", i)

Aquí definimos posicion inicialmente como None, ya que puede ocurrir que el elemento buscado no se encuentre en la lista. Si el valor sigue siendo ese luego de la ejecución del bucle, quiere decir que el elemento no se ha encontrado. El valor inicial no podría ser cero, porque el cero es un índice válido (el primer elemento). Tampoco -1, porque los elementos de una lista también pueden ser accedidos en sentido inverso (-1 es el último, -2 el penúltimo, etc.). Vemos que None encaja perfecto para esta tarea.

Asimismo None es particularmente útil al definir argumentos con valores por defecto. Supongamos una función que toma como argumento una lista e imprime cada uno de los elementos en pantalla.

def imprimir(lista):
    for elemento in lista:
        print(elemento)

imprimir([1, 2, 3, 4])

Pero ahora queremos que si el usuario no indica ninguna lista como argumento, imprima las líneas de un archivo de texto. Asignándole None como valor por defecto a lista, podremos saber en el interior de la función si el argumento fue provisto por el usuario.

def imprimir(lista=None):
    if lista is not None:
        for elemento in lista:
            print(elemento)
    else:
        with open("mi-archivo.txt") as f:
            print(f.read())

Explicación técnica

Para los interesados, la explicación técnica de por qué las comparaciones se realizan usando la partícula is es la siguiente: solamente existe un objeto None almacenado en memoria, y todas las variables que tengan este valor no son sino “punteros” a esa dirección de memoria. Podemos comprobar esto usando la función incorporada id(), que retorna la dirección de memoria de un objeto (nótese que es el mismo número).

>>> a = None
>>> b = None
>>> id(a)
1636093900
>>> id(b)
1636093900

La palabra reservada is lo que hace es, justamente, comparar las direcciones de memoria de dos objetos, una operación mucho más rápida que las efectuadas con la igualdad (==) y desigualdad (!=), que implican llamar al método mágico __eq__() de uno de los objetos de la comparación.



Deja un comentario