必会的5个Python小技巧

一、装饰器

Python 装饰器是添加到另一个函数中的函数,然后可以添加附加功能或修改其行为,而无需直接更改其源代码。它们通常作为装饰器实现,装饰器是一种特殊的函数,将另一个函数作为输入并对其功能进行一些更改。我最喜欢的 Python 装饰器是 Timer 装饰器,它测量任何函数的执行时间。

它有助于分析和优化代码。

import time
def timer(func):
    def wrapper(*args, **kwargs):
        # start the timer
        start_time = time.time()
        # call the decorated function
        result = func(*args, **kwargs)
        # remeasure the time
        end_time = time.time()
        # compute the elapsed time and print it
        execution_time = end_time - start_time
        print(f"Execution time: {execution_time} seconds")
        # return the result of the decorated function execution
        return result
    # return reference to the wrapper function
    return wrapper

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

在包装函数中,我们使用提供的参数调用所需的函数。

使用以下行来完成此操作:result = func(*args, **kwargs)。

最后,包装函数返回装饰函数的执行结果。

装饰器函数应该返回对我们刚刚创建的包装器函数的引用。

要使用装饰器,你可以使用 @ 符号并将其应用到所需的函数。然后,你可以使用以下方法简单地测量函数所花费的时间:

@timer
def train_model():
    print("Starting the model training function...")
    # simulate a function execution by pausing the program for 5 seconds
    time.sleep(5) 
    print("Model training completed!")

train_model() 

输出:

Starting the model training function…

Model Training completed!

Execution time: 5.006425619125366 seconds

二、多线程

多线程是一种在 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"]

# Create and start a thread for each URL
threads = []
for url in urls:
    thread = threading.Thread(target=download_url, args=(url,))
    threads.append(thread)
    thread.start()

# Wait for all threads to finish
for thread in threads:
    thread.join()
# Rather than downloading the urls sequentially, the requests are sent similutatncy 
print("All downloads complete.")

3、多重处理

与多线程相比,多重处理更适合 CPU 密集型任务,其执行受到处理能力的限制,而不是等待外部资源。多处理涉及运行多个独立的进程,每个进程都有自己的内存空间。每个进程都可以执行自己的 Python 解释器,并独立于其他进程运行自己的代码。这对于需要大量计算能力的 CPU 密集型任务(例如数据处理、数值计算和模拟)非常有用。

提示:请注意不要贪婪并同时启动太多进程,因为这可能会导致内存不足问题。

下面是一个玩具示例代码,演示了在机器学习项目中使用多重处理同时并行处理多个图像的强大功能。

# Some function to process images and save it in independet location
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)]

# Create and start a process for each image
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()

# Wait for all processes to finish
for process in processes:
    process.join()

print("Image processing complete.")

提示:为了避免可能的错误,请确保流程尽可能相互独立。在上面的示例代码中,每个图像都被读取并保存在独立的位置,没有任何内存共享或全局变量。

4、缓存

缓存是存储和重用先前计算或获取的数据的过程,以避免冗余计算或昂贵的操作,从而提高性能和效率。

想象一下你正在构建一个天气应用程序来获取给定城市的天气数据。由于天气数据不会每秒发生变化,缓存可以帮助你避免对同一城市的天气重复发出 API 请求,从而减少网络流量和响应时间。另一个例子是显示人们个人资料的网站。每次查看某人的个人资料时,网站都必须向数据库询问他们的信息。这可能需要时间和资源。因此,在这种情况下,网站不会一遍又一遍地获取相同的数据,而是将其存储起来以便快速访问。这样,就像有了一个快捷方式,可以帮助网站更高效地工作。

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

import requests
from cachetools import cached, TTLCache

# Create a cache with a time-to-live (TTL) of 300 seconds (5 minutes)
weather_cache = TTLCache(maxsize=100, ttl=300)

@cached(weather_cache)
def get_weather(city):
    print(f"Fetching weather data for {city}...")
    # This is a dummy API and won't work if you run the code
    response = requests.get(f"https://api.example.com/weather?city={city}")
    return response.json()

# Fetch weather for a city (API call will be made)
city1_weather = get_weather("New York")

# Fetch weather for the same city again (cached result will be used)
city1_weather_cached = get_weather("New York")

# Fetch weather for a different city (API call will be made)
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 请求以获取该城市的天气数据。

五、生成器

生成器是 Python 迭代器,非常适合迭代大型数据集或序列,而无需立即将整个数据集加载到内存中。它们一次生成一个值,从而减少内存消耗。

使用生成器创建列表时,会根据需要一次生成一个元素。

当处理大量元素或考虑内存使用时,这特别有用。相比之下,创建不带生成器的列表会预先为整个列表预先分配内存,如果你不需要一次获取所有元素,则效率可能会很低,并且如果列表足够大,则可能会导致内存不足错误。

在 Python 中,你可以使用称为生成器函数的特殊类型函数创建生成器。生成器函数使用 yield 关键字一次生成一个值,允许你迭代一系列值。

def large_dataset_generator(data):
    for item in data:
        yield process_item(item)
# Usage
data = [...]  # Large dataset
for processed_item in large_dataset_generator(data):
    # Process each item one at a time
    print(processed_item)

yield process_item(item) 象征着该函数是一个生成器。它使用名为 process_item() 的函数处理每个项目,并在循环迭代时逐一生成处理结果。同样的情况也会发生在 large_dataset_generator(data) 中的 forprocessed_item 上,其中每个数据项将被迭代处理并分配给变量processed_item。

结论

总之,学习这五个高级 Python 概念为创建高效、健壮和可扩展的应用程序提供了许多可能性。

  • 装饰器使我们能够通过将行为应用于函数和方法而不改变其核心逻辑来增强代码的可读性、可重用性和可维护性。
  • 多线程和多处理为 Python 应用程序引入了并行性,使它们能够利用现代多核处理器。
  • 缓存为优化重复性或资源密集型操作提供了实用的解决方案。通过将计算结果和数据存储在内存中,缓存显着减少了处理时间并增强了应用程序的整体响应能力。
  • 生成器展示了 Python 处理大型数据集或值序列的优雅方法。通过即时生成值,生成器无需将整个序列存储在内存中,从而节省资源并能够有效地处理海量数据流。
© 版权声明
THE END
喜欢就支持一下吧!
点赞61 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容