让你的 Python 代码更快的 9 个小技巧

"Python太慢了"。

在有关编程语言的讨论中,这种观点经常出现,常常掩盖了 Python 的众多优点。

事实上,如果你能以 Python 的方式编写 Python,那么 Python 是很快的。

细节决定成败。经验丰富的 Python 开发人员掌握了大量微妙而强大的技巧,可以显著提高代码的性能。

这些技巧乍看之下可能不起眼,但却能大幅提高效率。让我们深入了解其中的 9 种方法,改变您编写和优化 Python 代码的方式。

1. 更快的字符串连接: 巧妙选择 "join() "或 "+"

如果有大量字符串等待处理,字符串连接将成为 Python 程序的瓶颈。

基本上,Python 中有两种字符串连接方式:

  • 使用 join() 函数将字符串列表合并为一个 = 使用 ++= 符号将每个字符串添加到一个字符串中

那么哪种方式更快呢?

废话不多说,让我们定义 3 个不同的函数来连接相同的字符串:

python 复制代码
mylist = ["Yang", "Zhou", "is", "writing"]


# Using '+'
def concat_plus():
    result = ""
    for word in mylist:
        result += word + " "
    return result


# Using 'join()'
def concat_join():
    return " ".join(mylist)


# Directly concatenation without the list
def concat_directly():
    return "Yang" + "Zhou" + "is" + "writing"

根据您的第一印象,您认为哪个函数最快,哪个函数最慢?

真正的结果可能会让你大吃一惊:

python 复制代码
import timeit

print(timeit.timeit(concat_plus, number=10000))
# 0.002738415962085128

print(timeit.timeit(concat_join, number=10000))
# 0.0008482920238748193

print(timeit.timeit(concat_directly, number=10000))
# 0.00021425005979835987

如上所示,在连接字符串列表时,join() 方法比在 for 循环中逐个添加字符串更快。

原因很简单。一方面,字符串在 Python 中是不可变的数据,每次 += 操作都会创建一个新字符串并复制旧字符串,计算成本很高。

另一方面,.join() 方法专门针对连接字符串序列进行了优化。它会预先计算所生成字符串的大小,然后一次性创建。因此,它避免了循环中 += 操作带来的开销,因此速度更快。

不过,在我们的测试中,速度最快的函数是直接连接字符串字面量。它的高速是由于:

  • Python 解释器可以在编译时优化字符串字面量的连接,把它们变成一个字符串字面量。它不涉及循环迭代或函数调用,因此是一种非常高效的操作。
  • 由于所有字符串在编译时都是已知的,Python 可以非常快速地执行此操作,比循环中的运行时连接或经过优化的 .join() 方法要快得多。

总之,如果需要连接字符串列表,请选择 join() 而不是 +=。如果你想直接连接字符串,只需使用 + 即可。

2. 更快地创建列表:使用 []而不是 list()

创建列表并不难。两种常见的方法是

  • 使用 list() 函数
  • 直接使用 []

让我们用一个简单的代码片段来测试它们的性能:

python 复制代码
import timeit

print(timeit.timeit('[]', number=10 ** 7))
# 0.1368238340364769

print(timeit.timeit(list, number=10 ** 7))
# 0.2958830420393497

结果显示,执行 list() 函数要比直接使用 [] 慢。

这是因为 [] 是字面语法,而 list() 是构造函数调用。毫无疑问,调用函数需要额外的时间。

根据同样的逻辑,在创建字典时,我们也应该使用 {} 而不是 dict()

3. 更快的成员函数测试:使用集合而不是列表

成员检查操作的性能在很大程度上取决于底层数据结构:

python 复制代码
import timeit

large_dataset = range(100000)
search_element = 2077

large_list = list(large_dataset)
large_set = set(large_dataset)


def list_membership_test():
    return search_element in large_list


def set_membership_test():
    return search_element in large_set


print(timeit.timeit(list_membership_test, number=1000))
# 0.01112208398990333
print(timeit.timeit(set_membership_test, number=1000))
# 3.27499583363533e-05

正如上面的代码所示,集合中的成员资格测试比列表中的成员资格测试要快得多。

为什么会这样呢?

  • 在 Python 列表中,成员测试(列表中的元素)是通过遍历每个元素来完成的,直到找到所需的元素或到达列表的末尾。因此,该操作的时间复杂度为 O(n)
  • Python 中的集合是以散列表的形式实现的。在检查成员资格(元素在集合中)时,Python 使用散列机制,其时间复杂度平均为 O(1)

这里的重点是,在编写程序时要仔细考虑底层数据结构。利用正确的数据结构可以大大加快代码的运行速度。

4. 更快的数据生成:使用 For 循环的推导式

Python 中有四种类型的推导式:列表、字典、集合和生成器。它们不仅为创建相对数据结构提供了更简洁的语法,而且比使用 for 循环有更好的性能。因为它们在 Python 的 C 语言实现中进行了优化。

python 复制代码
import timeit


def generate_squares_for_loop():
    squares = []
    for i in range(1000):
        squares.append(i * i)
    return squares


def generate_squares_comprehension():
    return [i * i for i in range(1000)]


print(timeit.timeit(generate_squares_for_loop, number=10000))
# 0.2797503340989351

print(timeit.timeit(generate_squares_comprehension, number=10000))
# 0.2364629579242319

上面的代码是列表理解和 for 循环之间的简单速度比较。结果显示,列表推导式更快。

5. 更快的循环:优先考虑局部变量

在 Python 中,访问局部变量比访问全局变量或对象的属性更快。

下面是一个例子来证明这一点:

python 复制代码
import timeit


class Example:
    def __init__(self):
        self.value = 0


obj = Example()


def test_dot_notation():
    for _ in range(1000):
        obj.value += 1


def test_local_variable():
    value = obj.value
    for _ in range(1000):
        value += 1
    obj.value = value


print(timeit.timeit(test_dot_notation, number=1000))
# 0.036605041939765215
print(timeit.timeit(test_local_variable, number=1000))
# 0.024470250005833805

这就是 Python 的工作原理。直观地说,当编译一个函数时,它内部的局部变量是已知的,但其他外部变量需要时间来检索。

这是个小问题,但我们可以在处理大量数据时利用它来优化代码。

6. 更快的执行速度: 优先使用内置模块和库

当工程师说 Python 时,默认情况下是指 CPython。因为 CPython 是 Python 语言的默认实现,也是使用最广泛的实现。

鉴于其内置的大多数模块和库都是用 C 语言编写的,而 C 语言是一种更快、更低级的语言,因此我们应该利用其内置的武器库,避免重复造轮子。

python 复制代码
import timeit
import random
from collections import Counter


def count_frequency_custom(lst):
    frequency = {}
    for item in lst:
        if item in frequency:
            frequency[item] += 1
        else:
            frequency[item] = 1
    return frequency


def count_frequency_builtin(lst):
    return Counter(lst)


large_list = [random.randint(0, 100) for _ in range(1000)]

print(timeit.timeit(lambda: count_frequency_custom(large_list), number=100))
# 0.005160166998393834
print(timeit.timeit(lambda: count_frequency_builtin(large_list), number=100))
# 0.002444291952997446

上面的程序比较了两种计算列表中元素频率的方法。我们可以看到,利用集合模块内置的 Counter 比自己编写 for 循环更快、更整洁、更好。

7. 更快的函数调用: 利用缓存装饰器轻松实现内存化

缓存是一种常用的技术,可以避免重复计算并加快程序的运行速度。

幸运的是,在大多数情况下我们不需要编写自己的缓存处理代码,因为 Python 为此提供了一个开箱即用的装饰器 - @functools.cache

例如,下面的代码将执行两个斐波那契数字生成函数,其中一个有缓存装饰器,而另一个没有:

python 复制代码
import timeit
import functools


def fibonacci(n):
    if n in (0, 1):
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)


@functools.cache
def fibonacci_cached(n):
    if n in (0, 1):
        return n
    return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)


# Test the execution time of each function
print(timeit.timeit(lambda: fibonacci(30), number=1))
# 0.09499712497927248
print(timeit.timeit(lambda: fibonacci_cached(30), number=1))
# 6.458023563027382e-06

结果证明了 functools.cache 装饰器如何使我们的代码变得更快。

基本的 fibonacci 函数效率很低,因为在获取 fibonacci(30) 结果的过程中,它会多次重新计算相同的斐波纳契数。

缓存版本则快得多,因为它缓存了之前的计算结果。因此,它只对每个斐波那契数字计算一次,随后的调用会从缓存中获取相同的参数。

仅仅添加一个内置装饰器就能带来如此大的改进,这就是 Pythonic 的意义所在。😎

8. 更快的无限循环: 优先选择 while 1 而不是 while True

要实现无限 while 循环,我们可以使用 while Truewhile 1

它们的性能差异通常可以忽略不计。不过,while 1 的速度要稍快一些,这一点还是很有趣的。

这是因为 1 是字面意思,而 True 是一个全局名称,需要在 Python 的全局作用域中查找,因此需要的开销很小。

让我们在代码片段中看看这两种方法的实际对比:

python 复制代码
import timeit


def loop_with_true():
    i = 0
    while True:
        if i >= 1000:
            break
        i += 1


def loop_with_one():
    i = 0
    while 1:
        if i >= 1000:
            break
        i += 1


print(timeit.timeit(loop_with_true, number=10000))
# 0.1733035419601947
print(timeit.timeit(loop_with_one, number=10000))
# 0.16412191605195403

我们可以看到,while 1 确实稍微快一些。

不过,现代 Python 解释器(如 CPython)已进行了高度优化,这种差异通常微不足道。所以我们不必担心这种微不足道的差异。更何况 while Truewhile 1 更可读。

9. 更快的启动:智能导入 Python 模块

在 Python 脚本的顶部导入所有模块似乎很自然。

实际上,我们不必这样做。

此外,如果一个模块太大,在需要时导入会更好。

python 复制代码
def my_function():
    import heavy_module
    # rest of the function

在上面的代码中,heavy_module 是在函数内部导入的。这是一种 "懒加载 "的概念,即在调用 my_function 时才导入。

这种方法的好处是,如果在脚本执行过程中从未调用过 my_function,那么 heavy_module 就不会被加载,从而节省了资源,缩短了脚本的启动时间。

原文链接:9 Subtle Tricks To Make Your Python Code Much Faster

相关推荐
geovindu7 小时前
python: Memento Pattern
开发语言·python·设计模式·备忘录模式
苍何7 小时前
字节发力,豆包大模型2.0 震撼来袭(附 Trae 实测)
后端
苍何7 小时前
不会剪辑的人,开始用 AI 批量出爆款了
后端
苍何7 小时前
百度 APP 正式接入 OpenClaw,所有人限时免费!
后端
寻星探路8 小时前
【JVM 终极通关指南】万字长文从底层到实战全维度深度拆解 Java 虚拟机
java·开发语言·jvm·人工智能·python·算法·ai
lbb 小魔仙8 小时前
【Java】Java 实战项目:手把手教你写一个电商订单系统
android·java·python
岱宗夫up8 小时前
FastAPI入门(上篇):快速构建高性能Python Web API
开发语言·前端·python·fastapi
Dxy12393102168 小时前
中文乱码恢复方案
开发语言·python
rongyili889 小时前
Dify 外部知识库集成 Milvus 实战指南
开发语言·python·milvus
Hello eveybody9 小时前
什么是动态规划(DP)?(Python版)
python·动态规划