python
01. python中有哪些不可变类型和可变类型?
- 不可变类型:不能在原来的内存地址上修改数据的类型,而是指向了新的内存地址,int、float、str、tuple、frozenset
- 可变类型:可以在原来的内存地址上修改数据的类型,list、dict、set
02. Python中如何判断一个对象是否可变?
- 可以通过
id()
函数来判断一个对象是否可变。 - 如果一个对象的
id()
在修改前后没有变化,则说明该对象是不可变的;如果id()
发生了变化,则说明该对象是可变的。
03. Python中如何判断一个对象是否是可迭代对象?
- 可以使用
collections.abc.Iterable
来判断一个对象是否是可迭代对象。
04. Python中如何判断一个对象是否是可调用对象?
- 可以使用
callable()
函数来判断一个对象是否是可调用对象。
05. 栈和堆的区别是什么?
- 申请的方式不同。 栈由系统自动分配和释放内存,堆由程序员手动分配和释放内存。
- 申请的大小不同。 栈获得的内存大小是有限的,通常是几MB,而堆获得的内存大小是无限的。
- 申请的效率不同。 栈的申请和释放速度比堆快,因为栈是连续的内存空间,而堆是分散的内存空间。
- 底层不同。栈是连续的存储空间,堆是分散的存储空间。
06 堆、栈、队列之间的区别
- 堆是在程序运行时动态分配的内存空间,栈是在编译时分配的内存空间。
- 栈就是一个桶,后放进的先拿出来,堆是一个池子,后放进的可以在任意位置拿出来。栈是先进后出
- 队列只能在队头和队尾进行操作,栈只能在栈顶进行操作。先进先出,后进后出。
07.简述数组、链表、队列、堆栈的区别?
- 数组和链表是存储方式的概念,数组在连续的空间存储数据,链表在不连续的空间存储数据。
- 队列和堆栈是描述数据存取的概念,队列是先进先出,堆栈是后进先出。队列和堆栈可以用链表实现也可以用数组来实现。
08、深拷贝和浅拷贝的区别
首先深拷贝和浅拷贝都是对象的拷贝,看起来都生成了一个新的对象,但实际上两者的区别在于:他们拷贝出来对象的内存地址跟原来是否一致,也就是复制的地址还是值;
深拷贝 vs 浅拷贝:一句话总结
浅拷贝是“一层工”,只复制外壳,不复制内核;深拷贝是“克隆人”,里里外外都彻底复制一个新个体。
-
浅拷贝:只认第一层
记法:想象复印一张纸(外层),但如果纸上有一个网址(内层对象的引用),复印出来的纸上还是同一个网址,而不是把那个网站的内容也打印出来。
结果:
你在这张复印纸上写字(修改外层),不会影响原纸。
但你用笔去涂改复印纸上的网址(修改内层),所有印有这个网址的纸(原对象和浅拷贝对象)看到的都是被涂改后的结果。 -
深拷贝:全部翻新
记法:想象不仅复印了这张纸,还把纸上提到的每个网址对应的网页都全部打印出来,装订成一本全新的书。
结果:
你在这本新书的封面上写字,不会影响原书。
你在新书的某一页上做笔记,也绝对不会影响到原书对应页的内容。
什么时候用?
用浅拷贝:当对象内部没有嵌套其他可变对象(例如,只是一个简单列表 [1, 2, 3]),或者你希望内外层对象共享内部数据时。
用深拷贝:当对象结构复杂,有嵌套的可变对象(例如,列表套字典 [{‘name’: ‘Alice’}]),并且你需要一个完全独立的副本,修改时不想影响原对象时。
09. 面向对象的三个特性是什么?
- 封装:根据职责将属性和方法组织在一起,隐藏内部实现细节,只暴露必要的接口。
- 继承:实现代码的重用,想通代码不需要重复编写。
- 多态:不同子类调用相同的父类,产生不同的结果,增强代码的灵活性和可扩展性。
10.什么是闭包?
- 闭包是两个函数嵌套定义,内部函数用到了外部函数的变量,并且外部函数返回了内部函数。他们组成了一个内存空间,我们称之为闭包
11. 匿名函数、函数、闭包、对象在做实参有什么区别?
- 匿名函数:没有名字的函数,通常用于一次性使用,或者作为参数传递给其他函数。
- 函数:有名字的函数,可以被多次调用,具有明确的功能。
- 闭包:函数嵌套定义,内部函数使用了外部函数的变量,并且外部函数返回了内部函数。闭包可以访问外部函数的变量。
- 对象:能够完成最为复杂的功能,封装了数据和方法,可以通过属性和方法来访问和操作数据。
12.简述什么是进程、线程和协程?
- 进程:操作系统分配资源的基本单位,每个进程有独立的内存空间和资源。进程之间相互独立,通信需要通过IPC(进程间通信)机制。一个程序至少有一个进程。一个进程至少有一个线程。
- 线程:进程中的执行单元,线程共享进程的内存空间和资源。线程之间通信相对简单,但需要注意同步问题。一个进程可以有多个线程。是cpu调度的基本单位。
- 协程:更加轻量级的线程,协程在用户空间内调度,不需要操作系统的参与,可以在单线程内实现多任务并发。协程通过yield和resume来切换执行,适合I/O密集型任务。协程的切换开销小,效率高。一个协程可以有多个协程
13. 简述进程、线程和协程的区别?
- 进程切换需要的资源很大,效率相对低
- 线程切换需要的资源一般,效率比进程高
- 协程切换任务资源很小,三者中效率最高
多进程、多线程根据CPU核数不一样可能是并行,但是协程是在一个线程中所以是并发。
- 协程切换任务资源很小,三者中效率最高
11.简述进程、线程、协程适用于那种应用场景类型
计算密集型:用进程
IO密集型:用线程或协程
12.协程为何比线程还快
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理协程能保留上一次调用时的状态,不管是进程还是线程,每次阻塞、切换都需要陷入系统调用,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题。
13.什么是迭代器,为什么要用它?
- 可以被next()函数调用并返回下一个值的对象。迭代器是Python中用于遍历数据结构的对象。
1 | from collections import Iterator |
- 迭代是访问元素的一种方式。
- **迭代器保存的是获取数据的方式,而不是结果;**所以想用的时候可以生成,节省大量的内存,他是一个可以记住遍历位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有元素被访问完为止。
- 迭代器有两个基本方法:
__iter__()
和__next__()
。 - 字符串、列表或者元祖等可迭代对象都可以通过
iter()
函数转换为迭代器。
14.什么是生成器,为什么要用它?
- 生成器是一个特殊的迭代器,它使用
yield
关键字来返回值,而不是使用return
。每次调用next()
时,生成器会从上次停止的地方继续执行。
15.什么是装饰器,为什么要用它?
- 装饰器本质上是一个函数,这个函数的主要作用是包装另一个函数或类包装的- 目的是在不改变原函数名的情况下改变被包装对象的行为。
- 接收一个函数,内部对齐包装,然后返回一个新的函数。
- 通过高阶函数传递函数参数,新函数添加旧函数的需求,然后执行旧函数。
16.什么是线程安全?
- 线程安全:就是对于多线程同时操作是是安全的而不会发生写冲突,比如python的Queue
- 非线程安全:就是多线成同时操作时会发生写冲突,比如python的list,set,dict
18.标准库有哪些线程安全的队列?
Python Queue模块有三种队列:
- FIFO队列先进先出(线程安全)
- LifoQueue类似于堆,即先进后出(线程安全)
- ProrityQueue优先级队列,级别越低,越先出来(线程安全)
19.什么是GIL锁?
GIL是python的全局解释器锁,是不可控的,在同一个进程中,假如那么多线程同时运行,只有一个线程可以执行Python字节码,其他线程会被阻塞。GIL锁的存在是为了保证Python解释器的线程安全,但也限制了多线程的并发性能。
如果线程中遇到耗时操作(I/O密集型任务),则解释器锁会解开,使得其他线程运行,所以在多线程中,线程的运行仍然是有先后顺序的,并不是在同时运行。
20.什么时候释放GIL锁?
1.时间片耗尽(CPU时间)
2.遇到I/O操作(如文件读写、网络请求等)
3.执行任务结束
4.执行到字节码阀值
2.1 python高并发的方案
2.1.1 多进程
- 多进程是指在操作系统中同时运行多个进程,每个进程都有自己的内存空间和资源。多进程可以充分利用多核CPU的优势,提高程序的并发性能。
2.1.2 多线程
- 多线程是指在一个进程中同时运行多个线程,每个线程共享进程的内存空间和资源。多线程可以提高程序的并发性能,但需要注意线程安全问题。
2.1.3 异步编程
- 异步编程是指在一个线程中同时运行多个任务,通过事件循环和回调函数来实现任务的并发执行。异步编程可以提高程序的并发性能,特别适合I/O密集型任务。
原理: 操作系统调度线程。当某个线程遇到 I/O 操作时,操作系统会将其挂起,并切换到另一个就绪的线程继续执行。
优点:
编程模型相对简单(同步阻塞式)。
标准库支持,无需额外安装。
对于 I/O 阻塞型任务,能有效利用等待时间。
缺点:
受 GIL 限制,无法用于并行计算。
线程切换有开销。
需要处理线程安全问题(如锁)。
适用场景: I/O 密集型任务,如快速处理大量网络请求、文件操作,且并发量不是极端高的情况。
原理: 创建多个 Python 解释器进程,每个进程有自己独立的 GIL 和内存空间。真正实现了并行计算。
优点:
彻底绕过 GIL,充分利用多核 CPU,适合 CPU 密集型任务。
进程间内存隔离,更加稳定。
缺点:
创建和销毁进程的开销远大于线程。
进程间通信(IPC)比线程间通信复杂且慢(需要使用 Queue, Pipe, Manager 等)。
占用更多内存资源。
适用场景: CPU 密集型任务(如计算圆周率、视频编码、大型数据处理)。
3. 异步编程 (asyncio) - 现代首选
原理: 在单线程内通过协程(Coroutine) 和事件循环(Event Loop) 实现并发。当一个协程遇到 I/O 操作时,它会主动让出控制权,事件循环会立刻切换到其他可运行的协程,直到 I/O 操作完成。
优点:
极高的性能: 几乎没有线程/进程创建和切换的开销,可以轻松应对数万甚至数十万的并发连接(C10K problem)。
资源占用极低: 一个线程内运行成千上万个协程。
缺点:
编程范式复杂: 需要使用 async/await 语法,代码结构与非异步代码完全不同。
“传染性”: 一个函数是 async 的,调用它的函数也必须是 async 的。
所有相关库都必须是异步的: 不能在一个异步程序里同步地调用一个阻塞的库(如 requests),必须使用异步库(如 aiohttp, asyncpg)。
适用场景: 高并发 I/O 密集型应用的首选,如现代 Web 服务器、API 网关、微服务、网络爬虫等。
__END__