机器学习面试题面经
深度学习卷积神经网络面试题面经
PyTorch面试题面经
Python面试题面经
Linux,Git面试题面经
HR面试题面经
- python2与python3几个区别:
- python2有xrange,是生成器,python3没有
- 整除问题,python2 /是除以后取整, python3 有可能返回小数,比如p2中3/2=1,p3中3/2=1.5。python3中3//2=1,3/2=1.5
- print在p3中必须要加括号
- python2中有raw_input(), python3 是input()
- 1.with的用法。上下文管理器。一般打开文件用,打开文件在进行读写时可能会出现异常状况,如果不用with自己要try,except,finally。with实现了finally中的f.close
- 2.range,xrange区别。
- python2 range返回列表,python3 range是迭代器。xrange返回生成器
- 3.迭代器与生成器,yield
- 迭代就是循环。迭代器可以被 next()函数调用并不断返回下一个值得对象成为迭代器
- 生成器:一遍循环一遍计算的机制,优点是节约内存,迭代到下次调用使用的参数是上一次保留的
- 4.map reduce zip filter
- reduce(,):对参数元素累积 ,reduce(lambda x,y:x+y , [1,2,3,4])
- map(lambda ,): 根据提供的函数做指定的映射
- filter():用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。 第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。 filter(lambda x:x%2==1 , [1,2,3,4,5,6]) 返回[1,3,5],注意python3返回的是对象,自己要对对象遍历一遍
- 5.可变对象,不可变对象
- dict,list是可变对象。允许值发生变化,如果对变量进行append,+=操作后, 只是改变了变量值,不会新建一个对象,变量引用的对象的地址不会变化
- str,int,tuple,float是 不可变对象。不允许值发生变化,若改变了变量的值,相当于新建了一个对象,对于相同值的对象,内存中只有一个对象
- 6.内存机制,垃圾回收机制
- 小整数[-5,257]共用对象,常驻内存
- 单个字符共用对象,常驻内存
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
- 字符串(含空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
- 大整数不共用内存,引用计数为0,销毁
- 数值类型和字符串类型在python中不可变,无法修改这个对象的值,每次对变量的修改实际上是创建一个新的对象
- 7.面向对象,继承
- self代表实例,不是类。类函数里必须有一个额外的参数,默认self
- 若是多继承,在类括号里写多个父类名。但要注意继承顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左到右搜索,即方法在子类中未找到,从左到右查找父类中是否包含方法。
- 若父类方法功能不能满足要求,可以再子类重写父类方法。使用super(子类名,子类对象).func()调用父类方法
- 类内属性加两个下划线开头,表示属性私有,不能再类外被直接使用或者访问
- 单下划线和双下划线:
- 单下划线:用来指定变量私有,只有类对象和子类对象能够访问,外部访问需要接口, 不能用import 导入
- 双下划线:私有成员,只有类对象自己能够访问,子类对象也无法访问。通过 对象名._类名__xxx来访问
- __foo__: python内部的名字,用来区别其他用户自定义的命名,以防止冲突。
- 将列表生成式中的[]改成()之后数据类型是否改变:是, 由列表变为生成器
- 受到内存限制,直接创建很大的列表是要占用很大内存空间:因此没必要创建完成的列表,只需要用生成器,边循环边计算
- __new__和__init__区别:
- __new__是一个静态方法,而__init__是一个实例方法
- __new__方法会返回创建的实例,而__init__什么都不返回
- 当创建实例时用__new__, 初始化实例时用__init__
- 8.多线程。GIL
- GIL线程全局锁:python为了保证线程的安全而采取的独立线程运行的限制, 也就是说 一个核只能在同一时间运行一个线程
- 对于IO密集的任务可以采用多线程操作,而对于cpu密集的任务(偏向于用cpu计算,科学计算程序和机器学习程序等),应该采用多进程,如果此时用多线程有可能因为争夺资源而变慢。
- 协程:协程是进程和线程的升级版,进程和线程都面临内核态与用户态的切换问题而耗费许多切换时间,而协程就是自己控制切换的时机,不再需要陷入系统的内核态,常见的yield就是协程思想。
- 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序, 进程是系统分配资源的最小单元,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。线程由作用不同可以分为
- 主线程
- 子线程
- 守护线程(后台线程)
- 前台线程
- 线程: cpu调度执行的最小单元,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,多个线程共享内存(数据共享,共享全局变量),从而极大提高程序运行效率
- 进程与线程的区别:1.线程必须在某个进程中执行。2.一个进程中可以包含多个线程,其中有且只有一个主线程。3.多线程共享同一个地址空间、打开的文件以及其他资源。4.多进程共享物理内存、磁盘、打印机以及其他资源。
- 协程:用户态的轻量级线程, 协程的控制完全有用户控制,协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器的上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈没有任何内核切换开销,可以不加锁的访问全局变量,所以上下文切换的很快。
- 多线程竞争:线程是非独立的,同一个进程中的多个线程是数据共享的,当各个进程访问资源时候就会出现竞争状态:数据几乎同步会被多个线程占用,造成数据混乱,也就是所谓数据不安全。可以使用锁来解决。
- 锁:是python提供的对线程控制的对象,有 互斥锁,可重入锁,死锁。
- (好处)确保某段代码(共享数据)只能由一个线程从头到尾完整的执行,能解决多线程资源竞争下的原子操作问题。
- (坏处)阻止了多线程的并发执行,包含锁的那一段代码只能以单线程模式运行,效率大大降低。
- (致命问题)死锁
- 死锁:若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿意先锁着,互相干等着,程序无法执行下去,这就是死锁。
- 几个概念
- 同步:多个程序之间执行有先后顺序,一个执行完下个才能执行
- 异步:多个程序之间没有先后顺序,可以同时执行,有时候一个任务可能在必要的时候获取另一个同时执行的任务的结果,这叫回调。
- 阻塞:卡住了调用者,调用者不往下执行,就是说调用者阻塞了
- 同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言
- 孤儿进程:父进程退出,子进程还在运行的这些子进程都是孤儿进程。孤儿进程被init进程(进程号为1)收养,并有init进程对他们完成状态收集工作
- 僵尸进城: 进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程
- 并发与并行
- 并行:同一个时刻多个任务同时在运行。并行库:multiprocessing
- 并发:不会再同一时刻同时运行,存在交替执行的情况。并发库:threading
- 程序需要执行较多的读写、请求和回复任务的需要大量的IO,并发好,也就是多线程
- cpu运算量大的程序,并行好,也就是多进程
- GIL:全局解释锁
- 其他语言cpu是多核时支持多个线程同时执行。在python中,无论是单核还是多核,同时只能由一个线程在执行。根源是GIL的存在。某个线程想要执行,必须先拿到GIL,我们可以吧GIL看做是通行证,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入cpu执行。目前python解释器有cpython、pypy、jython、ironpython。GIL只有在cpython中才有,而在pypy和jython中没有GIL。
- 每次释放GIL锁,线程进行锁竞争、切换线程、会消耗资源。这就导致打印线程执行时长,会发现耗时更长。
- 并且由于GIL的存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为啥在多核cpu上,python的多线程效率并不高的原因。
- 创建多线程:thread和threading。前者用于更底层的操作,不常用。直接用threading.Thread()
- 线程合并:join函数执行顺序是逐个执行每个线程,执行完毕后继续往下执行。主线程结束后,子线程还在运行,join函数是的主线程等到子线程结束时再退出。
- 线程同步与互斥锁:线程之间数据共享。当多个线程对某一个共享数据进行操作时,需要考虑线程安全问题。threading模块中定义了Lock类,提供了互斥锁的功能来保证多线程情况下数据的正确性。创建步骤是:创建锁、锁定、释放。
- 可重入锁(递归锁):为满足在同一线程中多次请求同一资源的需求,python提供了可重入锁(RLock).RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。知道一个线程所有的acquire都被release其他线程才能获得资源。
- 守护进程:若希望主线程执行完毕后,不管子线程是否执行完毕都随着主线程一起结束。可以使用setDaemon(bool)函数,它跟join函数是相反的。它的作用是设置子线程是否随主线程一起结束,必须在start()之前调用,默认为false。
- 创建多进程:multiprocessing,其中的Process类跟threading模块的Thread类很相似。所以直接看代码熟悉多进程
- 多进程通信:进程之间不共享数据。如果进程之间需要进行童年高新,则要用到Queue模块或者Pipi模块来实现。
- Queue是多进程安全的队列,可以实现多进程之间的数据传递。主要有put和get两个函数
- Pipe本质是进程之间的用管道数据传递,而不是数据共享,这和socket有点像。pipe()返回两个连接对象分别表示管道的两端,每端都有send()和recv()函数。如果两个进程试图在同一时间的同一端进行读取和写入,那可能会损坏管道中的数据。
- 多进程通信:进程之间不共享数据。如果进程之间需要进行童年高新,则要用到Queue模块或者Pipi模块来实现。
- 9.参数为k,*args.**kargs。分别表示 变量,可变参数列表,参数及对应值列表。值,tuple,dict。相对位置不能变
- set的底层原理(无序):哈希。通过两个函数__hash__和__eq__结合实现的。
- 当两个变量的哈希值不同时,就认为这两个变量是不同的。
- 当两个变量哈希值时一样时,调用__eq__方法,当返回值为True时认为这两个变量是同一个,应该去除一个。返回False时不去重
- dict的原理(插入顺序):哈希。
- dict的key和set的值都是必须可哈希的:(不可变对象,str,tuple等)
- dict用空间来换时间,查找快
- dict的存储顺序和元素的添加顺序有关
- 添加的数据有可能改变已有的数据顺序
- list的原理:list是可变长度数组。python中的列表是由对其他对象的引用组成的连续数组。指向这个数组的指针及其长度呗保存在一个列表头结构中。这意味着,每次添加或删除一个元素时,由引用组成的数组需要改变大小(重新分配)。幸运的是,python在创建这些数组时采用了 指数分配,所以并不是每次操作都需要改变数组的大小。。但是也因为这个原因添加或取出元素的平均复杂度较低。
- tuple,不可变
- 10.list去重。用set或者dict,这是不考虑元素顺序关系的方法。若考虑顺序关系参考: https://blog.csdn.net/qq_21997625/article/details/86352651
#1.10从序列中一处重复项且保持元素间顺序不变
def dedupe(items):
seen = set() #若序列中的值是可哈希的(不可变,整数浮点数字符串元组),那么这是可通过集合和生成器解决
for item in items:
if item not in seen:
yield item #每次返回一个不再seen中的元素,其实就相当于返回给了调用外面的list,list是有插入顺序关系的`
seen.add(item)
a = [1,2,6,4,1,2,9,4,9]
print(list(dedupe(a)))
#在不可哈希对象中去除重复项,稍作修改即可
def dedupe(items,key=None): #key的作用是指定一个函数用来将序列中元素转换为可哈希类型,这么做目的是为了检测重读对象
seen = set()
for item in items:
val = item if item is None else key(item)
if val not in seen:
yield item
seen.add(val)
a = [{'x':1,'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
print(list(dedupe(a,key=lambda d:(d['x'],d['y'])))) #d['x']相等且d['y']相等
print(list(dedupe(a,key=lambda d:d['x']))) #只按d['x']进行筛选
- 11.两个dict相加,相同key直接加,不同key保留
def merge_dict(x,y):
for k,v in x.items():
if k in y.keys():
y[k] += v
else:
y[k] = v
- 12.求斐波那契数列
a,b=0,1
while i<100:
print(b)
a,b=b,a+b
- 13.闭包: 在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。闭包具有提高代码复用性的作用
def test(number):
def test_in(number_in):
print('number_in%d'%number_in)
return number + number_in
return test_in
ret = test(20) #给test函数赋值,这个20就是给参数number
print(ret(200)) #这里的200给number_in
print(ret(100))
#返回
#number_in200
#220
#number_in100
#120
def line_cof(a,b):
def line(x):
return a*x+b
return line
line1 = line_cof(1,1) #x+1
line2 = line_cof(4,5) #4x+5
print(line1(5))
print(line2(5))
#返回值
#6
#25
- 14.装饰器:函数可以作为参数传递的语言,可以使用装饰器。 他本质上是一个python函数, 它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。概括的讲, 装饰器的作用就是为已经存在的函数或对象添加额外的功能 。
def makebold(fn):
def warp():
return "<a>"+fn()+"<a>"
return warp
def makeitalic(fn):
def warp():
return "<b>"+fn()+"<b>"
return warp
@makebold #这句话相当于makebold(test1),也就是把当前函数传过去
def test1():
return "test1"
@makeitalic
def test2():
return "test2"
@makebold
@makeitalic
def test3(): #函数和装饰器是倒着执行的,从下往上,相当于makeold(makeitalic(test3))
return "test3"
print(test1())
print(test2())
print(test3())
- 15.is与==区别。is比较对象(也就是地址),==比较值
- 16.深拷贝,浅拷贝。
- 浅拷贝.copy(),对对象的拷贝,增加引用,并没有拷贝内容
- import copy, copy.deepcopy()深拷贝,相当于增加了一块内存,把对象所有内容复制一遍
- 17.Collections。Collections.Counter() 统计元素出现次数
- 18.字符串操作。lstrip(),split(),replace(),lower(), ''.join(), capitalize(), count() ,endswith(), find(), index(), partition()
- 19.list,dict,set(元素唯一,但不保证他们之间的顺序)操作命令
- list:append在最后插入,insert(q,x)在第q个位置插入x,list1.extend(list2)将list1和list2连起来
- list:pop()删除最后一个元素,remove()指定要删除的元素,del list1[x]按照下标删除元素
- dict:dict[k]=v,插入和修改元素
- dict: del dict[k]
- dict查询: dict[k]若没有会报错, dict.get(key,p)若没有不会报错,会返回p,若有返回value
- dict1.update(dict2) 将dict2的键值对更新到dict1中
- set: s.add()添加元素,若已有则不进行任何操作,s.update() 参数可以是列表,元组,字典
- set: s.remove() 移除某元素,若元素不存在会发生错误。 s.discard(x) 若元素不存在不会发生错误
- 20.list的append和extend,一个是在最后插入,extend是将两个list连接起来
- 21.python导入包。sys.path()
- 22.排序sort的key。
- sort与sorted区别:
- 1.注意sort是用在list上的方法,sorted可以对所有可迭代对象进行排序
- 2.list的sort对已存在的列表进行操作,无返回值。sorted返回一个新的list,而不是在原来基础上进行的操作
- sorted(iterable,cmp,key,reverse),cmp两个参数,大于返回1小于返回-1等于返回0,key主要用于指定比较的元素,只有一个参数,reverse=True降序
- L = [ ( ' b ' , 2 ) , ( ' a ' , 1 ) , ( ' c ' , 3 ) , ( ' d ' , 4 ) ]
- sorted(L,cmp=lambda x,y:cmp(x[1],y[1])) 返回 [ ( ' a ' , 1 ) , ( ' b ' , 2 ) , ( ' c ' , 3 ) , ( ' d ' , 4 ) ]
- sorted ( L , key = lambda x : x [ 1 ] ) 返回 [ ( ' a ' , 1 ) , ( ' b ' , 2 ) , ( ' c ' , 3 ) , ( ' d ' , 4 ) ]
- sort与sorted区别:
- 23.zip:用于将可迭代对象作为参数,将对象中对应元素打包成一个个元组,返回有元组组成的列表。若各迭代器元素个数不一致,范湖列表长度最短对象长度。利用*操作符可将元组 解压(就是恢复)为列表。
- a,b=[1,2,3],[4,5,6,7]
- ziped = zip(a,b) 返回[(1,4),(2,5),(3,6)]
- zip(*ziped) 就是与zip相反,*ziped为解压。返回[(1,2,3),(4,5,6)]
- 24.全局变量global
- 25.实现字符串转浮点型,str2float
DIGITS={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
def str2float(s):
s=s.split('.')
if s[0]==0:
return 0+reduce(lambda x,y:x/10+y , map(lambda x:DIGITS[x],s[1][::-1]))/10
else:
return reduce(lambda x,y:x*10+y,map(lambda x:DIGITS[x],s[0]))+reduce(lambda x,y:x/10+y , map(lambda x:DIGITS[x],s[1][::-1]))/10
print(str2float('123.0456'))
print(str2float('0.0456'))
#注意上面小数点后面是逆置后元素然后进行x/10+y。最后返回各位是有值得,所以进行/10操作
- 26.字符串前r‘’表示原始字符串标识,不转义
- 27.try, except, finally。捕获异常,若try发生异常,则执行except内容,无论是否产生异常,finally都会执行
- 28.断言assert(),断言成功,程序继续执行,断言失败,程序报错
- 29.面向对象。
- 变量名以两个下划线__开头,表示私有成员,外部不能访问, 需要自定义访问函数才可以访问。一个下划线表示:虽然可以被外部访问,但不要随意访问。双下划线其实可以被外部访问使用:一个下划线_classname__属性 来访问
- 30.isinstance() 判断对象是不是某种已知类型,type()输出是何种类型。 dir()获得对象所有属性和方法
- 31.getattr(),setattr(),hasattr()直接操作对象的状态
- 32.单下滑杠与双下滑杠的区别:(避免用下划线作为变量名的开始)
- _xxx:不能用'from module import *'导入。保护成员,在模块或类外不可以使用。只能允许其本身与子类进行访问
- __xxx__ : 系统定义的名字。特殊方法
- __xxx : 类中私有变量名。只允许类本身进行访问,子类不可以访问
- 33.enumerate函数:用于将一个可遍历的数据对象(列表,元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环中
- 34.python是值传递还是引用传递:python不允许程序员选择采用传值还是传引用。python参数传递采用的是“传对象引用”的方式。这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字,字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值”来传递对象。 https://www.cnblogs.com/loleina/p/5276918.html
- python list去重且保持有序
- 先建立一个新的空列表,通过遍历原来的列表,再利用逻辑关系not in 来去重。
1 li=[1,2,3,4,5,1,2,3]
2 new_li=[]
3 for i in li:
4 if i not in new_li:
5 new_li.append(i)
6 print(new_li)
- 将列表转化为集合再转化为列表,利用集合的自动去重功能。 可以通过列表中索引(index)的方法保证去重后的顺序不变。
1 li=[1,2,3,4,5,1,2,3]
2 new_li=list(set(li))
3 new_li.sort(key=li.index)
4 print(new_li)
- python 字典dict排序:
- 按照key排序: dict ( sorted (track_id.items() , key = lambda x: x[0 ]))
- 按照value排序: dict ( sorted (track_id.items() , key = lambda x: x[ 1 ]))
- 假如是格式化数据:
#1.13通过公共键对字典列表排序,根据一个或多个字典中的值来对列表排序
#利用operator中的itemgetter函数对这类结构排序很简单
rows = [
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
from operator import itemgetter
rows_by_name = sorted(rows,key=itemgetter('fname')) #按照fname的大小对字典进行排序
rows_by_uid = sorted(rows,key=itemgetter('uid')) #按照uid 对字典进行排序
print(rows_by_name)
print(rows_by_uid)
#itemgetter()函数可以接受多个键
rows_by_lfname = sorted(rows,key=itemgetter('lname','fname')) #按照lname,fname同时排序
print(rows_by_lfname)
#也可以自定义,用lambda函数取代itemgetter()函数的功能
rows_by_name = sorted(rows,key=lambda x:x['fname']) #按照fname的值排序
print(rows_by_name)
rows_by_lfname = sorted(rows,key=lambda x:(x['lname'],x['fname']))
print(rows_by_lfname)
- python中__name__=='__main__'的所用是什么
- 当.py文件执行时,main下面的代码执行
- 当.py文件被其他文件引用时,main下面的代码不执行
- python变量的查找顺序
- python解释器在解引用一个变量时遵循所谓‘legb’原则。即,首先在local即局部作用域中查找变量声明和值,如果没有找到,在函数的closure属性中查找变量(只有闭包函数要考虑)即enclosing,如果还没有找到则在全局作用域中查找变量即global,如果还是没有找到则在built-in的变量中查找,也就是python的关键字和默认的全局函数(e.g. list tuple open print)。