测试工程师社招-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)
