python开发 面试题
一、简述列表与元组的区别 答: 元组tuple与列表List相同点 元组tuple与列表List都是序列类型的容器对象,可以存放任何类型的数据、支持切片、迭代等操作。 元组tuple与列表List区别: 1.不可变与可变:两种类型除了字面上的区别(括号与方括号)之外,最重要的一点是tuple是不可变类型,大小固定,而list是可变类型、数据可以动态变化,这种差异使得两者提供的方法、应用场景、性能上都有很大的区别。 2.同样大小的数据,tuple 占用的内存空间更少,原子性的 tuple 对象还可作为字典的键。 3.同构与异构:tuple用于存储异构(heterogeneous)数据,当做没有字段名的记录来用,比如用tuple来记录一个人的身高、体重、年龄。而列表一般用于存储同构数据(homogenous),同构数据就是具有相同意义的数据 总结:元组和列表是常用的数组类型,在使用过程中,列表擅长对可变数据的操作,一般用于同构数据,而元组主要用于异构数据,数据库操作中查询出来的记录就是由元组构成的列表结构。 二、简述核心数据类型列列表中 append() | extend() | insert()方法的功能与区别 append():在列表尾部追加元素 extend():在列表尾部迭代追加元素 insert():在列表指定索引位置插入元素 三、用尽可能多的方法描述如何规避在对字典进行key索引取值时引发 KeyError错误 方式一: dic = {'name': 'alex', 'age': 46, 'sex': 'laddyboy'} ret = dic.get('name1') #None 方式二: dic = {'name': 'alex', 'age': 46, 'sex': 'laddyboy'} for key in dic: print(key,dic[key]) 方式三: dic = {'name': 'alex', 'age': 46, 'sex': 'laddyboy'} if 'name' in dic: print(dic['name']) 四、什么是线程? 什么是进程? 什么是协程? 在Python中多线程适用于什么场景?为什么? 进程: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。 线程: 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。 协程: 协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。 应用场景: 多进程适合在CPU 密集型操作(cpu 操作指令比较多,如科学计算,位数多的浮点运算) 多线程适合在IO 密集型操作(读写数据操作较多的,比如爬虫) why? 线程是并发 ,进程是并行 进程之间互相独立, 是系统分配资源的最小单位 同一个进程中的所有线程共享资源 并行 : 同一时刻多个任务同时在运行 并发: 在同一时间间隔内 多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况 系统运行时,大部分的状况是cpu在等I/O(硬盘/内存)的读/写,这样一来线程可以来回切换 IO密集型操作使用并发更好。 cpu 密集型: 大部分时间用来做计算 逻辑判断等 cpu 动作的程序称之cpu 密集型 五、介绍一下二分查找算法 并用Python代码来实现它 二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列 例子: l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] def func(l, aim,count): mid = (len(l) - 1) // 2 if l: if aim > l[mid]: count += 1 func(l[mid + 1:], aim, count) elif aim < l[mid]: count += 1 func(l[:mid], aim,count) elif aim == l[mid]: count += 1 print("找了%d次,才找到%d"%(count,aim)) else: print('找不到') func(l,66,0)#找了5次,才找到66 六、什么是装饰器,它的应用场景是什么? 请写出通过装饰器装饰一个带参数的函数的代码 装饰器: 其实装饰器本质是闭包,在不改变原函数的调用指令情况下,给原函数增加额外的功能。它的传参,返回值都是借助内层函数inner,它之所以借助内层函数inner就是为了让被装饰函数在装饰器装饰前后,没有任何区别,看起来没有变化。 应用场景是: 1. 授权(Authorization) 2. 日志(Logging) 3,函数执行时间统计 4,执行函数前预备处理 5,执行函数后清理功能 等等。。。 def wrapper(f): def inner(*args,**kwargs): ret =f(*args,**kwargs) return ret return inner @wrapper # func = wrapper(func) = inner def func(a,b): print(a,b) sum = a + b return sum ret = func(3,4) # inner(3,4) print(ret) 七、简述什么是深拷贝? 什么是浅拷贝并说出如下代码得出的结果是什么? 直接赋值:其实就是对象的引用(别名)。 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。 x = ['lily', '20', 'hr'] y = x x[0] = 'lucy' z = list(x) x[0] = ‘lilei’ 请问变量 y 的结果和 z的结果分别是什么 y的结果:['lilei', '20', 'hr'] #赋值 z的结果:['lucy', '20', 'hr'] #浅拷贝 x = ['lily', 20, ['study', 'coding']] y = x[:] import copy z = copy.deepcopy(x) x[2][0] = 'lol' 请问此时变量 y 和 z 的值分别是什么? 为什么? y的结果:['lily', 20, ['lol', 'coding']] #why 浅拷贝 z的结果:['lily', 20, ['study', 'coding']] #why 深拷贝 八、尽可能多的写出你掌握的创建字典的方式 方式一:通过关键字dict和关键字参数创建 a = dict(name='alex',age='18') print(a) 方式二:直接赋值创建 a = {'name':'alex','age':18} print(a,type(a)) 方式三:通过二元组列表创建 li = [('name', 'alex'), ('age', 18)] dic = dict(li) print(dic,type(dic)) 方式四:通过字典推导式创建 dic = {i:2*i for i in range(3)} print(dic,type(dic)) 方式五:dict和zip结合创建 dic = dict(zip('abc', [1, 2, 3])) print(dic,type(dic)) 方式六:通过dict.fromkeys()创建,通常用来初始化字典, 设置value的默认值 dic = dict.fromkeys(range(3), 'x') print(dic,type(dic)) 九、有列表 a =[‘apple’, ‘banana’, ‘orange’] 现需要将其中各元素通过”|”拼接起来,请写出你的实现方式,并说明为什什么你要这样实现 a =['apple', 'banana', 'orange'] b = '|'.join(a) print(b) why?可以使用字符串方法join()进行拼接列表元素 十、根据类的命名空间相关知识 说出下面代码会得出什什么结果 class c1: var1 = 'apple' def __init__(self): self.var2 = 10 x = c1() y = c1() x.var1 = 100 # 此时下面两句句print会打印出什什么内容 print(x.var1, x.var2) #100,10 print(y.var1, y.var2) #apple,10 why: 类实例化对象时,会创建类的实例化对象空间。 x.var1 = 100 相当于实例对象x 新开辟了一个变量空间var1 优先查找自己的名称空间 所有打印100,10 y.var1 是 apple 是因为自己的实例空间没有找到var1变量就去类的名称空间查找 十一、请简述 __new__ 与 __init__ 的区别 1 __new__方法获得空间地址(实例对象创建第一步必须创建空间) 2 将空间地址作为第一个位置参数传给__init__ ,完成实例空间赋值属性的过程 self变量等于addr变量 存储地址空间指针 十二、 在Python中 子类如何重写父类的构造函数 class A(object): def __new__(cls, *args, **kwargs): # c了基类object的__new__方法 print("this is 构造方法") addr = super().__new__(cls) # 必须调用父类开辟空间的方法(操作底层硬件) 默认传参 cls(当前类名) print("addr", addr) return addr def __init__(self, *args, **kwargs): # 覆盖了基类object的__init__方法 print("self", self) print("this is 初始化方法") A() # this is 构造方法 # addr <__main__.A object at 0x0000026B36E65EF0> # self <__main__.A object at 0x0000026B36E65EF0> # this is 初始化方法 十三、请简述Python中的实例方法 静态方法 类方法 实例方法 定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法); 调用:只能由实例对象调用。 类方法 定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法); 调用:实例对象和类对象都可以调用。 静态方法 定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法; 调用:实例对象和类对象都可以调用。 十四、多态 封装 继承 是OOP的三大基石, 请简述封装的意义是什么? 并用Python来实现 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。 1. 将变化隔离; 2. 便于使用; 3. 提高复用性; 4. 提高安全性; class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. a = A() a.bar() #from A 十五、简述Python代码异常处理理中 raise 与 assert语句的作用, 默认情况下, try与assert只能触发内置异常如(TypeError,KeyError),现需要实现引发用户自定义异常(badString),请写出代码 raise的作用:python可以自动触发异常,raise(内置函数)的定义为显示的抛出异常,用户可以使用raise进行判断,显式的引发异常,raise执行后程序将不再向下执行。 assert的作用:在自己不确定、或者怀疑会出错的时候用断言,或者在DEBUG时候用断言 class EvaException(BaseException): def __init__(self,msg): self.msg=msg def __str__(self): return self.msg user = input("请输入用户名:") pwd = input("请输入密码:") if user == 'alex': try: raise EvaException('用户名已存在!') except EvaException as e: print(e) if len(pwd) < 6: try: raise EvaException('密码不能低于6位数!') except EvaException as e: print(e) 十六:如何实现Python多进程之间的数据共享? 使用数据库来解决现在进程之间的数据共享问题 用Manager实现数据共享 from multiprocessing import Manager,Process,Lock def work(d,lock): with lock: #不加锁而操作共享的数据,肯定会出现数据错乱 d['count']-=1 if __name__ == '__main__': lock=Lock() with Manager() as m: dic=m.dict({'count':100}) p_l=[] for i in range(100): p=Process(target=work,args=(dic,lock)) p_l.append(p) p.start() for p in p_l: p.join() print(dic) 十七、简述内置函数 map() | zip() | filter() | reduce() 的功能 zip:函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。 filter 过滤 通过你的函数,过滤一个可迭代对象 map:会根据提供的函数对指定序列做映射。 reduce() 函数会对参数序列中元素进行累积。 十九、通过列表解析与三元表达式处理列表 [1,2,3,4,5], 对其中小于3的元素加2 对大于等于3的元素加3 print([i+2 for i in [1,2,3,4,5] if i < 3] + [ i+3 for i in [1,2,3,4,5] if i >= 3]) 二十、简述Python中正则表达式的贪婪模式与非贪婪模式 贪婪与非贪婪模式影响的是被量词修饰的子表达式的匹配行为, 贪婪模式在整个表达式匹配成功的前提下,尽可能多的匹配, 而非贪婪模式在整个表达式匹配成功的前提下,尽可能少的匹配。非贪婪模式只被部分NFA引擎所支持。