
“Python太慢了。”这种观点在编程语言的讨论中频频出现,常常使人忽视Python的众多优点。
但事实真的如此吗?与普遍看法相反,如果你掌握了Python式的编程技巧,Python其实可以像冠军选手一样快速奔跑。
在表面之下,精通Python的开发者们掌握着一系列微妙而强大的技巧,这些技巧能显著提升他们代码的性能,远超常规水平。这些不仅仅是技巧,它们甚至改变了游戏规则。
今天,我们将揭示九种变革性的策略,这些策略可以彻底改变你对Python编程的看法。这些策略乍看之下或许很简单,但它们具有强大的效力,能以你从未想象的方式提升效率。准备好给你的Python技能加速了吗?让我们深入了解并开始优化吧!
join 或 +:更快的字符串连接如果你的程序中经常进行字符串操作,那么字符串连接可能会成为你的 Python 程序的瓶颈。
基本上,在 Python 中有两种字符串连接的方法:
join()函数将一系列字符串合并为一个+或+=符号逐一将每个字符串添加到一个中那么哪种方法更快?废话少说,下面我们使用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() 方法都要快得多。总之,如果您需要连接字符串列表,请选择 join() 而不是 +=。如果您想直接连接字符串,只需使用 + 即可。
创建列表并不困难。两种常见的方法是:
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()。
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)却并不简单,因为需要付出更多的代价。哈哈😄😄😄,开个玩笑,扯远了,权当是给您枯燥的阅读带来一点小乐趣!
回到主题,为什么成员检查用集合比列表更快呢?
element in list)是通过迭代每个元素直到找到所需元素或达到列表末尾来执行的。因此,这个操作的时间复杂度为O(n)。element in set)时,Python使用哈希机制,其时间复杂度平均为O(1)。这里的要点是在编写程序时仔细考虑底层数据结构。利用正确的数据结构可以显著加快我们的代码速度。
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 循环之间的简单速度比较。正如如结果所示,列表推导式更快。
在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 的工作原理。直观地说,当函数编译时,其中的局部变量是已知的,但其他外部变量则需要时间来检索。
这只是一个很小的改良,但有时候我们缺可以利用它来优化我们的代码,特别是在处理大数据集时。
当工程师们说 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 循环更快,更整洁,更好(但有时候自定义的性能又会比内置模块更好,尚不知道原因)。
缓存是一种常用的技术,用于避免重复计算并加快程序的运行速度。幸运的是,在大多数情况下,我们不需要编写自己的缓存处理代码,因为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 的意义所在。😎
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 更强。
通常情况下,我们都习惯在Python 脚本顶部导入所有模块。事实上,有些时候不必这样做。此外,如果模块太大,则按需导入可能会是一个更好的主意。比如,在用到模块的函数内部导入:
def target_function():
import specific_module
# rest of the function
如上面的代码所示,specific_module 在函数内部执行导入操作。这是“惰性加载”的思想,在函数调用时才导入指定模块。
这种方法的好处是,如果在脚本执行期间从未调用 target_function,则永远不会加载 specific_module,从而节省资源并减少脚本的启动时间。
好啦,关于如何让你的Python代码提速增效的秘诀就分享到这里啦!🙇🙇🙇
“如果你觉得这篇文章对你有用,请“点赞➕关注”支持我!关注我,带你解锁更多Python实用技能、数据分析知识以及更多AI工具和新玩法!
Thank you for reading!
- END -
推荐阅读👇👇👇
Python编程技巧:你经常写for循环吗?那这份快速精简多层嵌套循环的攻略你一定要收好~~
Pandas高级功能:提升您的数据处理技能。值得一看!(建议收藏~~)
模拟现实:利用Python的Faker库生成逼真的测试数据,一个超实用的Python库!
原文作者:数据派探险家;如有侵权,请联系管理员删除。