跳转至主要内容

Python编程

五个高级Python概念

Sprite
发表于 2023年10月15日

当我第一次看到Python装饰器,这个概念对我来说一直很困惑,我在工作中从未真正使用过它们。然而,当我发现它们有多么有用时,我对它们和其他高级概念的态度完全改变了。在本文中,我将介绍几个在我的日常工作流程中变得必不可少的高级Python概念。

装饰者

Python装饰器是添加到另一个函数中的函数,可以在不直接更改源代码的情况下添加额外的功能或修改其行为。它们通常被实现为装饰器,这些装饰器是特殊的函数,接受另一个函数作为输入,并对其功能进行一些更改。

我最喜欢的Python装饰器是计时器装饰器,它可以测量任何函数的执行时间并打印出来。它在代码的性能分析和优化中非常有帮助。

import time

def timer(func):
def wrapper(*args, **kwargs):
# 记录开始时间
start_time = time.time()
# 调用被装饰函数
result = func(*args, **kwargs)
# 记录结束时间
end_time = time.time()
# 计算执行时间
execution_time = end_time - start_time
print(f"Execution time: {execution_time} seconds")
return result
return wrapper

为了在Python中创建装饰器,我们需要定义一个名为 timer 的函数,该函数接受一个名为 func 的参数,以指示它是一个装饰器函数。在计时器函数内部,我们定义另一个名为 wrapper 的函数,该函数接受通常传递给我们想要装饰的函数的参数。

在包装函数内部,我们使用提供的参数调用所需的函数。我们可以使用以下代码行来实现这一点: result = func(*args, **kwargs) 。

最后,包装函数返回装饰函数执行的结果。装饰器函数应该返回对刚刚创建的包装函数的引用。

要使用装饰器,您可以使用 @ 符号将其应用于所需的函数。

您可以通过以下方式简单地测量函数所需的时间:

@timer
def train_model():
print("Starting the model training function...")
# 模拟执行5秒
time.sleep(5)
print("Model training completed!")

train_model()

 

多线程

多线程是在Python程序中并行执行任务的一种技术。它对于I/O密集型任务非常有用,其中线程花费大量时间等待外部资源,如网络请求或文件I/O。

线程是进程内的最小执行单位。多个线程共享同一内存空间,这意味着它们可以访问相同的变量和数据结构。

多线程使用并发概念,这意味着当一个线程在等待服务器的响应时,另一个线程可以开始它的请求。这种并发性允许程序在等待期间高效利用可用的CPU周期。

这里有一个实际的例子,我们可以应用多线程来提高程序的执行速度:

import threading
import requests

def download_url(url):
response = requests.get(url)
print(f"Downloaded {url}")

urls = ["https://www.example.com", "https://www.google.com", "https://www.github.com"]

# 创建并开始每个线程
threads = []
for url in urls:
thread = threading.Thread(target=download_url, args=(url,))
threads.append(thread)
thread.start()

# 等待所有线程完成
for thread in threads:
thread.join()
print("All downloads complete.")

多进程

与多线程相比,多进程更适用于CPU密集型任务。多进程涉及运行多个独立的进程,每个进程都有自己的内存空间。每个进程可以执行自己的Python解释器,并独立于其他进程运行自己的代码。这在需要大量计算能力的CPU密集型任务中非常有用,比如数据处理、数值计算和模拟。

这个示例代码,展示了在机器学习项目中使用多进程同时并行处理多个图像的强大能力

import multiprocessing
from PIL import Image, ImageFilter
import os

# 处理函数
def process_image(image_path, output_path):
image = Image.open(image_path)
blurred_image = image.filter(ImageFilter.GaussianBlur(5))
blurred_image.save(output_path)
print(f"Processed: {image_path} -> {output_path}")

input_folder = "input_images"
output_folder = "output_images"
os.makedirs(output_folder, exist_ok=True)

image_paths = [os.path.join(input_folder, filename) for filename in os.listdir(input_folder)]

# 创建并开始每个进程
processes = []
for image_path in image_paths:
output_path = os.path.join(output_folder, os.path.basename(image_path))
process = multiprocessing.Process(target=process_image, args=(image_path, output_path))
processes.append(process)
process.start()

# 等待所有进程完成
for process in processes:
process.join()

print("Image processing complete."

缓存

想象一下,你正在构建一个天气应用程序,用于获取给定城市的天气数据。由于天气数据不会每秒钟都发生变化,缓存可以帮助你避免为同一城市的天气进行重复的API请求,从而减少网络流量和响应时间。

另一个例子是一个展示人们个人资料的网站。每当有人查看某人的个人资料时,网站都需要向数据库请求他们的信息。这可能需要花费时间和资源。

现在让我们尝试使用Python来实现第一个示例。对于这个示例,让我们使用 requests 库来进行HTTP请求,并使用 cachetools 库进行缓存。

import requests
from cachetools import cached, TTLCache

# 创建一个TTL缓存
weather_cache = TTLCache(maxsize=100, ttl=300)

@cached(weather_cache)
def get_weather(city):
print(f"Fetching weather data for {city}...")
response = requests.get(f"https://api.example.com/weather?city={city}")
return response.json()

# 调用获取天气api,获取New York天气(这个api发起http请求)
city1_weather = get_weather("New York")

# 重新调用获取天气api(这次直接从cache获取数据,因为New York城市已经发起过http请求了)
city1_weather_cached = get_weather("New York")

# 调用获取天气api,获取Los Angeles天气(这个api发起http请求)
city2_weather = get_weather("Los Angeles")

print("City 1 Weather (API call):", city1_weather)
print("City 1 Weather (Cached):", city1_weather_cached)
print("City 2 Weather (API call):", city2_weather)

在这个例子中, get_weather 函数被 cachetools 库中的 @cached 装饰器装饰。缓存具有最大大小( maxsize )和生存时间( ttl )的设定。 maxsize 决定缓存可以容纳多少条目,而 ttl 指定了条目在缓存中应保留的时间长度(以秒为单位)。

装饰器会自动处理缓存过程。当你调用被装饰的函数时,它会首先检查给定输入参数(在这种情况下是城市名称)的结果是否已经存在于缓存中。如果结果已经存在于缓存中,并且根据TTL(生存时间)尚未过期,缓存的数据将直接返回,而无需进行API调用。如果数据不在缓存中或已过期,函数将继续发起API请求,以获取该城市的天气数据。

 5—生成器

生成器是Python的迭代器,非常适合在不一次性加载整个数据集到内存中的情况下迭代大型数据集或序列。它们实时生成值,一次生成一个,从而减少内存消耗。

使用生成器创建列表时,元素会按需逐个生成。这在处理大量元素或关注内存使用时特别有用。相比之下,不使用生成器创建列表会预先分配整个列表的内存空间,如果不需要一次性使用所有元素,这种方式可能效率低下,并且如果列表足够大,可能会导致内存溢出错误。

在Python中,您可以使用一种特殊类型的函数称为生成器函数来创建生成器。生成器函数使用关键字 yield 来逐个产生值,使您能够迭代一系列的值。

def large_dataset_generator(data):
for item in data:
yield process_item(item)
# 用法
data = [...] # 大数据集
for processed_item in large_dataset_generator(data):
# 一次处理每一个项
print(processed_item)

yield process_item(item) 符号表示该函数是一个生成器。它使用一个名为 process_item() 的函数处理每个 item ,并在循环迭代时逐个产生处理后的结果。

同样的情况也会发生在 processed_item in large_dataset_generator(data) 上,其中每个数据项都会被迭代处理,并赋值给变量processed_item。

 结论

这五个高级的Python概念为创建高效、稳健和可扩展的应用程序打开了许多可能性。

  • 装饰器使我们能够通过对函数和方法应用行为而不改变其核心逻辑,提高代码的可读性、可重用性和可维护性。

  • 多线程和多进程为Python应用程序引入了并行性,使其能够充分利用现代多核处理器。

  • 缓存提供了一种实用的解决方案,用于优化重复或资源密集型操作。通过将计算结果和数据存储在内存中,缓存显著减少了处理时间,并提高了应用程序的整体响应能力。

  • 生成器展示了Python处理大型数据集或值序列的优雅方法。通过实时生成值,生成器消除了将整个序列存储在内存中的需要,节省资源并能够高效地处理大规模数据流。

你在平时的工作学习中,有用到哪些高级特性,欢迎在下面讨论。

分类:
标签:

评论已关闭。