1.Python 迭代器
在Python中,迭代器(Iterator)是一种用于遍历集合元素的对象。它是一个实现了迭代器协议(Iterator Protocol)的对象,该协议包含两个方法:__iter__
和__next__
。
-
__iter__
方法:返回迭代器对象自身。它在迭代开始之前被调用,用于初始化迭代器的状态。 -
__next__
方法:返回迭代器中的下一个元素。如果没有更多的元素可供返回,则抛出StopIteration
异常。
要创建一个迭代器,你可以使用一个类来定义它,实现上述两个方法。下面是一个简单的示例:
class MyIterator:
def __init__(self, max_num):
self.max_num = max_num
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current < self.max_num:
num = self.current
self.current += 1
return num
else:
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还提供了一些内置的迭代器,例如range
、enumerate
、zip
等。这些迭代器可以方便地用于不同的迭代场景,简化了迭代操作的编写。
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 x
my_gen = my_generator()
在第一种实现方式中,使用列表生成式一次性生成了包含一百万个整数的列表,并将其存储在内存中。而在第二种实现方式中,使用生成器函数每次迭代只生成一个整数,并且不会一次性加载所有数据到内存中。这样,使用生成器的方式可以大大减少内存消耗。
示例 2: 处理无限序列 生成器非常适合处理无限序列,因为它们可以无限地生成值而不会耗尽内存。考虑生成斐波那契数列的示例:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib_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 中使用协程变得更加简单和方便。以下是协程的基本概念和用法:
-
协程函数(Coroutine Function):协程函数使用
async def
声明,并在函数体中使用await
关键字来暂停执行,让出控制权给事件循环(Event Loop)。例如:import asyncio async def my_coroutine(): # 执行一些异步操作 await asyncio.sleep(1) print("Coroutine executed")
-
事件循环(Event Loop):事件循环是协程的调度器,负责调度协程的执行顺序和管理异步任务。通过
asyncio.get_event_loop()
获取当前线程的事件循环对象,然后使用loop.run_until_complete()
来运行协程。loop = asyncio.get_event_loop() loop.run_until_complete(my_coroutine())
-
异步任务(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 密集型任务
-
IO 密集型任务(IO-bound tasks): IO 密集型任务是指任务的主要瓶颈在于输入/输出操作(IO 操作),例如从磁盘读取文件、网络请求、数据库查询等。这些任务通常涉及与外部资源的交互,需要等待IO操作完成,而任务本身在等待的过程中并不会占用大量的 CPU 资源。在执行 IO 操作时,CPU 大部分时间都处于空闲状态。
示例:网页爬虫、文件读写、网络请求、图像处理等涉及到读写磁盘、网络传输或数据库查询的任务都属于 IO 密集型任务。
-
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 库来进行异步网页爬取的示例。逐步解释代码的执行过程:文章来源:https://www.toymoban.com/news/detail-498346.html
- 导入必要的模块:代码中导入了 asyncio、aiohttp 和 BeautifulSoup 模块,用于实现异步编程和网页解析。
- 定义异步函数 fetch_content(url):这是一个异步函数,用于获取给定 URL 的网页内容。它使用 aiohttp 库创建一个异步会话(ClientSession),发送 HTTP 请求并返回响应的文本内容。
- 定义异步函数 main():这是主要的异步函数,用于执行网页爬取的主要逻辑。首先,通过调用 fetch_content(url) 函数获取初始页面的内容,并使用 BeautifulSoup 进行解析。
- 解析页面内容:使用 BeautifulSoup 解析初始页面,获取显示即将上映电影信息的 div 元素(id=“showing-soon”)。
- 遍历电影信息:通过遍历每个电影的 div 元素,提取电影名称、URL 和上映日期,并将它们分别存储在 movie_names、urls_to_fetch 和 movie_dates 列表中。
- 创建任务列表:根据获取到的电影 URL 列表(urls_to_fetch),创建一个异步任务列表 tasks,其中每个任务都是调用 fetch_content(url) 函数获取网页内容的异步任务。
- 并发执行任务:使用 asyncio.gather() 函数并发执行任务列表中的所有任务,并使用 await 关键字等待所有任务完成,返回得到的网页内容列表 pages。
- 处理每个电影的网页内容:通过使用 zip() 函数将电影名称、上映日期和对应的网页内容进行配对,然后使用 BeautifulSoup 解析每个网页内容,并提取电影海报的 URL。
- 打印结果:将电影名称、上映日期和海报 URL 打印出来。
- 计算执行时间:使用 %time 计算程序的运行时间。
- 运行主函数:通过 asyncio.run() 运行主函数 main(),启动异步爬取过程。
该代码利用 asyncio 和 aiohttp 实现了异步的网页爬取过程,可以高效地同时请求多个网页并进行处理。通过异步编程,可以充分利用网络请求等IO操作的等待时间,提高爬取效率和程序的整体性能。文章来源地址https://www.toymoban.com/news/detail-498346.html
到了这里,关于Python进阶知识(三)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!