背景介绍:以下是我在准备Python开发工程师面试时准备的一些常问的题目!其中几个题目是我在面试时经常被问到的,比如深浅拷贝,多线程多进程;也有一些题目是我之前从来没听过,在面试的时候第一次听,然后记下来,网上搜索答案记录的!很多问题其实也是很好的引导,大家在学习Python的时候可以由问题带着思考,能学到不少。(ps.红色字体是我一个学姐给我做的批注)
欢迎大家打印复习,点赞收藏哦!
1 深拷贝和浅拷贝的区别是什么?
浅拷贝和深拷贝的不同仅仅是对组合对象来说,所谓的组合对象就是包含了其它对象的对象,如列表,类实例。而对于数字、字符串以及其它“原子”类型,没有拷贝一说,产生的都是原对象的引用。
1、赋值:简单地拷贝对象的引用,两个对象的id相同。
2、浅拷贝:创建一个新的组合对象,这个新对象与原对象共享内存中的子对象。
3、深拷贝:创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。
Python中浅拷贝和深拷贝的区别?
copy.copy()浅拷贝:拷贝了对象,但拷贝对象的值仍然是指向原对象的值(相当于引用),修改拷贝对象的元素,则被拷贝对象的值也会被修改。
copy.deepcopy()深拷贝: 不仅拷贝了对象,同时也拷贝了对象中的元素,获得了全新的对象,与被拷贝对象完全独立;但这需要牺牲一定的时间和空间。
import copy
>>> a = [[1, 2],[5, 6], [8, 9]]
>>> b = copy.copy(a) # 浅拷贝得到b
>>> c = copy.deepcopy(a) # 深拷贝得到c
>>> print(id(a), id(b)) # a 和 b 不同
139832578518984 139832578335520
>>> for x, y in zip(a, b): # a 和 b 的子对象相同
... print(id(x), id(y))
...
139832578622816 139832578622816
139832578622672 139832578622672
139832578623104 139832578623104
>>> print(id(a), id(c)) # a 和 c 不同
139832578518984 139832578622456
>>> for x, y in zip(a, c): # a 和 c 的子对象也不同
... print(id(x), id(y))
...
139832578622816 139832578621520
139832578622672 139832578518912
139832578623104 139832578623392
深拷贝和浅拷贝的主要区别在:子对象的Id也不同,在实际中有什么应用吗?
如果把某个列表或者字典变量通过浅拷贝的方式赋值给另一个变量,如果其中一个发生变化则另一个也会变化,而深拷贝则不会。如果要求两个变量只是初始值一样,但需要二者执行不同的指令,则需要深拷贝。
2简述多线程、多进程?
对于python来讲,线程共享的是进程的资源,它本身无法调度多核(多CPU ),多线程运算适用于高输入输出的非CPU密集型运算,比如调用外部接口、加载数据等操作;多进程可以调度多核(多CPU)进行运算,单个进程中可以有多个线程,进程间相互独立,适用于CPU计算密集型运算,比如矩阵的加减乘除运算。
进程和线程的概念
线程(Thread)是进程的执行单元,独立、并发的执行流,被称为轻量级进程。
进程:
线程:
应用:
IO密集的用多线程,在用户输入,sleep时候,可以切换到其他线程执行,减少等待时间
CPU密集的用多进程,因为假如IO操作少,用多线程的话,因为线程共享一个全局解释器锁,当前运行的线程会霸占GIL,其他线程没有GIL,就不能充分利用多核CPU的优势。
python如何实现多线程?
线程是轻量级的进程,多线程允许一次执行多个线程,众所周知,python是一种多线程语言,它有一个多线程包。(GIL-全局解释器锁)确保一次执行单个线程。一个线程保存Gil并将其传递给下一个线程之前执行一些操作。这就产生了并行执行的错觉。但实际上,只是线程轮流在CPU上。当然,所有传递都会增加执行的开销。
3Python列表和元组的区别?
列表非常适合用于存储在程序运行期间可能变化的数据集。列表是可修改的,不可变的列表被称为元组。元组:需要存储一组值在程序的整个生命周期内都不变。
4. 说一下P-E-8编码规范?
PE8是一个编程规范,内容是一些关于如何让你的程序更具有可读性的建议。
缩进:每一级缩进使用4个空格
行长:每行不超过80字符。
空行:要将程序的不同部分分开,可使用空行。
5 python中有多少种运算符?
7种,算术运算符(+ -*%),关系运算符(<),赋值运算符(+=),逻辑元素符(and,or,not),位运算符(&,|,^,~,>>,<<),成员运算符(in),身份运算符(is)is 是检查两个对象是否指向同一块内存空间,而 == 是检查他们的值是否相等。可以看出,is 是比 == 更严格的检查,is 返回True表明这两个对象指向同一块内存,值也一定相同。
6 python中的三元表达式
[ on true] if [expression] else[on false]
7 python的优点和特点:
1.解释性,python是一种解释性语言,它的源代码可以直接运行,python解释器会将源代码转换成中间语言,之后再翻译成机器码再执行。2 动态特性;3面向对象;4语法简洁;5开源和丰富的社区资源;
8 什么是python装饰器?
python装饰器是python中的特有变动,可以使得修改函数变得更容易。
Python装饰器是一个函数,是Python中的特有变动,可以使修改函数变得更容易。它可以使函数在不修改本身的函数定义外,动态产生额外的功能。
9 python中什么是构造器?
生成器是实现迭代器的一种机制,它功能的实现依赖于yield表达式,除此以外它跟普通函数没有两样。
迭代器、生成器
举个简单的例子,遍历字典的过程,就是取到一个元素后能够自动取到下一个
可迭代对象:可以使用for-in遍历的对象,都是可迭代对象
在Python中如果一个对象有__iter__( )方法或__getitem__( )方法,则称这个对象是可迭代的(Iterable);其中__iter__( )方法的作用是让对象可以用for ... in循环遍历,__getitem__( )方法是让对象可以通过“实例名[index]”的方式访问实例中的元素。换句话说,两个条件只要满足一条,就可以说对象是可迭代的。显然列表List、元组Tuple、字典Dictionary、字符串String等数据类型都是可迭代的。
迭代器:迭代器是访问集合元素的一种方式。能够使用for-in进行遍历,并能使用next函数进行迭代的对象
在Python中如果一个对象有__iter__( )方法和__next__( )方法,则称这个对象是迭代器(Iterator);其中__iter__( )方法是让对象可以用for ... in循环遍历,__next__( )方法是让对象可以通过next(实例名)访问下一个元素。注意:这两个方法必须同时具备,才能称之为迭代器。列表List、元组Tuple、字典Dictionary、字符串String等数据类型虽然是可迭代的,但都不是迭代器,因为他们都没有next( )方法。
10说一下python切片的用法, 冒号前后的意思,切片里的负数代表什么意思?
L=[0,1,2,3]
L[0:3]
表示[0,1,2],从索引0
开始取,直到索引3
为止,但不包括索引3
。即索引0
,1
,2
,正好是3个元素。
Python支持L[-1]
取倒数第一个元素,那么它同样支持倒数切片;
11切片里有“,”,代表什么意思?
查了一下,是用于python处理矩阵的时候,
X[:,0]就是取矩阵X的所有行的第0列的元素,X[:,1] 就是取所有行的第1列的元素。
X[:, m:n]即取矩阵X的所有行中的的第m到n-1列数据,含左不含右。
X[0,:]就是取矩阵X的第0行的所有元素,X[1,:]取矩阵X的第一行的所有元素。
12说说python里的yield参数和迭代器?(这个我用的少,也不是很理解其工作原理)
https://pyzh.readthedocs.io/en/latest/the-python-yield-keyword-explained.html
可以使用 for
..
in
.. 语法的叫做一个迭代器:列表,字符串,文件……你经常使用它们是因为你可以如你所愿的读取其中的元素,但是你把所有的值都存储到了内存中,如果你有大量数据的话这个方式并不是你想要的。生成器是可以迭代的,但是你 只可以读取它一次 ,因为它并不把所有的值放在内存中,它是实时地生成数据。yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器。为了精通 yield ,你必须要理解:当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象。
13 什么是pickling和unpickling?
Pickle模块读入任何python对象,将它们转换成字符串,然后使用dump函数将其存储到一个文件中,这个过程叫做pickling,反之,从存储的字符串文件中提取原始python对象的过程,叫做unpickling。
14 python是如何管理内存的?
python的内存管理是由私有heap空间管理的。所有的python对象和数据结构都在一个私有heap中,程序员没有访问该heap的权限,只有解释器才能对它进行操作。为python的heap空间分配内存是由python的内存管理模块进行的,其核心API会提供一些访问该模块方法的方法供程序员使用。python有自带的垃圾回收系统,它回收并释放没有被使用的内存,让它们能够被其他程序使用。
Python是怎样管理内存的?
1)引用计数机制:Python内部使用引用计数,来保持追踪内存中的对象。
2)垃圾回收机制:当一个对象的引用计数归零时,它将被垃圾收集机制处理掉;循环垃圾回收器,确保释放循环引用对象。
3).内存池机制:
Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统:
Pymalloc机制:为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。
15 python中的lambda是什么?
常被用于代码中的单个表达式的匿名函数。
16 遍历器?
遍历一组元素,如列表这样的容器。
17 With语句?
with语句会在执行完代码块后自动关闭文件。这里无论写文件的操作成功与否,是否有异常抛出,with语句都会保证文件被关闭。
18
无论try语句中是否抛出异常,finally中的语句一定会被执行不论try中写文件的过程中是否有异常,finally中关闭文件的操作一定会执行。由于finally的这个特性,finally经常被用来做一些清理工作。
19
实际上,__init__函数并不是真正意义上的构造函数,__init__方法做的事情是在对象创建好之后初始化变量。真正创建实例的是__new__方法。
python中怎么实现工厂模式????
20 IOError、AttributeError、ImportError、IndentationError、IndexError、KeyError、SyntaxError、NameError分别代表什么异常
IOError:输入输出异常
AttributeError:试图访问一个对象没有的属性
ImportError:无法引入模块或包,基本是路径问题
IndentationError:语法错误,代码没有正确的对齐
IndexError:下标索引超出序列边界
KeyError: 试图访问你字典里不存在的键
SyntaxError: Python代码逻辑语法出错,不能执行
NameError: 使用一个还未赋予对象的变量
21 python传参数是传值还是传址?
Python中函数参数是引用传递(注意不是值传递)。对于不可变类型(数值型、字符串、元组),因变量不能修改,所以运算不会影响到变量自身;而对于可变类型(列表字典)来说,函数体运算可能会更改传入的参数变量。参数按值传递和引用传递是怎样实现的?
Python中的一切都是类,所有的变量都是一个对象的引用。引用的值是由函数确定的,因此无法被改变。但是如果一个对象是可以被修改的,你可以改动对象。
类方法和静态方法
method:通过实例调用,可以引用类内部的任何属性和方法
classmethod:无需实例化,可以调用类属性和类方法,无法取到普通的成员属性和方法
staticmethod:无需实例化,无法取到类内部的任何属性和方法, 完全独立的一个方法
Python提供哪些内置类型?
整型(int)、字符串(str)、元组(tuple)、布尔(bool)、集合(set)、列表(list)、字典(dict)
字典推导式和列表推导式是什么?
它们是可以轻松创建字典和列表的语法结构。
22、Python中的模块和包是什么?
在Python中,模块是搭建程序的一种方式。每一个Python代码文件都是一个模块,并可以引用其他的模块,比如对象和属性。
一个包含许多Python代码的文件夹是一个包。一个包可以包含模块和子文件夹。
23
GIL(全局解释器锁)
我简单理解为它就是在多线程运算中控制每次只有一个线程在实际执行,其他线程休眠,多个线程是相互等待执行的,但并不是顺序等待,而是一会执行下这个一会执行下那个的状态,直到整体执行完。所以多线程没有办法利用多核的优势,它不能同时执行多个线程。
描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。
1.python语言和GIL没有任何关系,GIL不是python语言的特性,仅仅是因为历史原因在Cpython解释器,难以移除GIL
2.GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程执行代码
3.Python使用多进程(mutilprocess)代替多线程(threading),可以利用多核的CPU资源的
参考前面说的线程与进程的区别,多进程适用于CPU计算密集型运算
4.线程释放GIL锁的情况:在IO操作等可能会引起阻塞的系统调用之前,会暂时释放GIL,但在执行完毕之后,会重新获得GIL
5.多线程比单线程爬取有所提升,在遇到IO阻塞的时候会释放GIL,达到多线程爬出的目的。
24面向对象的三大特性(封装、继承、多态)
封装:把客观事物封装成抽象的类,类可以把自己的属性和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。提高了代码的重用性,能够对代码进行有效的管理,当某个类有问题只要修改这个类即可,而其继承的子类不需要修改
多态:必须是在继承的基础上,一个类实例的相同方法在不同情形下有不同的表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
鸭子类型和闭包这两个概念我也没有深究过
鸭子类型:在程序设计中,鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由这个对象继承特定的类或实现特定的接口,而是由当前方法和属性集合决定。“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。
新式类和经典类的区别,在多继承上。经典类(python2.x)会进行深度优先继承。新式类(python3.x)默认继承object,会进行广度优先查找。
@property:可以把一个实例方法变成其同名属性,以支持.号访问获得方法返回值。property属性内部进行一系列的逻辑计算,最终将计算结果返回。
闭包:在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。在闭包内函数想要修改外部临时变量时,需要加上nonlocal xx。函数闭包
引用了自由变量的函数即是一个闭包. 这个被引用的自由变量和这个函数一同存在, 即使已经离开了创造它的环境也不例外.
一个简单的闭包函数:
def outer():
a = 10
def inner():
b = 22
nonlocal a
a = 100
print(a+b)
return inner
if __name__ == '__main__':
x = outer()
x()
装饰器:写代码需要遵循开放封闭原则,已经实现的功能代码不允许被修改,但是可以扩展。装饰器的作用就是在不改变原有函数功能的情况下,为其添加额外的功能。
装饰器的功能:1.作为引入日志 2.函数执行时间的统计 3.执行函数前预备处理 4.执行函数后清理功能 5.权限校验等场景 6.缓存
一个简单的装饰器:
# wrapper装饰器,传入函数作为变量,由内函数调用
def outer_wrapper(func):
def wrapper(*args,**kwargs): # 进行函数的装饰
# 权限校验等场景
# 验证1
# 验证2
# 验证3
pass
func()
return wrapper
@outer_wrapper
def f1():
print('f1')
使用装饰器实现一个单例:
def singleton(cls,*args,**kwargs):
instances = {}
def get_instance(*args,**kwargs):
if cls not in instances:
instances[cls] = cls(*args,**kwargs)
return instances[cls]
return get_instance
单例:是软件设计的一种模式,该模式的目的主要是确保某一个类只有一个实例存在
使用__new__方法实现一个单例:
class SingleTon(object):
_instance = {}
def __new__(cls,*args,**kwargs):
if cls not in cls._instance:
# 重写父类的__new__实现单例
cls._instance[cls] = super(SingleTon,cls).__new__(cls,*args,**kwargs)
print(cls._instance)
return cls._instance[cls]