Python面试问题

is 与 == 的区别

is 用于判断两个变量引用对象是否为同一个, == 用于判断引用变量的值是否相等。

a is b 相当于id(a)==id(b),id() 能够获取对象的内存地址。 如果a=10;b=a; 则此时a 和b 的内存地址一样的; 但当a=[1,2,3]; 另b=a[:] 时,虽然a 和b 的值一样,但内存地址不一样。

finally

无论try语句中是否抛出异常,finally中的语句一定会被执行。

list方法的计算复杂度

Operation Big-O Efficiency
index [] O(1)
index assignment O(1)
append O(1)
pop() O(1)
pop(i) O(n)
insert(i,item) O(n)
del operator O(n)
iteration O(n)
contains (in) O(n)
get slice [x:y] O(k)
del slice O(n)
set slice O(n+k)
reverse O(n)
concatenate O(k)
sort O(n log n)
multiply O(nk)

装饰器

装饰器定义:装饰器便于代码复用, 将函数作为参数传给装饰器函数, 拓展原来函数功能的一种函数。

装饰器作用:装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能(增强函数功能但是又不修改原函数, 抽离函数中与函数本身无关的功能进行复用)。

Python的装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。
这样我们可以在不改变被装饰函数的代码的情况下给被装饰函数或程序添加新的功能。
Python的装饰器广泛应用于缓存、权限校验、性能测试(比如统计一段程序的运行时间)和插入日志等应用场景。
有了装饰器,我们就可以抽离出大量与函数功能本身无关的代码,增加一个函数的重用性。

用装饰器实现程序的计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time

def time_it(func):
def inner():
start = time.time()
func()
end = time.time()
print('用时:{}秒'.format(end-start))
return inner

@time_it
def func1():
time.sleep(2)
print("Func1 is running.")

if __name__ == '__main__':
func1()

迭代器

迭代器定义:迭代器(Iterator)是访问集合内元素的一种方式,提供了一种遍历序列对象的方法。一个类(对象)只要含有__iter__、__next__两个方法,就将其称为迭代器。

迭代器作用:迭代器最核心的功能就是可以通过__next__方法的调用来返回下一个值。而这个值不是从已有的数据中读取的,而是通过程序按照一定的规则生成的。这也就意味着我们可以不再依赖一个现存的数据集合来存放数据,而是边用边生成,这样的好处就是可以节省大量的内存空间。

生成器

生成器定义:一边循环一边计算的机制,称为生成器(generator)。生成器(generator)也是一种迭代器,在每次迭代时返回一个值,直到抛出 StopIteration 异常。

生成器作用:列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。

Refs

  1. 【Python系列】为啥老问装饰器、迭代器、生成器?-腾讯云开发者社区-腾讯云 (tencent.com)

传参

不可变对象 :int,string,float,tuple – 可理解为C中,该参数为值传递
可变对象 :list,dictionary – 可理解为C中,该参数为指针传递

对于不可变对象作为函数参数,相当于C系语言的值传递;
对于可变对象作为函数参数,相当于C系语言的引用传递,但其实不完全是引用传递,会先构造一个新的引用,更像是赋值引用。

Refs

  1. (1 封私信 / 59 条消息) Python 的函数是怎么传递参数的? - 知乎 (zhihu.com)

Python数组和列表有什么区别?

Python中的数组和列表具有相同的存储数据方式。但是,数组只能包含单个数据类型元素,而列表可以包含任何数据类型元素。

为什么使用*args,**kwargs?

当我们不确定将多少个参数传递给函数,或者我们想要将存储的列表或参数元组传递给函数时,我们使用*args。当我们不知道将多少关键字参数传递给函数时使用**kwargs,或者它可以用于将字典的值作为关键字参数传递。标识符args和kwargs是一个约定。

如何在Python中删除文件?

要在Python中删除文件,您需要导入OS模块。之后,您需要使用os.remove()函数。

如何在Python中实现多线程?

Python有一个多线程库,但是用多线程来加速代码的效果并不是那么的好,

Python有一个名为Global Interpreter Lock(GIL)的结构。GIL确保每次只能执行一个“线程”。一个线程获取GIL执行相关操作,然后将GIL传递到下一个线程。

虽然看起来程序被多线程并行执行,但它们实际上只是轮流使用相同的CPU核心。

所有这些GIL传递都增加了执行的开销。这意味着多线程并不能让程序运行的更快。

全局解释锁

适当的多线程能够提高运行效率,但在python中并不如此。

GIL功能:在 CPython 解释器中执行的每一个 Python线程,都会先锁住自己,以阻止别的线程执行。

存在原因:古老单核的调度机制

因此并行的过程在python中是通过线程交替执行模拟并行的线程。

CPython 中还有另一个机制,叫做间隔式检查(check_interval),意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况,每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。

GIL不意味着线程安全:
因为间隔式检查这种抢占机制,线程有可能也会被打断

避免GIL影响:

  1. 在以IO操作为主的IO密集型应用中,相比进程操作,线程操作更加轻量级,线程之间的通讯复杂度更低,建议使用多线程。
  2. 如果是计算密集型的应用,尽量使用多进程或者协程来代替多线程。

参考:

  1. Python GIL全局解释器锁详解(深度剖析) (biancheng.net)
  2. 深入理解Python中的GIL(全局解释器锁)。 - 知乎 (zhihu.com)