Notas básicas de Python para el desarrollo de pruebas (15): iteradores y generadores

15.1 Iterable Iterable

¿Recuerdas el bucle for? El bucle for puede iterar y procesar cadenas y tipos de contenedores, como listas, tuplas, diccionarios y conjuntos. ¿Sabe por qué for puede iterar estos tipos de datos? Porque estos objetos son objetos iterables.

Para juzgar si es un objeto iterable, se puede isinstance(obj, Iterable)juzgar que la salida True indica que el objeto obj es iterable (iterable).

15.2 Iterador iterador

Con los iteradores, los programadores pueden iterar sobre tipos no secuenciales, es decir, tipos que no sean listas, elementos, diccionarios y conjuntos.

El iterador obtiene el siguiente elemento en el objeto a través del método next(), que puede considerarse como un flujo de datos. No sabemos su longitud, y es un cálculo perezoso calcular los siguientes datos mientras lo usamos. Se accede a los objetos Iterator desde el primer elemento de la colección hasta que se haya accedido a todos los elementos.

Las principales ventajas de los iteradores son el ahorro de memoria (durante el proceso de bucle, no es necesario leer los datos de una sola vez, lo que es especialmente útil cuando se trata de objetos de archivo, porque los archivos también son objetos de iterador), independientemente de los valores de índice, y cálculo perezoso (los valores se toman cuando es necesario) calcular);

with open('java.txt') as f:
    for line in f:
        print(line)

De esta forma, se lee y procesa una línea a la vez, en lugar de leer todo el archivo de una sola vez, lo que ahorra memoria.

La desventaja de los iteradores es que solo se pueden recorrer una vez, pero no la segunda vez. Porque al atravesar el acceso, solo se puede acceder a los datos de uno en uno, hasta que no haya más datos, no se puede retroceder. Es decir, el iterador solo puede avanzar y no retroceder.

l = ['a', 'n', 's', 'd']
l_iterator = iter(l)  # 通过iter函数得到一个迭代器
for i in l_iterator:
    print(i)

for i in l_iterator:  # 经过第一遍迭代后,迭代器中已经没有数据了,或者说已经访问到尾部了。
    print(i)

15.2.1 Obtener un iterador a través de la función iter()

Aunque tuple, list, dict y str son iterables, no son iteradores. La función iter() puede devolver un iterador. El recorrido se puede lograr a través del método next(), y se llama al método next, ya sea para obtener el siguiente objeto del contenedor o para obtener un error de StopIteration. La tupla y la lista de iteración de bucle de Python foresencialmente primero los convierten en iteradores y luego next()los implementan llamando a funciones continuamente.

l_iterator=iter([1,2,3,4,5])

15.2.1 Implementando iteradores a través de clases

Hay muchos iteradores implementados por clases en Python, como reversed() y enumerate() Al observar el código fuente, puede encontrar que estas clases implementan __next__métodos y __iter__métodos .

Si desea que un objeto de clase sea iterable, implementar una clase como un iterador es hacer que la clase herede de Iterable y luego anular dos métodos : __next__método y __iter__método .

__iter__El método devuelve un objeto iterador especial.

__next__devolverá el siguiente objeto iterador.

Por ejemplo, implemente un iterador decreciente que disminuya un entero positivo en 1 hasta que llegue a 0.

from collections.abc import Iterable


class Decrease(Iterable):
    def __init__(self, init):
        self.init = init

    def __iter__(self):  # 返回对象本身
        return self

    def __next__(self):
        while 0 < self.init:
            self.init -= 1
            return self.init # 返回下一个
        raise StopIteration  #  通过 raise 终断next


for i in Decrease(6):  # 可以用for循环迭代这个类对象了
    print(i)

15.3 el módulo itertools

Este módulo implementa una serie de iteradores rápidos y eficientes. Estos iteradores son útiles solos o combinados. Esta sección presentará algunos iteradores muy útiles.

Ayuda (itertools) para ver los iteradores integrados. Si usa el editor Pycharm, presione Shift dos veces, ingrese itertools, marque Incluir elementos que no sean del proyecto y seleccione el archivo itertools en la página emergente para ingresar el archivo itertools.py. Verá algo como esto, que enumera todos los iteradores proporcionados por el módulo itertools:

"""
Functional tools for creating and using iterators.

Infinite iterators:
count(start=0, step=1) --> start, start+step, start+2*step, ...
cycle(p) --> p0, p1, ... plast, p0, p1, ...
repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times

Iterators terminating on the shortest input sequence:
accumulate(p[, func]) --> p0, p0+p1, p0+p1+p2
chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ... 
chain.from_iterable([p, q, ...]) --> p0, p1, ... plast, q0, q1, ... 
compress(data, selectors) --> (d[0] if s[0]), (d[1] if s[1]), ...
dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails
groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)
filterfalse(pred, seq) --> elements of seq where pred(elem) is False
islice(seq, [start,] stop [, step]) --> elements from
       seq[start:stop:step]
starmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...
tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n
takewhile(pred, seq) --> seq[0], seq[1], until pred fails
zip_longest(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ... 

Combinatoric generators:
product(p, q, ... [repeat=1]) --> cartesian product
permutations(p[, r])
combinations(p, r)
combinations_with_replacement(p, r)
"""

A continuación, nos centramos en aprender algunos de estos iteradores.

15.3.1 Concatenación de cadenas de iteradores

Convierte varios iterables en un único iterador. En la barra de navegación de Pycharm, seleccione Navigate->File Structure para listar todas las clases en el módulo itertools.py.

inserte la descripción de la imagen aquí

Haga clic en la clase de cadena para ver la descripción de la cadena:

class chain(object):
    """
    chain(*iterables) --> chain object
    
    Return a chain object whose .__next__() method returns elements from the
    first iterable until it is exhausted, then elements from the next
    iterable, until all of the iterables are exhausted.
    """

La documentación dice, cree un iterador que primero devuelva todos los elementos en el primer iterable, luego todos los elementos en el siguiente iterable, hasta que se agoten todos los elementos en el iterable. Convierte varios iterables en un único iterador. Para dar algunos ejemplos, edite en Pycharm:

from itertools import chain

chain_iterator = chain("ABCDef", "1234")  # 两个可迭代对象:"ABCDef"和"1234"
print(list(chain_iterator))  # ['A', 'B', 'C', 'D', 'e', 'f', '1', '2', '3', '4']

Un uso común de chain() es cuando desea realizar alguna operación en todos los elementos en diferentes colecciones. Por ejemplo, desea sumar los cuadrados de los elementos de ambas listas y formar una nueva lista. Esto se puede hacer así:

from itertools import chain


def square_multi_iterables(*iterables):  # 定义一个生成器
    chain_iterator = chain(*iterables)  # 利用chain生成迭代器
    for l in chain_iterator:  # 对迭代器里面的元素求进行平方
        yield l * l  # yield 返回


if __name__ == '__main__':
    lst1 = [1, 2, 3]
    lst2 = [4, 5, 6]
    for i in square_multi_iterables(lst1, lst2):
        print(i)

    print(list(square_multi_iterables(lst2, lst1)))

¡Esta solución es mucho más elegante que usar dos bucles separados!

15.3.2 Acumula iterador acumula

Primero mira lo que dice el código fuente:

class accumulate(object):
    """
    accumulate(iterable[, func]) --> accumulate object
    
    Return series of accumulated sums (or other binary function results).
    """

Aplica un iterable a la función func y devuelve un iterador acumulativo del iterable. Si no se proporciona func, se devuelve un iterador que consta de la suma acumulada de los objetos iterables.

Mira un ejemplo para ilustrar:

from itertools import accumulate

lst = [1, 2, 3, 4, 5, 6]
for i in accumulate(lst):
    print(i)
print(list(accumulate(lst)))  # 返回 [1, 3, 6, 10, 15, 21]

Este ejemplo no proporciona la función func y el valor predeterminado es acumular y sumar la secuencia original. Echemos un vistazo al uso de proporcionar la función func.

from itertools import accumulate

lst = [1, 2, 3, 4, 5, 6]
for i in accumulate(lst, lambda x, y: x * y):
    print(i)
print(list(accumulate(lst, lambda x, y: x * y)))  # 返回 [1, 2, 6, 24, 120, 720]

15.3.3 Iteradores de permutación y combinación

Todavía mira lo que dice el código:

class combinations(object):
    """
    combinations(iterable, r) --> combinations object
    
    Return successive r-length combinations of elements in the iterable.
    
    combinations(range(4), 3) --> (0,1,2), (0,1,3), (0,2,3), (1,2,3)
    """

Devuelve una subsecuencia de longitud riterable que consta de los elementos del iterable de entrada . Las combinaciones se devuelven en orden lexicográfico. Entonces, si se ordena el iterable de entrada , también se ordena la tupla combinada resultante.

Los elementos en diferentes posiciones se consideran diferentes incluso si los elementos tienen el mismo valor. Si los elementos son individualmente distintos, no hay elementos duplicados en cada combinación.

Por ejemplo, cómo generar ['AB', 'AC', 'AD', 'BC', 'BD', 'CD'] según "ABCD"

def my_combinations(iterables, length):
    for i in combinations(iterables, length):  
        yield "".join(i)


if __name__ == '__main__':
    lst=[]
    for element in my_combinations("ABCD", 2):  # 两个元素组成的排列组合
        lst.append(element)

    print(lst)  # ['AB', 'AC', 'AD', 'BC', 'BD', 'CD']

15.3.3 El iterador de compresión compress

Mira lo que dice el código fuente:

class compress(object):
    """
    compress(data, selectors) --> iterator over selected data
    
    Return data elements corresponding to true selector elements.
    Forms a shorter iterator from selected data elements using the
    selectors to choose the data elements.
    """

Crea un iterador que devuelve los elementos en los datos para los cuales los selectores probaron la Trueverdad . El iterador se detiene en la longitud más corta de los dos.

print(list(compress('ABCDEF', [1, 0, 1, 0, 1, 1])))  # 输出['A', 'C', 'E', 'F']

Equivale al siguiente código:

def compress(data, selectors):
    return (d for d, s in zip(data, selectors) if s)

15.3.4 drop iterador dropwhile

Mira la descripción del código fuente:

class dropwhile(object):
    """
    dropwhile(predicate, iterable) --> dropwhile object
    
    Drop items from the iterable while predicate(item) is true.
    Afterwards, return every element until the iterable is exhausted.
    """

Crea un iterador que descarta estos elementos si el predicado es verdadero y devuelve otros elementos. El iterador no produce ningún resultado hasta que el predicado es falso por primera vez, por lo que puede tardar una cierta cantidad de tiempo de inicio.

por ejemplo:

from itertools import dropwhile

print(list(dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1])))  # 输出 [6, 4, 1]

Aproximadamente equivalente a:

def my_dropwhile(predicate, iterable):
    iterable = iter(iterable)
    for x in iterable:
        if not predicate(x):
            yield x  # 返回第一个不满足predicate的值后退出这个循环
            break
    for x in iterable:  # 接着循环剩下的元素
        yield x


print(list(my_dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1])))  # 输出 [6, 4, 1]

Para obtener más iteradores, consulte el código fuente de itertools.py.

15.3 Generadores (iteradores especiales)

Una función con rendimiento es un generador y un generador también es un iterador. Por lo tanto, los generadores también tienen las características de los iteradores anteriores.

La comprensión popular del rendimiento se puede combinar con la palabra clave de valor de retorno de la función, el rendimiento es un tipo especial de retorno. Se dice que es un retorno especial porque la ejecución regresa inmediatamente cuando encuentra yield, que es similar a return.

La diferencia es que la próxima vez que ingrese a la función, irá directamente a la siguiente declaración de rendimiento y, después de regresar a la función, aún comenzará la ejecución desde la primera línea de código en el cuerpo de la función.

Declarar un iterador es simple.La fórmula de comprensión de lista [i for i in range(100000000)] puede generar una lista de 100 millones de elementos. Cada elemento se guarda en la memoria después de la generación (este proceso es muy lento), ocupa una gran cantidad de memoria y se producirán errores OOM si no hay suficiente memoria. Sin embargo, a veces, no necesitamos guardar tantas cosas en la memoria por adelantado, como acumular y sumar elementos, solo necesitamos saber cuánto es cada elemento en el momento de la suma, y ​​podemos tirarlo cuando lo necesitemos. agotado. .

El generador generador no genera todos los valores por adelantado y los guarda en la memoria, solo cuando se llama al siguiente método (el método generador), se generará la siguiente variable, acelerando así la ejecución del código y ahorrando memoria .

Hay dos formas de conseguir un generador:

15.3.1 Generador de definición de paréntesis

Al reemplazar [] con (), simplemente puede cambiar la comprensión de la lista a un generador, y obtiene un generador (i for i in range(100000000)). La comprensión de lista es la más utilizada, pero intente convertir la comprensión de lista en un generador, porque ahorra memoria, y la idea de ahorrar memoria debe reflejarse en el código en todas partes, para reflejar el nivel.

15.3.2 Definición de generadores con la palabra clave yield

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

Se ejecuta cada vez que se llama a next(), la declaración de rendimiento se detiene, se devuelve el valor después de la expresión de rendimiento y la ejecución continúa desde la declaración de rendimiento cuando se llama de nuevo a next.

15.3.3 El valor de los generadores

Además de ahorrar memoria aprovechando el cálculo perezoso, los generadores también pueden mejorar la legibilidad del código. Por ejemplo, busque el subíndice inicial de cada palabra en un fragmento de texto. Solución sin generador:

def index_words(text):
    result = []  # 存放下标
    if text:
        result.append(0)
    for index, letter in enumerate(text, 1):  # 第二个参数是1,如果不写是什么情况
        if letter == " ":
            result.append(index)
    return result


if __name__ == '__main__':
    enumerate_desc = "The enumerate object yields pairs containing a count (from start, which defaults to zero) and a value yielded by the iterable argument."
    print(index_words(enumerate_desc))

Si usa un generador:

def index_words(text):
    if text:
        yield 0  # 第一次返回
    for index, letter in enumerate(text, 1): 
        if letter == " ":
            yield index  # 每次调用返回


if __name__ == '__main__':
    enumerate_desc = "The enumerate object yields pairs containing a count (from start, which defaults to zero) and a value yielded by the iterable argument."
    for index in index_words(enumerate_desc):
        print(index)
    print(list(index_words(enumerate_desc)))

El código que puede usar generadores es más limpio y tiene menos líneas de código. Cuando no se usa el generador, para cada resultado, primero se realiza una operación de adición y finalmente se devuelve el resultado. Al usar el generador, el índice de rendimiento directo reduce la interferencia causada por la operación de lista.Se puede ver de un vistazo que el código es para devolver el índice.

Los datos de consumo de Kafka pueden usar el generador de rendimiento. También se usa en la función fixture de pytest, y yield también se usa para iniciar webdrvier.

Asegúrate de usar mucho los generadores en tu trabajo. Tenga en cuenta, sin embargo, que dado que los generadores también son iteradores, los generadores solo se pueden recorrer una vez .

15.4 Preguntas de práctica

  1. Encuentre la lista de todos los índices de subíndices de un elemento en la lista
def index_generator(L, target):
    for i, num in enumerate(L):
        if num == target:
            yield i

print(list(index_generator([1, 6, 2, 4, 5, 2, 8, 6, 3, 2], 2)))

########## 输出 ##########

[2, 5, 9]

index_generator devolverá un objeto Generador, que debe convertirse en una lista usando la lista antes de que pueda imprimirse.

  1. Dadas dos secuencias, determine si la primera es una subsecuencia de la segunda.

El enlace de LeetCode está aquí: https://leetcode.com/problems/is-subsequence/. Una secuencia es una lista, y una subsecuencia significa que los elementos de una lista aparecen en orden en la segunda lista, pero no necesariamente uno al lado del otro. Por ejemplo, [1, 3, 5] es una subsecuencia de [1, 2, 3, 4, 5], pero [1, 4, 3] no lo es.

Para resolver este problema, el algoritmo convencional es el algoritmo voraz. Mantenga dos punteros apuntando al comienzo de las dos listas, luego barra la segunda secuencia hasta el final, si un número es el mismo que el primer puntero, luego avance el primer puntero un paso. Devuelve True cuando el primer puntero se mueve fuera del último elemento de la primera secuencia; de lo contrario, devuelve False.

Sin embargo, en esta sección, se utilizan generadores para lograr esto.

def is_subsequence(a, b):
    b = iter(b)
    return all(i in b for i in a)

print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))
print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5]))

########## 输出 ##########

True
False

Supongo que te gusta

Origin blog.csdn.net/liuchunming033/article/details/107896326
Recomendado
Clasificación