Python面试综合-Python相关
Python特性
设计哲学 | Python3 版本变化 | 与其它语言对比(比如C++) |
---|---|---|
优雅 | 动态解释性脚本语言 | |
明确 | int、long | 开发维护成本低 |
简单 | byte、str | 丰富的生态圈 |
... | 函数新特性 | 跨平台 |
... |
GIL
Global Interpreter Lock:
- 出现原因
- 运行机制--一个进程同一时刻只有一个线程在运行(非并行)
- 只在Cpython解释器中存在
GIL释放时机:
- 线程开始IO操作时
- 计算任务,解释器每隔100opcode或者15ms
Python的多线程和单线程效率测试:
- Python的多线程只对IO密集型任务产生正面效果,而当其中至少有一个CPU密集型任务时,多线程效率将急剧下降
如何避免GIL消极影响:
- 多进程、协程
- 其它解释器,但会带来新的问题
Python解释器相关
内存管理
对象存储--万物皆对象:
- 整数和较小字符串
- 容器对象
垃圾回收机制
对象回收:
- 引用计数
- 分代回收:gc.get_threshold(),gc.set_threshold(700, 10,10)
循环引用解决办法:
- 手动回收:gc.collect()
- 弱引用: weakref.ref
示例面试(1)
问题:
- 什么情况下使用Python语言开发比较好?
- 你对Python怎么看?
- 为什么要用Python?
- ...
回答思路1:
- 时间线:
- 1991年,第一个稳定的Python版本;
- 2000年,Python2.7稳定版本发布;
- 2008年,Python3发布;
- 2020年,Python2不再维护;
- 空间线:
- Python设计哲学;
- Python2、3特性;
- 和其它语言(如C++)对比;
- 语言选型考虑因素;
思路串联1:
设计哲学(Python本质特性)->如何选型(正面回答,取其精华) -> 埋下一个问题引子
示例答案1:
Python自1991年Rossum编写发布出第一个版本以来,便一直遵循着其设计哲学”简单、明确、优雅“,设计哲学的根本出发点就决定了Python这门语言的编程开发的易用性和工作高效性,同样的业务需求实现,可能传统编程语言C++程序猿需要1周工期,而Python程序猿很有可能三天不到就能完成,而且潜在隐藏的问题会少很多,因为Python本身目前有非常庞大的开源第三方库,同时封装了很多复杂的语言底层操作如内存分配与释放,指针的相关操作等;
在具体的项目实践过程中,开发语言的选型需要考虑到几个方面:项目业务本身的特点适合哪种语言、公司历史的技术栈和人员技术储备倾向于哪种语言、选型的语言当前领域内的生态体系和发展前景(比如是否不在维护);如果项目非计算密集型项目,其中对于算法性能等要求不是非常非常高,相对而言是业务功能和流程逻辑较为复杂,此时Python语言就天然具备很大的优势,碰到其中少部分算法等计算性能瓶颈的地方可以用c++等语言重写,然后Python调用,这样可以兼顾Python开发低成本和C++高性能两方面的有点;
具体选用Python后,新项目推荐使用python3,2008年Python3发布后,十几年来Python3生态圈三方库已经非常完备和齐全,而官方已宣布Python2在2020年将不再维护,另外Python3本身性能也相较Python2有一定的提升;
回答思路2:
- 时间线:
- 截止2014年,哈佛、麻省理工、伯克利、卡内基等美国顶尖高校将Python作为教学语言;
- 2018年开始,Python雄踞各大年度最佳编程语言排行榜榜首(IEEE, TIOBE);
- 空间线:
- Python特性:动态、解释、脚本、跨平台...
- 解释器:cpython,jpython,ipython,jit
- Python2,3特性
思路串联2:
Python特性(解释、动态、脚本)-> 当前发展-> 解释器及性能优化 -> 埋下一个问题引子
示例答案2:
Python语言和C++, Java等其他编译型语言不一样,它是一种动态解释型脚本语言,代码在执行时会一行一行的解释成CPU能理解的机器码。同时它又是跨平台的,可以允许在windows,linux,mac等系统上,同样的程序逻辑,可能C语言需要1000行代码,java有100行,而Python实现可能只需要20行,这极大程度上提升了企业的项目开发效率。
记得之前看到一篇报道说,到2014年为止,哈佛、麻省理工、伯克利等顶尖高校都已经使用Python作为教学语言,而最近这一两年来Python也多次被各大机构评为年度最佳编程语言。对于企业项目后台开发而言,不是说一定需要使用Python开发,但至少应该是一个首选考虑项,当然Python本身从根本上来说性能是比不上C/C++这些编译型语言,但现在语言本身的性能差距在实际开发的过程中,相对业务功能合理性设计、代码编写架构质量等等,语言底层本身造成的性能损失越来越可以忽略不计;针对于一些特定的功能模块,Python当前也出现了pypy,JIT等大幅提高Python程序运行效率的相关技术,能够满足大多数功能需求业务场景。
具体选用Python后,新项目推荐使用python3,2008年Python3发布后,十几年来Python3生态圈三方库已经非常完备和齐全,而官方已宣布Python2在2020年将不再维护,另外Python3本身性能也相较Python2有一定的提升;
回答思路3:
- 空间线:
- 语言的本质
- Python行业发展(人工智能、云计算、大数据...)
- Python开源生态圈
- Python2,3特性
思路串联3:
语言进化 -> 语言本质 ->Python当前发展(应用产业及开源生态圈)-> 留下一个问题引子(此思路不说Python缺陷,留下两个下一问题引子)
示例答案3:
恩...这个问题我是这么看的,上世纪60年Unix诞生时,贝尔实验室是使用的B语言,解决了面向机器的汇编语言极其复杂问题,然后到72年,面向过程的C语言出现,再到95年Java诞生,面向对象的思想极大的提升了程序猿的开发效率。2000年第一个稳定的Python2版本,20年来其简单高效的特质大大降低了研发人员的学习和开发成本;
编程语言的本质是人与机器沟通的工具,将人类希望机器做的事翻译成机器本身能够理解的指令;语言发展进化的历史也已经表明,越符合人类自身思维逻辑及习惯的编程语言将越受到大众欢迎。目前从云端、客户端,到物联网终端,python应用无处不在,同时也是人工智能首先的编程语言(Python在在人工智能上的优势至今无人能够撼动)。Python当前也已经具备了非常完备丰富的开源三方生态圈(比如web框架tornado,sanic, 运维监控zabbix,游戏引擎firefly等等不胜枚举)。对于大多数企业新后台项目开发,个人倾向于推荐Python作为首选语言。
具体选用Python后,新项目推荐使用python3,2008年Python3发布后,十几年来Python3生态圈三方库已经非常完备和齐全,而官方已宣布Python2在2020年将不再维护,另外Python3本身性能也相较Python2有一定的提升;
进程、线程、协程、并行、并发
进程
进程:
- 资源分配和调度的基本单位
- 一个运行的程序
- 独立的内存空间
- 父子进程:fork时复制逻辑地址 物理地址=基址+逻辑地址
- copy on write
适用场景:CPU密集型任务
缺陷:
- 创建和切换代价高
- 进程间通信效率相对较低
- 资源消耗大
线程
线程:
- 系统运算调度的最小单位
- 依赖进程存在:主线程、内核线程、用户线程
- 资源共享
适用场景:CPU密集型任务
缺陷:
- 频繁线程切换代价高
- 线程安全(GIL)
如果某些资源不独享会导致线程运行错误,则该资源就由每个线程独享,而其他资源都由进程里面的所有线程共享。
线程共享资源 | 线程独享资源 |
---|---|
地址空间 | 程序计数器 |
全局变量 | 寄存器 |
打开的文件 | 栈 |
子进程 | 状态字 |
闹铃 | |
信号及信号服务程序 | |
记账信息 |
寄存器---》cache---》内存---》外存(硬盘)---》外设
协程
- 产生背景:先于线程产生,但不符合当时自顶向下的程序设计思想,不流行
- 微线程:轻量级线程,协程的调度完全由用户控制
- 运作机制:协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快
适用场景:多进程+协程
优势:
- 省去大量线程切换开销
- 单线程运行,无需加锁
并行、并发
Parallel(并行):同一时刻执行多个任务,并行库multiprocessing
Concurrency(并发):在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况,并发库threading
程序需要执行较多的读写、请求和回复任务的需要大量的 IO 操作,IO 密集型操作使用并发更好。CPU 运算量大的程序程序,使用并行会更好。
示例面试(2)
问题:Python会不会存在内存泄漏?
思路:
- 空间线
- 内存管理方式
- 波音737事件
- 引用计数
- 循环引用
思路串联:
简述内存管理的方式->自动管理分配的弊端(举例波音)->循环引用 -> 引用计数 -> 留下一个问题引子
示例答案:
编程语言中内存的管理和分配一般有两种方式,程序手动回收或者系统自动回收。前者的代表性语言如C++, 而Python使用的是系统自动回收的机制。自动化管理的方式大大提供程序猿开发效率的同时,但在一些特殊的场景下也可能出现错误处理的情况,就像前段时间的美国波音737事件,传感器错误数据引发飞机自动化系统操作强制下降,最终酿成事故。
在Python中是可能出现内存泄露的,比如当对象A中引用了对象B,同时对象B中又引用了对象A,即出现了循环引用。Python中使用的引用计数的内存管理方式,此时A和B的引用计数将永远大于等于1,也就是说解释器将无法自动回收对象A和B的内存空间,也即产生了内存泄露。随着该模块对象创建次数的逐渐增加,在操作系统负载上可以看到该Python程序内存持续上升,直到系统资源不够进程被强制中止。
问题:过往的项目中有没有出现过性能问题?
思路:
- 空间线
- 内存管理方式
- 波音737事件
- 引用计数
- 循环引用
思路串联:
直面回答有(内存泄露问题)->回归到Python内存泄露示例上
示例答案:
有,有出现过性能问题。之前我参与的一个项目中有出现过内存泄露的情况。
当时经过跟踪后发现有一个项目代码过程中有一个对象A对象B,同时对象B中又引用了对象A,即出现了循环引用。编程语言中内存的管理和分配一般有两种方式,程序手动回收或者系统自动回收。前者的代表性语言如C++, 而Python使用的是系统自动回收的机制。自动化管理的方式大大提供程序猿开发效率的同时,但在一些特殊的场景下也可能出现错误处理的情况,就像前段时间的美国波音737事件,传感器错误数据引发飞机自动化系统操作强制下降,最终酿成事故。
Python中使用的引用计数的内存管理方式,循环引用出现时对象的引用计数将永远大于等于1,也就是说解释器将无法自动回收对象A和B的内存空间,也即产生了内存泄露。
问题:什么是协程?
思路:
- 时间线
- 20世纪60年代,进程的概念随着多道操作系统诞生而引入
- 80年代,出现了能独立运行的基本单位--线程
- 协程最早产生于1963年
- 空间线
- 进程的特性及多进程应用场景
- 线程的特性及多线程应用场景
- 协程的特性及应用场景
- GIL
思路串联:
协程历史->进程特性、多进程-->线程特性、多线程-->协程特性及使用-->GIL引子
示例答案:
协程的概念最早提出于1963年,但由于其并不符合当时崇尚的的“自顶向下”的程序设计思想,未能成为当时主流编程语言的一部分;
20世纪60年代,进程的概念被引入,进程作为操作系统资源分配和调度的基本单位,多进程的方式在很长时间内大大提高了系统运行的效率,虽然中间产生了Copy-On-Write等技术的出现,但进程的频繁创建和销毁代价较大、资源的大量复制和分配耗时仍然较高,于是80年代出现了能够独立运行的单位--线程。多线程之间可以直接共享资源,同时线程之间的通信效率又远高于进程间,将任务并发的性能再次向前推进了一大步。不过多线程也有其不足的地方,虽然线程之间切换代价相较多进程小了很多,但一些场景下线程CPU时间片的大量切换其实是做了很多不必要的无用功,特别是Python中因为GIL锁的存在,其多线程很多时候并不能提高程序运行效率,于是协程的概念又开始发挥了作用,只有一个线程在执行,只有当该子程序内部发生中断或阻塞时,才会交出线程的执行权交给其他子程序,在适当的时候再返回来接着执行。这省去了线程间频繁切换的时间开销同时也解决了多线程加锁造成的相关问题;
具体的生产环境中,Python项目经常会使用多进程+协程的方式,规避GIL锁的问题,充分利用多核的同时又充分发挥协程高效的特性。