在最近参加的一些技术会议上,我常常听到参会员在会中讨论技术选型时提到“Python太慢了”。然而,这种观点往往没有考虑到Python的众多优点。实际上,如果能够遵循Pythonic的编程风格,Python的运行速度可以非常快。这其中的关键在于掌握一些技术细节上的巧妙技巧。那些经验丰富的Python开发者通常掌握着许多既微妙又强大的方法来提升代码性能。这些技巧虽然看似简单,但实际上能够显著提高编程效率。接下来,我们将深入讨论九种可以改变你编写和优化Python代码方式的方法。
在Python程序中,如果需要处理大量字符串,字符串拼接的效率会成为关键因素。在Python里,主要有两种进行字符串拼接的方法:
join()
函数,将一个字符串列表合并成一个单一的字符串。+
或+=
运算符,逐一将单独的字符串添加到已有的字符串中。那么,哪一种方法的效率更高呢?为了验证这一点,我们可以定义三个不同的函数来实现相同的字符串拼接任务:
mylist = ["Yang", "Zhou", "is", "writing"]"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"
关于你对这些函数性能的初步印象,你认为哪个是执行最快的,哪个又可能是最慢的?
实际的测试结果可能会出乎你的意料:
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()
方法针对连接字符串序列进行了专门优化。它会预先计算结果字符串的总大小,然后一次性构建最终的字符串。这样,它就避免了循环中+=
操作所带来的额外开销,从而运行更快。
然而,在我们的测试中,直接连接字符串字面量的方法表现得最快。这是因为:
.join()
方法。总结来说,如果你需要拼接一系列字符串,建议选择join()
而不是+=
。而如果你的目标是直接连接固定的字符串,简单使用+
即可完成任务。
在创建列表时,有两种常见的方法:
list()
函数。[]
字面量。通过一个简单的代码测试,我们可以比较这两种方法的性能。
import timeit
print(timeit.timeit('[]', number=10 ** 7))
# 0.1368238340364769
print(timeit.timeit(list, number=10 ** 7))
# 0.2958830420393497
测试结果表明,使用list()
函数创建列表相比于直接使用[]
字面量要慢一些。这是因为[]
是一种字面量语法,而list()
则是一个函数调用。无疑,调用函数会涉及到额外的时间消耗。
基于这个原理,当我们需要创建字典时,同样建议使用{}
而不是dict()
函数。这种选择同样是出于性能考虑。
成员检查操作的效率很大程度上依赖于所使用的数据结构。
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的列表中,成员测试(即element in list
)是通过逐个遍历元素,直到找到目标元素或到达列表末尾来实现的。因此,这一操作的时间复杂度为O(n)。
而Python中的集合则是基于哈希表实现的。进行成员检查(element in set
)时,Python利用哈希机制,其平均时间复杂度是O(1)。
这说明在编程时仔细考虑使用的底层数据结构非常重要。选用合适的数据结构可以显著提升代码的执行效率。
Python支持四种类型的推导式:列表、字典、集合和生成器。这些推导式不仅提供了创建这些数据结构的更简洁的语法,而且在性能上也优于传统的for循环。这是因为这些推导式在Python的C语言底层实现中得到了优化。
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循环的速度,结果表明列表推导式具有更快的执行速度。
在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,这是Python语言的默认实现,也是应用最广泛的版本。
鉴于大多数Python的内置模块和库都是用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)
# 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风格。
要创建一个无限的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
的执行确实略微快于while True
。
然而,在现代Python解释器(例如CPython)中,由于经过了高度优化,这种差异通常非常微小,几乎可以忽略不计。因此,在实际编程中,我们不需要过分关注这种微小的差异。而且从代码可读性的角度考虑,while True
比while 1
更为直观。
虽然在Python脚本顶部导入所有需要的模块看起来是一种自然的做法,但实际上我们并不必须这样做。
特别是对于体积较大的模块,根据实际的使用需求来导入它们会是一个更好的选择。
def my_function ():
import Heavy_module
# 函数的其余部分
如上述代码所示,heavy_module
模块被导入到了函数内部。这体现了“延迟加载”的概念,即将导入操作延迟到my_function
函数被实际调用的时候。
这种做法的优点在于,如果在脚本的执行过程中my_function
函数从未被调用,那么heavy_module
模块将不会被加载。这样不仅节省了资源,还缩短了脚本的启动时间。
如果你对Python感兴趣,想要学习python,这里给大家分享一份Python全套学习资料,都是我自己学习时整理的,希望可以帮到你,一起加油!
😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
Python全套学习资料
对于从来没有接触过Python的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~
每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
我们学会了Python之后,有了技能就可以出去找工作啦!下面这些面试题是都来自阿里、腾讯、字节等一线互联网大厂,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
上述所有资料 ?? ,朋友们如果有需要的,可以扫描下方👇👇👇二维码免费领取🆓