in

python – ¿Cómo verifico si una lista está vacía?

apple touch icon@2

La mejor forma de comprobar si una lista está vacía

Por ejemplo, si pasa lo siguiente:

a = []

¿Cómo verifico si a está vacío?

Respuesta corta:

Coloque la lista en un contexto booleano (por ejemplo, con un if o while declaración). Probará False si está vacío, y True de lo contrario. Por ejemplo:

if not a:                           # do this!
    print('a is an empty list')

PEP 8

PEP 8, la guía de estilo oficial de Python para el código de Python en la biblioteca estándar de Python, afirma:

Para las secuencias (cadenas, listas, tuplas), utilice el hecho de que las secuencias vacías son falsas.

Yes: if not seq:
     if seq:

No: if len(seq):
    if not len(seq):

Deberíamos esperar que el código de biblioteca estándar sea lo más eficaz y correcto posible. Pero, ¿por qué es así y por qué necesitamos esta guía?

Explicación

Con frecuencia veo código como este de programadores experimentados nuevos en Python:

if len(a) == 0:                     # Don't do this!
    print('a is an empty list')

Y los usuarios de lenguajes perezosos pueden tener la tentación de hacer esto:

if a == []:                         # Don't do this!
    print('a is an empty list')

Estos son correctos en sus respectivos otros idiomas. Y esto es incluso semánticamente correcto en Python.

Pero lo consideramos no Pythonic porque Python admite esta semántica directamente en la interfaz del objeto de lista mediante coerción booleana.

Desde el docs (y tenga en cuenta específicamente la inclusión de la lista vacía, []):

De forma predeterminada, un objeto se considera verdadero a menos que su clase defina un __bool__() método que regresa False o un __len__() método que devuelve cero, cuando se llama con el objeto. A continuación, se muestran la mayoría de los objetos integrados que se consideran falsos:

  • constantes definidas como falsas: None y False.
  • cero de cualquier tipo numérico: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • secuencias y colecciones vacías: '', (), [], {}, set(), range(0)

Y la documentación del modelo de datos:

object.__bool__(self)

Llamado para implementar pruebas de valor de verdad y la operación incorporada bool(); debería volver False o True. Cuando este método no está definido,
__len__() se llama, si está definido, y el objeto se considera verdadero si su resultado es distinto de cero. Si una clase no define ni __len__()
ni __bool__(), todas sus instancias se consideran verdaderas.

y

object.__len__(self)

Llamado para implementar la función incorporada len(). Debe devolver la longitud del objeto, un entero> = 0. Además, un objeto que no define un __bool__() método y cuyo __len__() El método devuelve cero se considera falso en un contexto booleano.

Entonces en lugar de esto:

if len(a) == 0:                     # Don't do this!
    print('a is an empty list')

o esto:

if a == []:                     # Don't do this!
    print('a is an empty list')

Hacer esto:

if not a:
    print('a is an empty list')

Hacer lo que Pythonic suele dar sus frutos en rendimiento:

¿Vale la pena? (Tenga en cuenta que menos tiempo para realizar una operación equivalente es mejor 🙂

>>> import timeit
>>> min(timeit.repeat(lambda: len([]) == 0, repeat=100))
0.13775854044661884
>>> min(timeit.repeat(lambda: [] == [], repeat=100))
0.0984637276455409
>>> min(timeit.repeat(lambda: not [], repeat=100))
0.07878462291455435

Para escalar, aquí está el costo de llamar a la función y construir y devolver una lista vacía, que puede restar de los costos de los controles de vacío utilizados anteriormente:

>>> min(timeit.repeat(lambda: [], repeat=100))
0.07074015751817342

Vemos eso cualquiera comprobar la longitud con la función incorporada len en comparación con 0 o comparar con una lista vacía es mucho menos eficaz que usar la sintaxis incorporada del lenguaje tal como se documenta.

¿Por qué?

Para el len(a) == 0 cheque:

Primero Python tiene que verificar los globales para ver si len está ensombrecido.

Entonces debe llamar a la función, cargar 0y haga la comparación de igualdad en Python (en lugar de con C):

>>> import dis
>>> dis.dis(lambda: len([]) == 0)
  1           0 LOAD_GLOBAL              0 (len)
              2 BUILD_LIST               0
              4 CALL_FUNCTION            1
              6 LOAD_CONST               1 (0)
              8 COMPARE_OP               2 (==)
             10 RETURN_VALUE

Y para el [] == [] tiene que construir una lista innecesaria y luego, nuevamente, hacer la operación de comparación en la máquina virtual de Python (a diferencia de C)

>>> dis.dis(lambda: [] == [])
  1           0 BUILD_LIST               0
              2 BUILD_LIST               0
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

La forma «Pythonic» es una verificación mucho más simple y rápida ya que la longitud de la lista se almacena en caché en el encabezado de la instancia del objeto:

>>> dis.dis(lambda: not [])
  1           0 BUILD_LIST               0
              2 UNARY_NOT
              4 RETURN_VALUE

Evidencia de la fuente C y documentación

PyVarObject

Esta es una extensión de PyObject eso agrega el ob_size campo. Esto solo se usa para objetos que tienen alguna noción de longitud. Este tipo no suele aparecer en la API de Python / C. Corresponde a los campos definidos por la expansión del PyObject_VAR_HEAD macro.

De la fuente c en Incluir / listobject.h:

typedef struct {
    PyObject_VAR_HEAD
    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size

Respuesta a los comentarios:

Me gustaría señalar que esto también es cierto para el caso no vacío, aunque es bastante feo como con l=[] luego %timeit len(l) != 0 90,6 ns ± 8,3 ns, %timeit l != [] 55,6 ns ± 3,09, %timeit not not l 38,5 ns ± 0,372. Pero no hay forma de que nadie disfrute not not l a pesar de triplicar la velocidad. Parece ridículo. Pero la velocidad gana
Supongo que el problema es probar con el tiempo, ya que if l: es suficiente pero sorprendentemente %timeit bool(l) rinde 101 ns ± 2,64 ns. Interesante, no hay forma de coaccionar a bool sin esta penalización. %timeit l es inútil ya que no se produciría ninguna conversión.

Magia de ipython, %timeit, no es del todo inútil aquí:

In [1]: l = []                                                                  

In [2]: %timeit l                                                               
20 ns ± 0.155 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

In [3]: %timeit not l                                                           
24.4 ns ± 1.58 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [4]: %timeit not not l                                                       
30.1 ns ± 2.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Podemos ver que hay un poco de costo lineal por cada not aquí. Queremos ver los costos ceteris paribus, es decir, todo lo demás igual, donde todo lo demás se minimiza en la medida de lo posible:

In [5]: %timeit if l: pass                                                      
22.6 ns ± 0.963 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [6]: %timeit if not l: pass                                                  
24.4 ns ± 0.796 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [7]: %timeit if not not l: pass                                              
23.4 ns ± 0.793 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Ahora veamos el caso de una lista vacía:

In [8]: l = [1]                                                                 

In [9]: %timeit if l: pass                                                      
23.7 ns ± 1.06 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [10]: %timeit if not l: pass                                                 
23.6 ns ± 1.64 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [11]: %timeit if not not l: pass                                             
26.3 ns ± 1 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Lo que podemos ver aquí es que hay poca diferencia si pasa en un bool a la verificación de condición o la lista en sí, y en todo caso, dar la lista, tal como está, es más rápido.

Python está escrito en C; usa su lógica en el nivel C. Todo lo que escriba en Python será más lento. Y probablemente será órdenes de magnitud más lento a menos que esté utilizando los mecanismos integrados en Python directamente.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Función de biblioteca C – strcpy ()

gfg 200x200 min

Escribir en un archivo en Python