Python进阶知识(三)

文章目录

  • 1.Python 迭代器
  • 2.Python 生成器
  • 3.Python 列表推导式
  • 4.Python协程
    • 4.1 IO 密集型任务和 CPU 密集型任务
    • 4.2 豆瓣近日推荐电影爬虫

1.Python 迭代器

在Python中,迭代器(Iterator)是一种用于遍历集合元素的对象。它是一个实现了迭代器协议(Iterator Protocol)的对象,该协议包含两个方法:__iter____next__

  1. __iter__方法:返回迭代器对象自身。它在迭代开始之前被调用,用于初始化迭代器的状态。
  2. __next__方法:返回迭代器中的下一个元素。如果没有更多的元素可供返回,则抛出StopIteration异常。

要创建一个迭代器,你可以使用一个类来定义它,实现上述两个方法。下面是一个简单的示例:

class MyIterator:def __init__(self, max_num):self.max_num = max_numself.current = 0def __iter__(self):return selfdef __next__(self):if self.current < self.max_num:num = self.currentself.current += 1return numelse:raise StopIteration# 使用自定义迭代器
my_iter = MyIterator(5)
for num in my_iter:print(num)

输出结果将是:

Copy code
0
1
2
3
4

在上面的示例中,MyIterator类实现了迭代器协议。它通过__init__方法初始化迭代器的状态,在__next__方法中定义了迭代的逻辑,并在达到最大值时抛出StopIteration异常。__iter__方法返回迭代器对象自身,使得该对象可以在迭代过程中被使用。

除了自定义迭代器,Python还提供了一些内置的迭代器,例如rangeenumeratezip等。这些迭代器可以方便地用于不同的迭代场景,简化了迭代操作的编写。

2.Python 生成器

在Python中,生成器(Generator)是一种特殊类型的迭代器,可以通过函数来创建。它使用了一种称为"yield"的关键字,允许你暂停函数的执行并返回一个值,然后在需要时继续执行,从而实现了延迟计算。

生成器的特点是它们在每次迭代时生成一个值,而不是一次性生成所有值。这样可以减少内存占用,并且允许处理大量的数据流或无限序列。

要创建一个生成器,你可以使用函数来定义它,并在需要生成值时使用yield语句。下面是一个简单的示例:

def my_generator(max_num):for i in range(max_num):yield i# 使用生成器
my_gen = my_generator(5)
for num in my_gen:print(num)

输出结果将是:

Copy code
0
1
2
3
4

在上面的示例中,my_generator函数是一个生成器函数。它使用yield语句在每次迭代时生成一个值,并在下一次迭代时继续执行。通过将生成器函数调用赋值给变量my_gen,我们得到了一个生成器对象。然后,我们可以像使用其他迭代器一样使用生成器进行迭代。

生成器非常适合处理大型数据集或需要逐个生成值的场景。由于生成器只在需要时生成值,所以可以大大减少内存消耗。此外,生成器还可以用于处理无限序列,因为它们可以无限地生成值而不会耗尽内存。

除了使用yield语句创建生成器函数之外,Python还提供了生成器表达式(Generator Expression)的语法。它类似于列表推导式,但使用圆括号而不是方括号,并返回一个生成器对象。生成器表达式的语法更简洁,适用于简单的生成器场景。

my_gen = (x for x in range(5))
for num in my_gen:print(num)

输出结果与前面的示例相同。在这里,我们使用生成器表达式(x for x in range(5))创建了一个生成器对象,并对其进行迭代。

总结来说,生成器是一种使用函数和yield语句创建的特殊类型的迭代器。它提供了一种延迟计算的方式,逐个生成值,并且可以有效地处理大量的数据流或无限序列。

当处理大量数据或无限序列时,生成器的内存效率可以通过以下几个示例进行说明:

示例 1: 生成大量数据 假设你需要生成一个包含一百万个整数的列表。使用生成器的方式可以显著减少内存消耗。比较以下两种实现方式:

使用列表生成式:

my_list = [x for x in range(1000000)]

使用生成器:

def my_generator():for x in range(1000000):yield xmy_gen = my_generator()

在第一种实现方式中,使用列表生成式一次性生成了包含一百万个整数的列表,并将其存储在内存中。而在第二种实现方式中,使用生成器函数每次迭代只生成一个整数,并且不会一次性加载所有数据到内存中。这样,使用生成器的方式可以大大减少内存消耗。

示例 2: 处理无限序列 生成器非常适合处理无限序列,因为它们可以无限地生成值而不会耗尽内存。考虑生成斐波那契数列的示例:

def fibonacci():a, b = 0, 1while True:yield aa, b = b, a + bfib_gen = fibonacci()

在这个示例中,fibonacci函数是一个生成器函数,用于生成斐波那契数列。通过使用yield语句,每次迭代时生成一个斐波那契数,并且可以无限地生成下去。生成器fib_gen可以用于按需获取斐波那契数列的值,而不需要将整个序列加载到内存中。

通过以上示例,可以看出生成器在处理大量数据或无限序列时的内存效率。它们按需生成值,减少了内存消耗,并且能够处理比可用内存更大的数据集或无限序列。

3.Python 列表推导式

Python 中的列表推导式(List Comprehension)是一种简洁而强大的语法,用于创建新的列表,通常基于现有的列表或其他可迭代对象进行转换、过滤或组合操作。它的基本语法结构如下:

pythonCopy code
new_list = [expression for item in iterable if condition]

解释一下这个语法结构中的各部分:

  • expression 是一个用于对每个元素进行转换或操作的表达式。
  • item 是用于迭代的变量,代表可迭代对象中的每个元素。
  • iterable 是一个可迭代对象,如列表、元组、字符串或其他可迭代对象。
  • if condition(可选)是一个条件表达式,用于过滤元素。只有满足条件的元素才会被包含在新列表中。

下面是一些示例来说明列表推导式的使用:

示例 1: 创建新的列表

pythonCopy code
numbers = [1, 2, 3, 4, 5]
squared_numbers = [x**2 for x in numbers]
print(squared_numbers)  # 输出: [1, 4, 9, 16, 25]

在这个示例中,通过列表推导式将原始列表 numbers 中的每个元素平方,生成一个新的列表 squared_numbers

示例 2: 过滤元素

pythonCopy code
numbers = [1, 2, 3, 4, 5]
even_numbers = [x for x in numbers if x % 2 == 0]
print(even_numbers)  # 输出: [2, 4]

在这个示例中,通过列表推导式将原始列表 numbers 中的偶数元素提取出来,生成一个新的列表 even_numbers。只有满足条件 x % 2 == 0 的元素才会被包含在新列表中。

示例 3: 字符串处理

pythonCopy code
words = ['hello', 'world', 'python']
capitalized_words = [word.upper() for word in words]
print(capitalized_words)  # 输出: ['HELLO', 'WORLD', 'PYTHON']

在这个示例中,通过列表推导式将原始列表 words 中的每个字符串转换为大写形式,生成一个新的列表 capitalized_words

除了上述示例,列表推导式还可以嵌套、使用多个迭代变量和条件表达式,以及与函数等结合使用,从而实现更复杂的转换和过滤操作。

列表推导式是一种简洁而强大的语法,可以使代码更加清晰和高效。然而,当列表推导式变得过于复杂或难以理解时,应考虑使用传统的循环方式来替代,以提高代码的可读性和可维护性。

4.Python协程

在 Python 中,协程(Coroutines)是一种并发编程的技术,用于在单线程中实现异步操作和并发任务。协程允许在执行过程中暂停和恢复函数的执行,并在需要时交替执行多个任务,以实现更高效的异步编程。

Python 3.4 引入了 asyncio 模块,它提供了对协程的支持,使得在 Python 中使用协程变得更加简单和方便。以下是协程的基本概念和用法:

  1. 协程函数(Coroutine Function):协程函数使用 async def 声明,并在函数体中使用 await 关键字来暂停执行,让出控制权给事件循环(Event Loop)。例如:

    import asyncioasync def my_coroutine():# 执行一些异步操作await asyncio.sleep(1)print("Coroutine executed")
    
  2. 事件循环(Event Loop):事件循环是协程的调度器,负责调度协程的执行顺序和管理异步任务。通过 asyncio.get_event_loop() 获取当前线程的事件循环对象,然后使用 loop.run_until_complete() 来运行协程。

    loop = asyncio.get_event_loop()
    loop.run_until_complete(my_coroutine())
    
  3. 异步任务(Task):可以通过 asyncio.create_task() 创建一个任务,将协程函数包装为一个可调度的对象。任务可以并发执行,可以使用 await 关键字等待任务的完成。

    async def main():task1 = asyncio.create_task(my_coroutine())task2 = asyncio.create_task(my_coroutine())await asyncio.gather(task1, task2)
    

通过使用协程和 asyncio 库,可以实现高效的并发编程。协程可以在遇到阻塞操作时暂停自己,切换到其他任务执行,从而充分利用系统资源并提高程序的响应性。它们在网络编程、IO密集型任务和并发处理等场景中非常有用。

需要注意的是,协程并不是多线程或多进程的替代方案。协程在单线程中运行,并使用事件循环调度任务的执行,因此适合处理 IO 密集型任务,而不是 CPU 密集型任务。此外,协程的性能和效果也取决于具体的应用场景和使用方式。

4.1 IO 密集型任务和 CPU 密集型任务

  1. IO 密集型任务(IO-bound tasks): IO 密集型任务是指任务的主要瓶颈在于输入/输出操作(IO 操作),例如从磁盘读取文件、网络请求、数据库查询等。这些任务通常涉及与外部资源的交互,需要等待IO操作完成,而任务本身在等待的过程中并不会占用大量的 CPU 资源。在执行 IO 操作时,CPU 大部分时间都处于空闲状态。

    示例:网页爬虫、文件读写、网络请求、图像处理等涉及到读写磁盘、网络传输或数据库查询的任务都属于 IO 密集型任务。

  2. CPU 密集型任务(CPU-bound tasks): CPU 密集型任务是指任务的主要瓶颈在于 CPU 计算能力,需要大量的 CPU 运算来完成任务。这些任务通常涉及复杂的数学计算、算法运算、图像处理、加密解密等,需要大量的 CPU 资源进行计算。在执行 CPU 密集型任务时,CPU 被长时间占用,而等待 IO 操作完成的时间相对较少。

    示例:科学计算、图像/视频处理、加密解密、大规模数据分析等需要大量计算的任务都属于 CPU 密集型任务。

区分 IO 密集型任务和 CPU 密集型任务的重要性在于对资源的合理利用。在 IO 密集型任务中,任务的执行时间主要花费在等待 IO 操作上,因此可以通过异步编程、并发处理或使用多线程/多进程来提高效率。而在 CPU 密集型任务中,任务的执行时间主要花费在 CPU 运算上,因此可以通过优化算法、并行计算、使用多线程/多进程甚至利用分布式计算来提高效率。

了解任务的性质(IO 密集型还是 CPU 密集型)有助于选择适当的编程模型、并发策略和资源分配方式,从而使程序能够充分利用可用的计算资源,提高执行效率和性能。

4.2 豆瓣近日推荐电影爬虫

import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def fetch_content(url):async with aiohttp.ClientSession(headers=header, connector=aiohttp.TCPConnector(ssl=False)) as session:async with session.get(url) as response:return await response.text()
async def main():url = "https://movie.douban.com/cinema/later/beijing/"init_page = await fetch_content(url)init_soup = BeautifulSoup(init_page, 'lxml')movie_names, urls_to_fetch, movie_dates = [], [], []all_movies = init_soup.find('div', id="showing-soon")
for
each_movie in all_movies.find_all('div', class_="item"):
all_a_tag = each_movie.find_all('a')
all_li_tag = each_movie.find_all('li')
movie_names.append(all_a_tag[1].text)
urls_to_fetch.append(all_a_tag[1]['href'])
movie_dates.append(all_li_tag[0].text)
tasks = [fetch_content(url) for url in urls_to_fetch]
pages = await asyncio.gather(*tasks)for movie_name, movie_date, page in zip(movie_names, movie_dates, pages):soup_item = BeautifulSoup(page, 'lxml')img_tag = soup_item.find('img')print('{} {} {}'.format(movie_name, movie_date, img_tag['src']))
%time asyncio.run(main())

这段代码展示了使用 asyncio、aiohttp 和 BeautifulSoup 库来进行异步网页爬取的示例。逐步解释代码的执行过程:

  1. 导入必要的模块:代码中导入了 asyncio、aiohttp 和 BeautifulSoup 模块,用于实现异步编程和网页解析。
  2. 定义异步函数 fetch_content(url):这是一个异步函数,用于获取给定 URL 的网页内容。它使用 aiohttp 库创建一个异步会话(ClientSession),发送 HTTP 请求并返回响应的文本内容。
  3. 定义异步函数 main():这是主要的异步函数,用于执行网页爬取的主要逻辑。首先,通过调用 fetch_content(url) 函数获取初始页面的内容,并使用 BeautifulSoup 进行解析。
  4. 解析页面内容:使用 BeautifulSoup 解析初始页面,获取显示即将上映电影信息的 div 元素(id=“showing-soon”)。
  5. 遍历电影信息:通过遍历每个电影的 div 元素,提取电影名称、URL 和上映日期,并将它们分别存储在 movie_names、urls_to_fetch 和 movie_dates 列表中。
  6. 创建任务列表:根据获取到的电影 URL 列表(urls_to_fetch),创建一个异步任务列表 tasks,其中每个任务都是调用 fetch_content(url) 函数获取网页内容的异步任务。
  7. 并发执行任务:使用 asyncio.gather() 函数并发执行任务列表中的所有任务,并使用 await 关键字等待所有任务完成,返回得到的网页内容列表 pages。
  8. 处理每个电影的网页内容:通过使用 zip() 函数将电影名称、上映日期和对应的网页内容进行配对,然后使用 BeautifulSoup 解析每个网页内容,并提取电影海报的 URL。
  9. 打印结果:将电影名称、上映日期和海报 URL 打印出来。
  10. 计算执行时间:使用 %time 计算程序的运行时间。
  11. 运行主函数:通过 asyncio.run() 运行主函数 main(),启动异步爬取过程。

该代码利用 asyncio 和 aiohttp 实现了异步的网页爬取过程,可以高效地同时请求多个网页并进行处理。通过异步编程,可以充分利用网络请求等IO操作的等待时间,提高爬取效率和程序的整体性能。

本文链接:https://my.lmcjl.com/post/8303.html

展开阅读全文

4 评论

留下您的评论.