文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Python提速秘籍:九个让你的代码飞速运行的巧妙技巧!

2024-11-29 23:24

关注

但事实真的如此吗?与普遍看法相反,如果你掌握了Python式的编程技巧,Python其实可以像冠军选手一样快速奔跑。

在表面之下,精通Python的开发者们掌握着一系列微妙而强大的技巧,这些技巧能显著提升他们代码的性能,远超常规水平。这些不仅仅是技巧,它们甚至改变了游戏规则。

今天,我们将揭示九种变革性的策略,这些策略可以彻底改变你对Python编程的看法。这些策略乍看之下或许很简单,但它们具有强大的效力,能以你从未想象的方式提升效率。准备好给你的Python技能加速了吗?让我们深入了解并开始优化吧!

1.join 或 +:更快的字符串连接

如果你的程序中经常进行字符串操作,那么字符串连接可能会成为你的 Python 程序的瓶颈。

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

那么哪种方法更快?废话少说,下面我们使用3种不同的方式连接相同的字符串:

str_list = ['Facts', 'speak', 'louder', 'than', 'words!']

# 使用 + 号
def concat_plus(strings):
    result = ''
    for word in strings:
        result += word + ' '
    return result

# 使用 join() 方法
def concat_join(strings):
    return ' '.join(strings)

# 直接连接
def concat_directly():
    return 'Facts' + 'speak' + 'louder' + 'than' + 'words!'

根据您那作为男士or女士神奇的第六感(🤭😜),悄悄告诉我您认为哪个函数速度最快,哪个最慢?实际结果可能会让您感到惊讶哦😛😛😛:

import timeit

print(f'The plus symbol: {timeit.timeit(concat_plus, number=10000)}')
print(f'The join function: {timeit.timeit(concat_join, number=10000)}')
print(f'The direct concatenation: {timeit.timeit(concat_directly, number=10000)}')

图片

如上所示,对于字符串连接,join() 方法比通过循环逐个添加字符串要快。

原因很简单。一方面,在Python中,字符串是不可变数据,每个 += 操作都会伴随新字符串变量的创建和旧字符串的复制,这会额外消耗更多的计算资源。另一方面,.join() 方法专门针对连接列表字符串进行了优化。它预先计算生成字符串的大小,然后一次性为其分配存储空间。因此,它避免了循环中的 += 操作带来的开销,因此更快。

然而,在我们的测试中,最快的函数是直接连接字符串字面量。其高速度归结于:

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

2.更快的列表创建:选择“[]”而非“list()”

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

import timeit

print('The List Creation:')
print(f"[]: {timeit.timeit('[]', number=10 ** 7)}")
print(f'The list function: {timeit.timeit(list, number=10 ** 7)}')

图片

正如结果所示,直接使用 [] 比执行 list() 函数要快差不多2倍。这是因为 [] 是一种字面语法,而 list() 是一个构造函数调用。毫无疑问,调用函数需要额外的时间。相同的逻辑,在创建字典时,我们也应该利用 {} 而不是 dict()。

3.更快的成员检查:用 Set 而不用 List

成员检查操作的性能在很大程度上取决于底层数据结构,一起来看看下面这个例子:

import timeit

target_dataset = range(1000000)
search_element = 1314
target_list = list(target_dataset)
target_set = set(target_dataset)

def list_membership_test():
    return search_element in target_list

def set_membership_test():
    return search_element in target_set

print(f'The list membership test: {timeit.timeit(list_membership_test, number=1000)}')
print(f'The set membership test: {timeit.timeit(set_membership_test, number=1000)}')

图片

结果显示,在集合中进行成员检查比在列表中快得多。我还发现一个问题,那就是搜索的元素越靠前则耗时越短,如果搜索一个不存在的元素则耗时最长。上面我们搜索的目标元素是1314,如果我们搜索一个不存在的元素1314520,则明显耗时更多:

图片

因为搜索一个不存在的元素必须遍历完整个列表或集合。By the way,从这个例子可以看出要做到一生一世(1314)很容易,因为每个人生来便有,但是要做到一生一世我爱你(1314520)却并不简单,因为需要付出更多的代价。哈哈😄😄😄,开个玩笑,扯远了,权当是给您枯燥的阅读带来一点小乐趣!

回到主题,为什么成员检查用集合比列表更快呢?

这里的要点是在编写程序时仔细考虑底层数据结构。利用正确的数据结构可以显著加快我们的代码速度。

4.更快的数据生成:用推导式而不用 for 循环

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

一起看看下面这个生成1-10000的立方示例:

import timeit

def generate_cubes_for_loop():
    cubes = []
    for i in range(10000):
        cubes.append(i * i * i)
    return cubes

def generate_cubes_comprehension():
    return [i * i * i for i in range(10000)]

print(f'For loop: {timeit.timeit(generate_cubes_for_loop, number=10000)}')
print(f'Comprehension: {timeit.timeit(generate_cubes_comprehension, number=10000)}')

上述代码只是列表推导式和 for 循环之间的简单速度比较。正如如结果所示,列表推导式更快。

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

在Python中,访问局部变量比访问全局变量或对象属性要快。这里用一个简单例子来证明这一点:

import timeit

class Test:
    def __init__(self):
        self.value = 0
        
obj = Test()

def access_global_variable():
    for _ in range(1000):
        obj.value += 1
        
def access_local_variable():
    value = obj.value
    for _ in range(1000):
        value += 1
        
print(f'Access global variable: {timeit.timeit(access_global_variable, number=1000)}')
print(f'Access local variable: {timeit.timeit(access_local_variable, number=1000)}')

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

这只是一个很小的改良,但有时候我们缺可以利用它来优化我们的代码,特别是在处理大数据集时。

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

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

考虑到大多数内置模块和库都是用更快速和更底层的语言 C 编写的,我们应该尽可能利用这些内置工具并避免重复发明轮子。

import timeit
import random
from collections import Counter

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

def counter_builtin(lst):
    return Counter(lst)

target_dataset = [random.randint(0, 100) for _ in range(1000)]
print(f'Counter custom: {timeit.timeit(lambda: counter_custom(target_dataset), number=100)}')
print(f'Counter builtin: {timeit.timeit(lambda: counter_custom(target_dataset), number=100)}')

这里比较了在列表中计算元素频率的两种方法。正如我们所看到的,利用 collections 模块中的内置 Counter 比自己编写 for 循环更快,更整洁,更好(但有时候自定义的性能又会比内置模块更好,尚不知道原因)。

7.更快的函数调用:利用缓存装饰器

缓存是一种常用的技术,用于避免重复计算并加快程序的运行速度。幸运的是,在大多数情况下,我们不需要编写自己的缓存处理代码,因为Python提供了一个用于此目的的开箱即用的装饰器 — @functools.cache。

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

import timeit
from functools import cache

def fibonacci_norm(n):
    if n <= 1:
        return n
    return fibonacci_norm(n - 1) + fibonacci_norm(n - 2)

@cache
def fibonacci_cached(n):
    if n <= 1:
        return n
    return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)

print(f'fibonacci normal: {timeit.timeit(lambda: fibonacci_norm(30), number=1)}')
print(f'fibonacci cached: {timeit.timeit(lambda: fibonacci_cached(30), number=1)}')

结果显示 cache 装饰器版本比普通版本的速度快得多。

普通的斐波那契函数效率低下,因为在获取 fibonacci(30) 的结果过程中,它多次重新计算相同的斐波那契数。

缓存版本明显更快,因为它缓存了先前计算的结果。因此,每个斐波那契数它只计算一次,并且使用相同参数进行的后续调用都从缓存中获取。

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

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

要创建一个无限循环,我们可以使用 while True 或 while 1 。它们的性能差异通常是可以忽略的。但有趣的是 while 1 稍微更快。这源于 1 是字面值,而 True 是 Python 全局范围内需要查找的全局名称,因此需要微小的额外开销。

我们也通过一个简单的示例比较这两种方式的性能:

import timeit

def infinite_loop_with_true():
    result = 0
    while True:
        if result >= 10000:
            break
        result += 1
        
def infinite_loop_with_one():
    result = 0
    while 1:
        if result >= 10000:
            break
        result += 1
        
print(f'Infinite loop with true: {timeit.timeit(infinite_loop_with_true, number=10000)}')
print(f'Infinite loop with one: {timeit.timeit(infinite_loop_with_one, number=10000)}')

正如我们所看到的,while 1 确实略快。但是,现代 Python 解释器(如CPython)经过高度优化,这样的差异通常微不足道。因此,我们无需在意这种微不足道的差异。另外,从代码可读性角度来说,其实 while True 的可读性比 while 1 更强。

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

通常情况下,我们都习惯在Python 脚本顶部导入所有模块。事实上,有些时候不必这样做。此外,如果模块太大,则按需导入可能会是一个更好的主意。比如,在用到模块的函数内部导入:

def target_function():
    import specific_module
    # rest of the function

如上面的代码所示,specific_module 在函数内部执行导入操作。这是“惰性加载”的思想,在函数调用时才导入指定模块。

这种方法的好处是,如果在脚本执行期间从未调用 target_function,则永远不会加载 specific_module,从而节省资源并减少脚本的启动时间。

来源:数据派探险家内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯