测试工程师社招-python面试题
基础知识
函数
函数是可重用的程序代码块,不仅可以实现代码的复用,还能实现代码的一致性。
Python执行def时,会创建一个函数对象,绑定到函数名变量上。
在函数内部改变全局变量的值,增加global关键字声明,nolocal声明外部函数的局部变量。
a = 20 def a(): b = 10 def inner(): nonlocal b #声明外部函数的局部变量 print(b) b = 30 global a #声明全局变量 a = 1000 inner() print(b) a() print(a)
Locals()打印输出的局部变量(键值对的形式)
Globals()打印输出的全局变量(键值对的形式)
局部变量的查询和访问速度比全局变量快,优先使用。
函数的参数传递本质:从实参到形参的赋值操作1、对可变对象进行写操作,直接作用于原对象本身,比如:列表、字典等,直接修改这个对象 2、对不可变对象进行写操作,产生一个新的对象空间,比如:数值、元祖、字符串等,系统会新建一个对象
深拷贝和浅拷贝:1、浅拷贝:copy.copy()不拷贝子对象的内容,只拷贝子对象的引用2、深拷贝:copy.deepcopy()子对象的内存也全部拷贝一份,对子对象的修改不会影响源对象
import copy def testCopy(): '''测试浅拷贝''' a = [10, 20, [5, 6]] b = copy.copy(a) print("a", a) print("b", b) b.append(30) b[2].append(7) print("浅拷贝......") print("a", a) print("b", b) def testDeepCopy(): '''测试深拷贝''' a = [10, 20, [5, 6]] b = copy.deepcopy(a) print("a", a) print("b", b) b.append(30) b[2].append(7) print("深拷贝......") print("a", a) print("b", b) testCopy() print("*************") testDeepCopy() a [10, 20, [5, 6]] b [10, 20, [5, 6]] 浅拷贝...... a [10, 20, [5, 6, 7]] b [10, 20, [5, 6, 7], 30] ************* a [10, 20, [5, 6]] b [10, 20, [5, 6]] 深拷贝...... a [10, 20, [5, 6]] b [10, 20, [5, 6, 7], 30]
传递不可变对象包含的子对象是可变的:修改可变对象,源对象会发生变化,a=(10,20,[5,6]) a[2][0] = 88 修改了源对象
参数的几种类型:1、位置参数,按照顺序传递形参实参一一对应,def f(a,b,c) f(1,2,3) 2、默认值参数,默认值参数必须位于普通参数的后面 def f1(a,b,c = 10,d = 20) f1(9,8,19) 3、命名参数,按照形参的名称传递参数,在调用的时候进行命名def f2(a,b,c) f2(c = 10,b = 20,a=22) 4、可变参数:*param,将多个参数收集到一个元祖对象中;**param,将多个参数收集到一个字典对象中 def f3(a,b,*c) f3(8,9,19,20) def f4(a,b,**d) f4(8,9,name=’zmx’,age=18) 5、强制命名参数:在可变参数后面增加新的参数,必须在调用的时候强制命名 def f5(*a,b,c) f5(2,3,4) f5(2,b = 3,c = 4)
lambda表达式和匿名函数:简单的定义函数的方法,lambda函数实际生成了一个函数对象
f = lambda a,b,c:a+b+c print(f) print(f(2,3,4)) #<function <lambda> at 0x01397E40> #9 g = [lambda a:a*2,lambda b:b*3,lambda c:c*4] print(g) #三个函数对象 print(g[0](6),g[1](2),g[2](3)) #使用lambda函数对字典的值排序 d = {'a':4,'b':1,'c':2,'d':3} D = sorted(d.items(),key = lambda x:x[1]) print(b)
递归函数(栈):自己调用自己,1、终止条件,递归什么时候结束2、递归步骤,n和n-1相关联
def fun(x,n): if n == 0: return 1 else: return x*fun(x,n-1) print(fun(3,4))
字典
键值对的无序可变序列,键任意不可变,值任意数据
创建:1、{}、dict()2、zip对象 dict(zip(k,v)) 3、fromkeys创建值为空的字典 dict.fromkeys()
k = {'name','age','job'} v = {'gaoqi',18,'teacher'} d = dict(zip(k,v)) print(d) a = dict.fromkeys(k) print(a)
访问:1、通过键访问 2、get,不存在返回空,或指定对象3、items()所有的键值对,keys()所有的键,values()所有的值4、len()键值对的个数 5、检测一个键是否在字典中,”name” in dict 返回true或者false
a = {'name': 'gaoqi', 'age': 18, 'job': 'programmer'} print(a['name']) print(a.get('sex','nan')) #键不存在,返回指定的对象 print(a) print(a.items()) print(a.keys()) print(a.values()) print(len(a)) #键值对的个数
字典元素添加、修改、删除:1、新增键值对,键已存在,覆盖旧的;不存在则新增2、update()将新字典所有键值对全部添加到旧字典上,key有重复则直接覆盖 3、删除
a = {'name':'gaoqi','age':18,'job':'programmer'} a['address'] = '111' b = {'a':1,'b':2} a.update(b) print(a) del(a['name']) b = a.pop('age') print(a) print(a.popitem())#随机删除和返回该键值对
序列解包用于字典时,默认对键进行操作
s = {'name':'gaoqi','age':18,'job':'teacher'} name,age,job = s name,age,job = s.items() #键值对 name,age,job = s.values() #值 print(name)
面试题:统计一个字符串中每个字符的个数
str = 'hello,world' dict = {} for i in str: if i in dict: dict[i] += 1 else: dict[i] =1 for i in dict: print(i,dict[i])
元组
不可变,不能修改元组中的元素
创建:1、() 可省略 2、tuple()
访问:1、不能修改 2、a[1] a[:4]返回的仍是元组对象 3、sorted(tuple) 元组排序,并且返回一个新的元组对象
Zip:将多个列表对应位置的元素合成元组,并返回这个zip对象
a = [10,20,30] b = [20,30,10] d = zip(a,b) print(list(d))
生成器推导式创建元组:可以通过生成器对象,转化成列表或元组,也可以使用生成器的__next__()方法进行遍历,元素访问结束之后,重新访问必须重新创建该生成器对象。T = (x*2 for x in range(5)) t.__next__() tuple(s)只能访问一次,第二次就空了需要在生成一次
集合
一个无序的不重复的元素序列,用花括号{}编写
创建(1、s = set() #空集合 2、s= {} type(s) #dict 3、s = {3,4,1,4,3} #3,4,1 可以去重)
访问(集合没有顺序没有索引 无法指定位置去访问,可以遍历)(s = {'a','b','c'} for i in s:print(i)
添加、删除 s.add() s.update()参数可以是列表、元祖、字典 s.remove('a')删除不存在的会报错 s.discard('3')元素不存在不会报错 s.clear()清空 s.pop()随机删除
集合运算:s1&s2 s1|s2 交集并集差集
面试题:
集合和列表区别1、集合无序,列表有序(访问索引)2、集合元素时唯一不重复,列表元素可以重复 3、集合中的元素不可变,而列表可变 4、集合 {} 列表[]
集合和字典区别1、集合无序不重复;字典有序可重复 2、集合只能存储不可变的对象,比如数字、str、元祖等 3、集合唯一 字典可重 4、集合不索引,字典可键
面向对象
面向对象和面向过程:1、都是解决问题的思维方式,都是代码组织的方式2、解决简单问题可以使用面向过程3、解决复杂问题:宏观上使用面向对象把握,微观处理上仍是面向过程
类:通过类定义数据类型的属性和方法,对象是类的实例,一个类创建对象时,每个对象会共享这个类的行为(类中定义的方法),但是会有自己的属性值。
class Student: def __init__(self,name,score): self.name = name #实例属性 self.score = score def say_score(self): #实例方法 print(self.name,'的分数是:',self.score) s = Student('zmx',20) #s是实例对象 s.say_score() print(dir(s)) #获得对象的所有属性和方法 print(s.__dict__) #对象的属性字典 #isinstance(对象,类型) 判断对象是不是指定的类型
__init__()方法:初始化创建好的对象,给实例属性赋值;不定义时,系统会提供一个默认的初始化方法;__new__()方法:用于创建对象,一般无需定义该方法;
Init和new区别:1、new负责对象的创建 init负责对象的初始化2、new创建对象时调用,会返回当前对象的一个实例;init创建完对象后调用,对当前对象的一些实例初始化,无返回值
实例属性:1、init方法中定义 2、本类其他实例方法self.实例对象名访问3、创建实例对象后,通过实例对象访问 obj = 类名();obj.实例属性名 = 值 #给已有属性赋值,也可以添加新的属性
实例方法:1、第一个参数self 2、调用时,不需要也不能给self传参
函数和方法的区别:1、本质,都用来完成一个功能 2、方法通过对象调用 3、方法定义时需要传递self,函数不需要
类对象:执行class语句时,会创建一个类对象
类属性:从属于类对象的属性,也称为类变量
class Student: company = 'sss' #类属性 count = 0 def __init__(self,name,score): self.name = name #实例属性 self.score = score Student.count = Student.count+1 def say_score(self): print("我的公司是:",Student.company) print(self.name,'的分数是:',self.score) s = Student('zmx',20) s.say_score()
类方法:从属于类对象的方法,装饰器@classmethod 1、装饰器2、第一个参数cls,指的是类对象本身 3、调用格式:类名.类方法名(参数列表),参数列表中不需要也不能给cls传值
class Student: company = 'sss' @classmethod def printCompany(cls): print(cls.company) Student.printCompany()
静态方法:与类、对象无关的方法,和普通函数没有区别,只不过静态方法放到了类里面,需要通过类调用1、@staticmethod 2、调用静态方法:类名.静态方法名(参数列表)3、访问实例属性和实例方法导致错误
class Student: company = 'sss' @staticmethod def add(a,b): print('{0}+{1}={2}'.format(a,b,(a+b))) return a+b Student.add(20,30)
__del__方法(析构函数)和垃圾回收机制:用于实现对象被销毁时所需的操作,比如释放对象占用的资源;python自动垃圾回收机制,当对象没有被引用时自动调用析构方法。
__call__方法和可调用对象:定义了call方法的对象成为可调用对象,该对象可以像函数一样被调用。
Python方法没有重载:如果在类中定义多个重名的方法,只有最优一个方法有效。
方法的动态性:可以动态的为类添加新的方法,或者动态的修改类的已有方法
class Person: def work(self): print("努力上班") def play_game(self): print("玩游戏") def work2(s): print("好好工作,努力上班") Person.play = play_game Person.work = work2 p = Person() p.play() p.work()
私有属性和私有方法(实现封装):1、两个下划线开头的属性是私有的private 2、类内部可以访问私有属性(方法)3、类外部不能直接访问私有属性(方法)4、类外部可以通过_类名__私有属性(方法)访问私有属性
class Employee: __company = "sss" #私有类属性 def __init__(self,name,age): self.name = name self.__age = age #私有实例属性 def say_company(self): print("我的公司是:",Employee.__company) #类内部可以直接访问私有属性 print(self.name,'年龄是:',self.__age) self.__work() def __work(self): #私有实例方法 print("工作!好好工作!") p1 = Employee("sss",22) print(p1.name) print(dir(p1)) p1.say_company() print(p1._Employee__age) #直接访问私有属性
@property装饰器:将一个方法的调用变成属性调用,处理属性的读、写操作,做法不安全
面试题:装饰器是什么(本质是一个函数,别的函数添加装饰器可以在不修改代码的基础上添加额外的功能)
class Employee: @property def salary(self): return 30000 e = Employee() print(e.salary) print(type(e.salary))
封装:隐藏对象的属性和实现细节,只对外提供必要的方法,通过私有属性私有方法实现封装
class Employee: def __init__(self,name,salary): self.name = name self.__salary = salary @property #相当于salary属性的getter方法 def salary(self): print("月薪为{0},年薪{1}".format(self.__salary,12*(self.__salary))) return self.__salary @salary.setter #相当于salary属性的setter方法 def salary(self,salary): if(0<salary<1000000): self.__salary = salary else: print("薪水录入错误") e = Employee("zmx",1000) print(e.salary) e.salary = -200
继承:定义子类时,必须在其构造函数中调用父类的构造函数
class Person: def __init__(self,name,age): self.name = name self.__age = age def say_age(self): print(self.name,'年龄是:',self.__age) class Student(Person): def __init__(self,name,age,score): self.score = score Person.__init__(self,name,age) s = Student("zzz",12,30) s.say_age() print(dir(s))
1、成员继承:子类继承了父类除构造方法之外的所有成员 2、方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为重写 3、mro()或者__mro__可以输出这个类的继承层次结构 Student.mro()
class Person: def __init__(self,name,age): self.name = name self.age = age def say_age(self): print(self.name,'的年龄是:',self.age) def say_name(self): print("我是:",self.name) class Student(Person): def __init__(self,name,age,score): self.score = score Person.__init__(self,name,age) def say_score(self): print(self.name,'的分数是:',self.score) def say_name(self): print("报告老师,我是",self.name) s = Student("张三",17,89) s.say_age() s.say_score() s.say_name()
__str__()方法,用于返回一个对于对象的描述,常用于print()方法)(把对象转换成一个字符串,一般用于print方法)
class Person: def __init__(self,name,age): self.name = name self.__age = age def __str__(self): return "名字是{0},年龄是{1}".format(self.name,self.__age) p = Person("zzz",20) print(p)
Super()获得父类定义:在子类中,如果想要获得父类的方法,可以通过super()来做。
class A: def say(self): print("A:",self) print("AAA") class B(A): def say(self): super().say() print("BBB") b = B() b.say()
多态:同一个方法调用由于对象不同可能会产生不同的行为1、多态是方法的多态,属性没有多态 2、两个必要条件,继承和方法重写
class Animal: def shout(self): print("动物叫了一声") class Dog(Animal): def shout(self): print("汪汪汪") class Cat(Animal): def shout(self): print("喵喵喵") def animalShout(a): if isinstance(a,Animal): a.shout() animalShout(Dog()) animalShout(Cat())
面试题
1、幂等
多次请求所产生的影响和一次请求执行的影响效果相同
调用远程服务:成功、失败、超时,超时是未知的,转账超时做好幂等控制,发起重试,保证转账正常进行,又不会多转一笔(提交表单快速点击可能产生了两条一样的数据,前端重复提交)
方案:超时了,先查一下对应的记录,如果成功,走成功的流程;如果失败按失败处理
方案二:下游接口支持幂等,上游如果超时,发起重试即可(唯一索引、数据库主键)
2、内存溢出和内存泄露
内存溢出:内存不够,通常运行大型软件或者游戏的时候,软件和游戏所需要的内存远远超出了主机所安装的内存,这就是内存溢出
内存泄露:程序申请内存后,无法释放申请的内存空间
3、进程和线程
进程:资源分配的最小单位,独立的执行环境,拥有自己的内存空间和资源
线程:程序执行的最小单位,一个进程最少一个先后才能可以多个线程同时执行,共享进程的资源(通信)
进程类比公司,线程类比员工
4、栈的应用(括号匹配、函数递归调用)
5、app闪退和崩溃
手机、包、内存、第三方库、兼容
包本身,内存问题(不足)、第三方库(没有容错机制)、兼容、设备故障
编程错误、内存问题(内存溢出、内存泄露)程序崩溃、资源不足、第三方库(youbug,不兼容)、网络问题、数据异常、设备兼容问题、硬件故障
编程题
1、时间转秒
import datetime #使用strptime(time,格式)将t.str解析为datetime对象 def time_to_seconds(time): t = datetime.datetime.strptime(time,"%H:%M:%S") seconds = (t.hour*3600)+(t.minute*60)+t.second return seconds print(time_to_seconds("10:35:40"))
2、秒转时间
import datetime #使用datetime下的timedelta(seconds)函数将秒转换成时间,并且格式化 def seconds_to_time(seconds): t = datetime.timedelta(seconds=seconds) time = str(t) return time print(seconds_to_time(5445))
3、两个时间差
import datetime #先转格式,在计算差,在total_seconds()转换秒 def time_diff(start_time,end_time): t1 = datetime.datetime.strptime(start_time,"%H:%M:%S") t2 = datetime.datetime.strptime(end_time,"%H:%M:%S") diff = t2-t1 seconds = diff.total_seconds() return seconds print(time_diff("01:30:45","02:15:30"))
4、统计每个字符串个数
s = 'hello world' dict = {} for i in s: if i in dict: dict[i]+=1 else: dict[i]=1 for i in dict: print(dict[i],i)
5、判断回文串
def palidrome1(s): n = len(s) for i in range(int(n/2)): if s[i] != s[n-i-1]: return False return True print(palidrome1("zmxmz")) def palidrome2(s): return s == s[::-1] print(palidrome2())
7、找一个数组中最大的数
def func(alist): n = len(alist) mid_index = alist[0] for i in range(0,n): if alist[i]<=alist[mid_index]: i+=1 else: mid_index = i return alist[mid_index] alist = [2,3,4,5,6,7,10] print(func(alist))
8、冒泡排序
def bubble_sort(a): n = len(a) for k in range(n-1): count = 0 for i in range(n - 1): if a[i] > a[i + 1]: a[i], a[i + 1] = a[i + 1], a[i] count += 1 if count == 0: #判断count的值是否等于0,如果等于0说明没有交换 break alist = [1,2,3,4,5] bubble_sort(alist) print(alist)
9、插入排序
def insert_sort(a): n = len(a) for j in range(1,n): #两部分中的第二部分,从第二个元素开始到最后一个元素 i = j while i>0: #和有序列表中每个元素进行比较(从最后一个开始) if a[i]<a[i-1]: a[i],a[i-1] = a[i-1],a[i] #如果当前元素比前一个元素小,进行交换 else: #否则已经是有序序列,不需要进行交换 break i -= 1 alist=[54, 226, 93, 17, 77, 31, 44, 55, 20] insert_sort(alist) print(alist)
10、选择排序
def select_sort(alist): n = len(alist) for i in range(n-1): #循环一次,找起始位置,最后一个不用 O(n) min_index = i for j in range(i+1,n): #利用索引 在剩余元素中找到最小的元素 O(n) if alist[j] < alist[min_index]: min_index = j if min_index!=i: #如果最小索引变了,交换元素 alist[i],alist[min_index] = alist[min_index],alist[i] alist = [7,5,3,6,44,22,99,11] select_sort(alist) print(alist)
11、快速排序
def quick_sort(alist,start,end): #递归退出的条件 if start>=end: return mid = alist[start] low = start high = end while low < high: #由右往左 alist[high]>mid 则high-1 while low < high and alist[high] >mid: high -= 1 alist[low] = alist[high] #由左往右 alist[low]<mid 则low+1 while low < high and alist[low] <mid: low += 1 alist[high] = alist[low] #将基准数放置在对应的位置 alist[low] = mid #比基准数小的即左边的数据,要重复调用quick_sort() quick_sort(alist,start,low-1) #比基准数大的即右边的数据,要重复调用quick_sort() quick_sort(alist,low+1,end) alist=[54, 226, 93, 17, 77, 31, 44, 55, 20] quick_sort(alist,0,len(alist)-1) print(alist)
12、归并排序
def merge_sort(alist): if len(alist)<=1: return alist num = len(alist)//2 left = merge_sort(alist[:num]) right = merge_sort(alist[num:]) return merge(left,right) def merge(left,right): l,r = 0,0 result = [] while l<len(left) and r < len(right): if left[l]<right[r]: result.append(left[l]) l+=1 else: result.append(right[r]) r+=1 result += left[l:] result += right[r:] return result alist = [54,26,77,17,44,55] sorted_alist = merge_sort(alist) print(sorted_alist)