Python-函数记录-技巧记录-特性记录
本文最后更新于:2021年1月18日 下午
函数
sorted
函数说明
sorted(iterable, key=None, reverse=False)
参数 | 参数说明 |
---|---|
iterable | 可迭代对象 |
key | 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。 |
reverse | 排序规则 reverse = True 降序 reverse = False 升序(默认) |
用例
1 |
|
1 |
|
zip
函数说明
1 |
|
参数 | 参数说明 |
---|---|
iterable | 可迭代对象 |
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表
用例
1 |
|
技巧
生成式
基础
案例:遍历生成新数据
1 |
|
案例:遍历计算生成新数据
1 |
|
带判断
注意:判断在执行计算之前,如果判断为False,则不进入计算流程,直接忽略
案例:遍历判断计算生成新数据
1 |
|
双生
案例:遍历两个list,计算生成新数据
1 |
|
案例:遍历两个list,进行判断计算生成新数据
1 |
|
更美观的print - pprint
print
函数能在终端输出一些东西
如果输出的内容是一个数组或元组,且其元素类型各不相同,数量又多,看着很难受。此时可以使用 pprint
函数 来取代 print
函数
pprint()
模块打印出来的数据结构更加完整,每行为一个数据结构,更加方便阅读打印输出结果
缺点在于使用需要
from pprint import pprint
更美观的字符串拼接 - format
使用format函数能让print函数看上去更美观,同时不会出现需要转换类型的问题
1 |
|
输出结果
1 |
|
更好的遍历
使用enumerate
如果你希望遍历元素,又不希望使用变量计算索引,可以试试enumerate
1 |
|
使用{}表达式
python3.6以上才有的特性
1 |
|
eval() 与 json.loads()
eval()
能计算表达式,也能将str
转为 python
对象json.loads()
能将str转为 python
对象
如果只是将写着JSON
内容的字符串转为python
对象,那么json.loads()
的转换速度比eval()
大概快十倍
要点
Python的GIL
GIL是什么
GIL全称Global Interpreter Lock
GIL并不是 Python
的特性,它是在实现Python解析器(CPython)时所引入的一个概念。
官方解释:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
个人翻译:
在CPython中,GIL是一个用来防止多个线程同时请求同一机器码的互斥锁。CPython的内存管理方式是线程不安全的,所以这个锁是很有必要设置的。
线程安全与线程不安全
- 线程安全
指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 - 线程不安全
是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
GIL为何存在
由于物理上得限制,各CPU厂商在核心频率上的比赛已经被多核所取代。
为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。为了有效解决多份缓存之间的数据同步时各厂商花费了不少心思,也不可避免的带来了一定的性能损失。
Python
当然也逃不开,为了利用多核,Python
开始支持多线程。而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。
慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,并且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?
所以简单的说GIL的存在更多的是历史原因。如果推到重来,多线程的问题依然还是要面对,但是至少会比目前GIL这种方式会更优雅。
GIL的影响
从上文的介绍和官方的定义来看,GIL无疑就是一把全局排他锁。毫无疑问全局锁的存在会对多线程的效率有不小影响。甚至就几乎等于Python是个单线程的程序。 那么读者就会说了,全局锁只要释放的勤快效率也不会差啊。只要在进行耗时的IO操作的时候,能释放GIL,这样也还是可以提升运行效率的嘛。或者说再差也不会比单线程的效率差吧。理论上是这样,而实际上呢?Python比你想的更糟。
Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降
用 multiprocessing 替代 Thread
multiprocessing
库的出现很大程度上是为了弥补thread
库因为GIL
而低效的缺陷。
它完整的复制了一套thread
所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。
当然multiprocessing也不是万能良药。
它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。
所以没救了么?
当然Python社区也在非常努力的不断改进GIL,甚至是尝试去除GIL。并在各个小版本中有了不少的进步。有兴趣的读者可以扩展阅读这个Slide 另一个改进Reworking the GIL
将切换颗粒度从基于opcode计数改成基于时间片计数
避免最近一次释放GIL锁的线程再次被立即调度
新增线程优先级功能(高优先级线程可以迫使其他线程释放所持有的GIL锁)
总结
Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。
- 因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能
- 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现
- GIL在较长一段时间内将会继续存在,但是会不断对其进行改进
参考:
不同目录import
import 说到底是根据路径导入
这个导入路径有多个,可以使用sys.path
来查看
1 |
|
添加sys以处理不同级导入
1 |
|
小整数池
为避免整数频繁申请和销毁内存空间,Python
定义了一个小整数池 [-5, 256]
这些整数对象是提前建立好的,不会被垃圾回收
以下代码请在 终端Python环境
下测试,如果你是在IDE中测试,由于 IDE
的影响,效果会有所不同
1 |
|
字符串驻留 intern
Python
解释器中使用了 intern
(字符串驻留)的技术来提高字符串效率
什么是intern机制
同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,共用。这也决定了字符串必须是不可变对象
1 |
|
其它
Python 之禅
当你尝试引入this
包
1 |
|
你会发现它会输出这样一段话
1 |
|
翻译
1 |
|
很有意义的文字,不是吗
它的源码字符串使用了凯撒加密,有兴趣可以去看
定义路径:环境/lib/this.py
try-finaly-return 执行问题
1 |
|
无论是否发生异常,finally
子句始终在离开try
语句之前执行
如果try
子句中发生了异常且未由except
子句处理(或在except
或else
子句中发生),则在执行finally
子句后重新引发该异常
当try
语句的任何其他子句通过break
,continue
或return
语句离开时,finally
子句也将“在离开的try
时候”执行
如果一个函数没有 return
,会隐式的返回 None
函数的返回只有一个,如果显式声明了 return
,那么会覆盖旧的 return
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!