9个使Python代码更快的微妙技巧。
微信搜索关注《Python学研大本营》,加入读者群,分享更多精彩
图片来自Wallhaven
在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的工作原理。直观地说,当一个函数被编译时,其中的局部变量是已知的,但其他外部变量需要时间来检索。
这可能是一个小问题,但是当处理大量数据时,我们可以利用它来优化我们的代码。
当工程师们说到Python时,默认情况下指的是CPython。因为CPython是Python语言的默认实现,也是使用最广泛的实现。
鉴于它的大部分内置模块和库都是用C语言编写的,而C语言是一种更快且更底层的语言,因此我们应该利用这些内置模块和库,避免重复劳动。
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
上面的程序比较了两种统计列表中元素频率的方法。可以看到,利用collections
模块中内置的Counter
函数比自己编写的for
循环更快、更简洁、更好。
缓存是一种常用的技术,用于避免重复计算并加快程序的运行速度。
幸运的是,在大多数情况下,我们不需要自己编写缓存处理代码,因为Python为此提供了一个开箱即用的装饰器来实现这个目的——@functools.cache
。
例如,下面的代码将执行两个生成斐波那契数的函数,一个有缓存装饰器,而另一个没有:
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)
# 测试每个函数的执行时间
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的意义所在。
while 1
"而不是"while True
"要创建一个无限的while
循环,我们可以使用while True
或while 1
。
它们的性能差异通常可以忽略不计。但是,了解while 1
稍微快一些还是很有趣的。
这源于1
是一个字面常量,而True
是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 True
比while 1
更易读了。
在Python脚本的顶部导入所有模块似乎是很自然的。
实际上,我们并不需要这样做。
而且,如果一个模块太大,在需要时导入它是一个更好的选择。
def my_function():
import heavy_module
# 函数的其余部分
以上代码中,heavy_module
是在函数内部导入的。这是一种“延迟加载”的思想,即延迟到在调用my_function
时才进行导入。
这种方法的好处是,如果在执行脚本的过程中从未调用过my_function
,那么heavy_module
就不会被加载,从而节省资源并减少脚本的启动时间。
综上所述,就是9个优化Python代码性能的实用技巧,但在实际应用时需要根据具体情况进行权衡和调整。通过综合考虑代码的性能、可读性和可维护性,进而编写出高效且易于理解的Python代码。
《Python从入门到精通(第3版)》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用Python进行程序开发应该掌握的各方面技术。全书共分27章,包括初识Python、Python语言基础、运算符与表达式、流程控制语句、列表和元组、字典和集合、字符串、Python中使用正则表达式、函数、面向对象程序设计、模块、文件及目录操作、操作数据库、使用进程和线程、网络编程、异常处理及程序调试、Pygame游戏编程、推箱子游戏、网络爬虫开发、火车票分析助手、数据可视化、京东电商销售数据分析与预测、Web编程、Flask框架、e起去旅行网站、Python自动化办公、AI图像识别工具等内容。书中所有知识都结合具体实例进行介绍,涉及的程序代码都给出了详细的注释,读者可轻松领会Python程序开发的精髓,快速提升开发技能。
《Python从入门到精通(第3版)》https://item.jd.com/14055900.html
精彩回顾
《数据科学必备的Python前端库Top-5》
《轻松上手,本地运行LlaMA 2的简易指南》
《8个Python开发者必备的PyCharm插件》
《数据科学不可或缺的10个Python库,让你事半功倍》
《掌握Python设计模式,SQL Alchemy助你打破ORM与模型类的束缚》
《Python进阶之路,2024年7个不可错过的技巧》
微信搜索关注《Python学研大本营》,加入读者群,分享更多精彩
访问【IT今日热榜】,发现每日技术热点