python基础面试题
1简述列举了解的编程语言及语言之间的区别?
编译型语言:一次性翻译(C)
优点:运行速度快
缺点:开发效率低
解释型语言:一行一行翻译(python)
优点:开发效率高
缺点:运行速度慢
2.python2和python3的区别?
python2:
1.源码不统一,源码重复
2.print不加括号
3./向下取整
4.不能有中文
5.xrange(1,3)表示1-2
6.input输出的是输入的数据类型
python3:
1.源码统一,源码不重复
2.print加括号
3./有小数
4.可以有中文
5.range表示可迭代对象
6.input输出的是字符串
3.看代码写结果
优先级 :() > not > and > or
v1 = 1 or 2
v2 = 3 and 7 or 9 and 0
print(v1)1
print(v2)7
4.比较下面值的不同
v1 = [1,2,3]
v2 = [(1),(2),(3)]
v3 = [(1,),(2,),(3,)]
[1, 2, 3]
[1, 2, 3]
[(1,), (2,), (3,)]
5.一行代码实现数值转换
a = 1
b = 2
a,b=b,a
print(a,b)
6.python中'',"",""""""区别
1. 单行注释(当行注释)"" 或''
2. 多行注释
""" 123 """
''' 123 '''
3. 被注释的内容,不会执行相当于是字符串
7.is 和==的区别?
is是判断地址是否相同
==是判断值是否相等
8.如何实现tuple和list的转化?
l=(1,2,3)
print(list(l))
9.如何将字符串name='老男孩'反转?
name='老男孩'
print(name[::-1])
10.两个set的交并差?
s={1,2,3,4,9}
s1={1,2,5,7,8}
print(s-s1)
print(s&s1)
print(s|s1)
{9, 3, 4}
{1, 2}
{1, 2, 3, 4, 5, 7, 8, 9}
11.哪些情况下y!=x-(x-y)会成立?
x={1,2}
y={2,3}
print(y!=x-(x-y))
12.python中如何拷贝一个对象?
copy
13.简述赋值拷贝深拷贝的关系?
赋值: 将多个变量名指向一个同一个内存地址就是赋值
浅拷贝: 只拷贝第一层元素的地址,只有修改拷贝的数据第一层的时候源数据不受影响, 给可变数据类型进行添加的时候源数据会受影响 = 是修改 .append是添加 可变数据类型能够修改和添加,不可变数据类型只能修改
深拷贝: 不可变数据类型内存地址共用,可变数据类型新开辟一个空间 不管嵌套多深
赋值 : 将变量和值在内存中形成映射指向关系
浅拷贝 : 只拷贝第一级里所有的元素 copy.copy
深拷贝 : 为所有层级的元素都单独开辟新空间 copy.deepcopy() (地址:原不可变数据只是暂时的指向,可变的数据独立开辟新空间)
14.pass的作用
占位
15.写结果
import copy
a = [1,2,4,5,['b','c']]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(5)
a[4].append('d')
print(b)[1,2,4,5,['b','c','d'],5]
print(c)[1,2,4,5,['b','c','d']]
print(a)[1,2,4,5,['b','c','d'],5]
16.用python实现99乘法表
for i in range(1,10):
for x in range(1,i+1):
print("{}*{}={} ".format(x,i,x*i),end='')
print()
17.斐波那契数列
i=int(input("请输入想要几个值?"))
n1=0
n2=1
count=2
if i==0:
print("请输入正确数值")
elif i==1:
print(n1)
elif i==2:
print(n1,n2)
else:
print(n1, n2, end=',')
while count<i:
nth=n1+n2
print(nth,end=',')
n1=n2
n2=nth
count =count+1
# 方法一
lst = [1,1]
for i in range(10):
lst.append(lst[-1] + lst[-2])
print(lst)
# 方法二
a,b = 0,1
for i in range(10):
print(b)
a,b = b,a+b
# 方法三
def fib(n):
if n <= 2:
return 1
# 上一个值 + 上上个值
return fib(n-1) + fib(n-2)
print(fib(6))
18.如何删除列表中重复的值?
lis=[1,2,3,4,4,4]
print(set(lis))
19.一个大小为100G的文件etl_log.txt, 要读取文件中的内容, 写出具体过程代码?
f=open('t1',mode='r',encoding='utf-8')
for i in f:
print(i.strip())
20.a = dict(zip(("a","b","c","d","e"),(1,2,3,4,5))) 请问a是什么?
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
21.lambda关键字的作用?
匿名函数
lambda 匿名函数 : 用一句话表达只有返回值的无名函数
lambda 参数 : 返回值
22.*arg
和**kwarg
作用?
*arg接受无数个值
*kwargs接受无数键值对
# *arg 普通收集参数 : 收集多余的没人要的普通实参
# **kwarg 关键字收集参数: 收集多余的没人要的关键字实参
23.如何在函数中设置一个全局变量 ?
global
24.filter、map、reduce的作用?
filter过来
map映射
reduce累计数
25.什么是匿名函数?匿名函数有什么作用?
f = lambda x:x+6#函数名叫lambda()
因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
26.Python递归的最大层数?
1000
官方说法1000 , 实际测试 994 ~ 1000
import sys
sys.setrecursionlimit(999999) # 修改递归的最大深度
27.什么是迭代器?什么是可迭代对象?
是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比 较单一的数据集。
list,tuple,str,set,dict 取值方式只能直接看
只要具有__iter__() 方法就是一个可迭代对象
28.什么是生成器?
生成器的本质就是迭代器,可以自定义迭代的逻辑
创建方式两种:
(1)生成器表达式 (推导式) (i for i in range(3))
(2)生成器函数 (含有yield关键字)
生成器和迭代器实际功能上没有什么区别。最大的区别就是生成器是自己用python语言编写的。return换成yield
29.什么是装饰器及应用场景?
1.装饰器
开发封闭原则:
1.对扩展开放
2.对修改源代码及调用方式封闭
# 装饰器的本质就是闭包
# 在不修改原有代码的前提下,额外增加新功能就是装饰器
# 应用:登录认证,property类,框架(django,flask,@app.route("/",methdos=["GET","POST"]))
30.什么是反射及应用场景?
class A:
\# name="黎明"
\# sex="男"
\# age=54
\# def __init__(self,name,sex):
\# self.name=name
\# self.sex=sex
\# def fun(self):
\# print("调用实例方法1")
\# def fun1(self):
\# print("调用实例方法2")
\# a=A("黄家驹","男")
\# 类的操作
\# print(getattr(A,"name"))#获取
\# setattr(A,"name","想名")#设置
\# print(getattr(A,"name"))
\# getattr(A,"fun")(1)**双下方法**
\# print(hasattr(A,"fun"))
\# delattr(A,"name")
\# print(A.__dict__)
使用字符串调用对象时使用
# 通过字符串去操作类对象 或者 模块中的属性方法
hasattr getattr setattr delattr
应用: 可以配合用户的操作或者输入,调用其中的成员,api接口中
31.写一个普通的装饰器。
闭包:内函数使用了外函数的局部变量,外函数把内函数返回出来的过程叫做闭包
这个内函数叫做闭包函数;
特点:如果内函数使用了外函数的局部变量,那么该变量于内函数发生绑定,延长该变量的生命周期
```
# def func(f):
# def foo(*args,**kwargs):
# print("修饰前的函数")
# ret=f(*args,**kwargs)
# print("修饰后的函数")
# return ret
# return foo
# @func
# def cen(*args,**kwargs):
# print(f"被修饰的函数{args,kwargs}")
# return "我是cen函数"
# print(cen(1,2,3,a=1))
```
32.写一个带参数的装饰器。
```
# 带参修饰器
# while True:
# def count(a):
# def func(f):
# def fool(*args,**kwargs):
# if a=="1":
# print("这是第一个装饰的函数qq")
# f(*args,**kwargs)
# if a == "2":
# print("这是第一个装饰的函数微信")
# f(*args, **kwargs)
# if a == "3":
# print("这是第一个装饰的函数虎牙")
# f(*args, **kwargs)
# if a == "4":
# print("这是第一个装饰的函数腾讯")
# f(*args, **kwargs)
# return fool
# return func
#
# msg="""
# 1.qq
# 2.微信
# 3.虎牙
# 4.腾讯
# """
# s=input(msg+"请输入选择的序号")
# @count(s)
# def cen(*args,**kwargs):
# print(f"这是被修饰函数{args}")
# # func=count(s)
# # cen=func(cen)
# cen(1)
```
33.求结果
```python
def num():
return [lambda x:i*x for i in range(4)]
print([m(2) for m in num()])
[6, 6, 6, 6]
def出现的位置是函数的定义处
函数() 出现的位置是函数的调用处
(1)调用的时候,才会把函数中的代码,从上到下执行一遍,否则不执行
(2)里面的func是一个闭包函数,延长了当前变量i的生命周期,最后一次i的值3,所以再去调用时候拿的3
```
34.def(a, b=[])这种写法有什么陷阱?
```
函数传参为列表陷阱,列表是可变数据类型,可能会在过程中修改里面的值
b身上的默认值是列表,如果使用原来默认的参数,调用func函数
会把几次调用的值都存放在同一个默认列表里
"""
默认参数:
如果调用时,用户给实参了,那么使用用户的
如果调用时,用户没给实参,那么使用默认的(早已存在内存中的这个列表)
默认值会提前在内存中驻留,在使用时,才能调取,在定义函数的时候就提前开辟了空间
```
35.看代码写结果
```python
def func(a,b=[]):
b.append(a)
return b
v1 = func(1)
v2 = func(2,[10,20])
v3 = func(3)
print(v1,v2,v3)
[1, 3] [10, 20, 2] [1, 3]
```
36.看代码写结果
```python
def func(a,b=[]):
b.append(a)
return b
v1 = func(1)
print(v1)1
v2 = func(2,[10,20])
print(v2)[10, 20, 2]
v3 = func(3)1,3
print(v3)
```
37.请编写一个函数实现将IP地址转换成一个整数。
def ipfunc(ip):
a = ip.split('.')
s = ''
l = []
for i in a:
i = bin(int(i))[2:]
i = i.rjust(8, '0')
l.append(i)
s = ' '.join(l)
return s
print(ipfunc('10.3.9.12'))
def text(b):
list_str=b.split(" ")
new_str=[]
for i in list_str:
new_str.append(str(int(i,2)))
return ".".join(new_str)
print(text("00001010 00000011 00001001 00001100")) # 10.3.9.12
python
如 10.3.9.12 转换规则为:
10 00001010
3 00000011
9 00001001
12 00001100
再将以上二进制拼接起来计算十进制结果:00001010 00000011 00001001 00001100 = ?
# ljust 原字符串居左,填充符号
# rjust 原字符串居右,填充符号
# 方法一
ip = "10.3.9.12"
strvar = ""
for i in ip.split("."):
bin_str = str(bin(int(i)))[2:]
# 总长度是8 原字符串居右
strvar += bin_str.rjust(8,"0")
print(strvar)
# 把二进制字符串转换成十进制,默认转换时,是十进制
print(int(strvar,2))
# 方法二
ip = "10.3.9.12"
strvar = ""
for i in ip.split("."):
# format 将整型转化成二进制,不够8位的拿0补位
strvar += format(int(i) , "08b")
print(int(strvar,2))
38.请查找一个目录下的所有文件(可能存在文件嵌套)。
# 方法一 (递归写法)
import os
def getallsize(pathvar):
size = 0
lst = os.listdir(pathvar)
print(lst)
for i in lst:
pathvar2 = os.path.join(pathvar,i)
print(pathvar2)
# 判断是否是文件
if os.path.isfile(pathvar2):
size += os.path.getsize(pathvar2)
# 判断是否是文件夹
elif os.path.isdir(pathvar2):
size += getallsize(pathvar2)
print(size)
return size
# "E:\串讲基础\day2\test\1.txt"
pathvar = r"E:\串讲基础\day2\test"
res = getallsize(pathvar)
# print(res)
# 方法二
import os
# os.walk() => 生成器
pathvar = r"E:\串讲基础\day2\test"
gen = os.walk(pathvar)
for root,dirs,files in gen:
for name in files:
pathvar = os.path.join(root,name)
print(pathvar)
39.求结果
```
import math
print (math.floor(5.5))
返回小于等于5.5的最大整数
```
40.是否使用过functools中的函数?其作用是什么?
functools
functools 是python2.5被引人的,一些工具函数放在此包里。
python2.7中
python3.5中
import functools
dir(functools)
运行结果:
['MappingProxyType',
'RLock',
'WRAPPER_ASSIGNMENTS',
'WRAPPER_UPDATES',
'WeakKeyDictionary',
'_CacheInfo',
'_HashedSeq',
'__all__',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'_c3_merge',
'_c3_mro',
'_compose_mro',
'_convert',
'_find_impl',
'_ge_from_gt',
'_ge_from_le',
'_ge_from_lt',
'_gt_from_ge',
'_gt_from_le',
'_gt_from_lt',
'_le_from_ge',
'_le_from_gt',
'_le_from_lt',
'_lru_cache_wrapper',
'_lt_from_ge',
'_lt_from_gt',
'_lt_from_le',
'_make_key',
'cmp_to_key',
'get_cache_token',
'lru_cache',
'namedtuple',
'partial',
'partialmethod',
'reduce',
'singledispatch',
'total_ordering',
'update_wrapper',
'wraps']
python3中增加了更多工具函数,做业务开发时大多情况下用不到,此处介绍使用频率较高的2个函数。
partial函数(偏函数)
把一个函数的某些参数设置默认值,返回一个新的函数,调用这个新函数会更简单。
import functools
def showarg(*args, **kw):
print(args)
print(kw)
p1=functools.partial(showarg, 1,2,3)
p1()
p1(4,5,6)
p1(a='python', b='itcast')
p2=functools.partial(showarg, a=3,b='linux')
p2()
p2(1,2)
p2(a='python', b='itcast')
wraps函数
使用装饰器时,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。
添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响,例如:
def note(func):
"note function"
def wrapper():
"wrapper function"
print('note something')
return func()
return wrapper
@note
def test():
"test function"
print('I am test')
test()
print(test.__doc__)
运行结果
note something
I am test
wrapper function
所以,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。例如:
import functools
def note(func):
"note function"
@functools.wraps(func)
def wrapper():
"wrapper function"
print('note something')
return func()
return wrapper
@note
def test():
"test function"
print('I am test')
test()
print(test.__doc__)
运行结果
note something
I am test
test function
41.re的match和search区别?
search找3开头是任意位置开始,如果没有报错。
match只找第一个是不是3是就输出不是就报错如果没有group函数的话不报错输出None
42.用Python匹配HTML tag的时候,<.>和<.?>有什么区别?
第一种写法是,尽可能多的匹配,就是匹配到的字符串尽量长,第二中写法是尽可能少的匹配,就是匹配到的字符串尽量短。
比如`<tag>tag>tag>end`,第一个会匹配`<tag>tag>tag>`,第二个会匹配`<tag>`,如果要匹配到二个 `>`,就只能自己写了
43.如何生成一个随机数?
random
44.super的作用?
super 按照mro进行查找
用来解决多继承之间复杂的调用关系使用super
在多继承中,如果出现了多个同名方法
super在调用的时候,会按照mro列表的继承顺序依次调用
类.mro() = > lst
45.双下划线和单下划线的区别?
“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;”双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据
46.@staticmethod和@classmethod的区别?
静态方法和类方法
- @staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
- @classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。
而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。
class A(object):
bar = 1
def foo(self):
print 'foo'
@staticmethod
def static_foo():
print 'static_foo'
print A.bar
@classmethod
def class_foo(cls):
print 'class_foo'
print cls.bar
cls().foo()
###执行
A.static_foo()
A.class_foo()
47.实现一个单例模式(加锁)。
```
import threading
import time
class Singleton:
instance = None
lock = threading.RLock()
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if cls.instance:
return cls.instance
with cls.lock:
if cls.instance:
return cls.instance
time.sleep(0.1)
cls.instance = object.__new__(cls)
return cls.instance
def task():
obj = Singleton('x')
print(obj)
for i in range(10):
t = threading.Thread(target=task)
t.start()
# 执行1000行代码
data = Singleton('asdfasdf')
print(data)
```
48.栈和队列的区别?
栈:先进后出
队列:先进先出
49.以下代码输出是什么? 请给出答案并解释。
```python
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
111
print Parent.x, Child1.x, Child2.x
Child1.x = 2
121
print Parent.x, Child1.x, Child2.x
Parent.x = 3
323
print Parent.x, Child1.x, Child2.x
```
50.参考下面代码片段
```python
class Context:
pass
with Content() as ctx:
ctx.do_something()
请在Context类下添加代码完成该类的实现
class Context():
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 相当于在最后,执行了文件的关闭操作,fp.close()
print("abc123")
def do_something(self):
print(1111)
with Context() as ctx:
ctx.do_something()
print(ctx)
# 自动实现了关闭操作
# with open("文件") as fp:
# res = fp.read()
```
第二部分 可选题
- 如何获取列表中第二大的值?
sorted排序选出第二大的值
- 简述Python内存管理机制。
python采用"引用计数"和"垃圾回收"两种机制来管理内存。
引用计数通过记录对象被引用的次数来管理对象。
对对象的引用都会使得引用计数加1,移除对对象的引用,引用计数则会减1,
当引用计数减为0时,对象所占的内存就会被释放掉。
引用计数可以高效的管理对象的分配和释放,但是有一个缺点,就是无法释放引用循环的对象
这个时候就需要垃圾回收机制(garbage collection),来回收循环应用的对象。
垃圾回收机制会根据内存的分配和释放情况的而被调用,
- 简述Python的垃圾回收机制。
垃圾回收
Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。通过分代回收以空间换取时间进一步提高垃圾回收的效率。
引用计数
原理:当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1,当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。
优点:引用计数有一个很大的优点,即实时性,任何内存,一旦没有指向它的引用,就会被立即回收,而其他的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。
缺点:但是它也有弱点,引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的,这显然比其它那些垃圾收集技术所带来的额外操作只是与待回收的内存数量有关的效率要低。同时,引用技术还存在另外一个很大的问题-循环引用,因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用的内存始终都不会被释放掉。如下:
a = []
b = []
a.append(b)
b.append(a)
print a
[[[…]]]
print b
[[[…]]]
标记-清除
标记-清除只关注那些可能会产生循环引用的对象,显然,像是PyIntObject、PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它对象的引用。Python中的循环引用总是发生在container对象之间,也就是能够在内部持有其它对象的对象,比如list、dict、class等等。这也使得该方法带来的开销只依赖于container对象的的数量???
原理:1. 寻找跟对象(root object)的集合作为垃圾检测动作的起点,跟对象也就是一些全局引用和函数栈中的引用,这些引用所指向的对象是不可被删除的;2. 从root object集合出发,沿着root object集合中的每一个引用,如果能够到达某个对象,则说明这个对象是可达的,那么就不会被删除,这个过程就是垃圾检测阶段;3. 当检测阶段结束以后,所有的对象就分成可达和不可达两部分,所有的可达对象都进行保留,其它的不可达对象所占用的内存将会被回收,这就是垃圾回收阶段。(底层采用的是链表将这些集合的对象连接在一起)
缺点:标记和清除的过程效率不高。
分代回收
原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,Python默认定义了三代对象集合,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。
- 请用两个队列来实现一个栈。
"""
栈 : 先进后出,后进先出
队列 : 先进先出,后进后出
"""
from queue import Queue
class Stack():
def __init__(self):
self.master_queue = Queue()
self.minor_queue = Queue()
def push(self,val):
# 入栈
self.master_queue.put(val)
def pop(self):
# 出栈
# 如果队列中没有任何值,直接返回None
if self.master_queue.qsize() == 0 :
return None
while True:
# 当队列总长度为1的时候,循环终止,把最后一个元素拿出来,为了满足栈后进先出的特点
if self.master_queue.qsize() == 1:
value = self.master_queue.get()
break
# 剩下还没有拿出来的元素,暂时放在2号队列中存储
self.minor_queue.put(self.master_queue.get())
"""
minor_queue(1)
master_queue(2 3 4)
minor_queue(2)
master_queue(3 4)
minor_queue(3)
master_queue(4)
"""
# 交换队列,重新循环,继续去最后一个值,如法炮制
self.master_queue,self.minor_queue = self.minor_queue,self.master_queue
return value
obj = Stack()
obj.push("a")
obj.push("b")
obj.push("c")
print(obj.pop()) # c
print(obj.pop()) # b
print(obj.pop()) # a
print(obj.pop()) # a
[a,b,c]
[a,b]
[a]
[]
- 请用Python实现一个链表。
# ### (1) 创建链表
class Node():
def __init__(self, value, next):
self.value = value
self.next = next
head = Node("头", None)
last = head
for i in range(5): # v0 v1 v2 v3 v4
node = Node("v%s" % i, None)
last.next = node
last = node
# 查看链表的关系
print(head.value)
print(head.next.value)
print(head.next.next.value)
print(head.next.next.next.value)
print(head.next.next.next.next.value)
print(head.next.next.next.next.next.value)
# print(head.next)
print("<========>")
- 请用Python实现链表的逆转
def reverse_link_list(head):
# 要是空的,或者None,直接返回head
if not head or not head.next:
return head
# 获取上一个节点对象
prev_node = None
# 获取下一个节点对象
next_node = head.next
# 获取当前节点对象
current_node = head
while True:
# 修改next,所指向的对象
current_node.next = prev_node
# 如果下一个阶段对象是None
if not next_node: # not None
break
# 重新获取上一个对象,即把当前丢向单独存一份,以准备第二次循环时插进next属性中
prev_node = current_node
# 重新获取当前对象 , 即把下一个对象单独存储起来(下个)
current_node = next_node
# 重新获取下一个对象,即把下一个对象单独存储起来,所指向的下个新对象赋值给next_node(下下个)
next_node = current_node.next
return current_node
head = reverse_link_list(head)
print(head.value)
print(head.next.value)
print(head.next.next.value)
print(head.next.next.next.value)
print(head.next.next.next.next.value)
print(head.next.next.next.next.next.value)