python 面试题
50道Python面试题,面试巩固必看,建议收藏!-CSDN博客
1、装饰器
方法一、函数装饰器,装饰函数
写一个记录函数执行时间的装饰器。
# 点评:高频面试题,也是最简单的装饰器,面试者必须要掌握的内容。
# 方法一:用函数实现装饰器。
import time
def record_time(func):
def wrapper(*args,**kwargs):
starttime=time.time()
re=func(*args,**kwargs)
time.sleep(10)
print(f'{func.__name__}执行时间:{time.time()-starttime}')
return re
return wrapper
@record_time
def sumadd(*args):
sum=0
for i in args:
sum=sum+i
return sum
print(sumadd(1,2,3,4,5))
方法二:用类实现装饰器。类有__call__魔术方法,该类对象就是可调用对象,可以当做装饰器
来使用。# 类装饰器的实现,必须实现__call__和__init__两个内置函数。
# __init__:装饰器带参数时不再接收被装饰函数,而是接收传入参数;没带参数时接受函数为构造函数
# __call__:带参数时接收被装饰函数,再起一层wrapper。不带参数时接受参数*args, **kwargs参数,这时他就是wrapper。
不带参数
class DecoratorTest(object): #定义一个类
def __init__(self,func):
self.__func = func
def __call__(self): #定义call方法,当直接调用类的时候,运行这里。
print('pre msg')
self.__func()
print('post msg')
@DecoratorTest
def test():
print('主函数体')
if __name__=='__main__':
test()
# 例子:带参数
class animals:
def __init__(self, prefix):
self.prefix = prefix # 装饰器传入的参数变为类的属性
def __call__(self, func): # 原传入函数在call函数传入,再起一层wraooer
def wrapper(*args, **kwds): # 核心装饰器,参数用通用的这个格式
print("前置功能" + self.prefix)
print(func(*args, **kwds))
print("后置功能")
return wrapper
@animals("汪汪叫")
def cat(c):
print(f"叫声是{c}")
cat("喵喵叫")
from functools import wraps
class Record:
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f'{func.__name__}执行时间: {time.time() - start}秒')
return result
return wrapper
# 说明:装饰器可以用来装饰类或函数,为其提供额外的能力,属于设计模式中的代理模式。
# 扩展:装饰器本身也可以参数化,例如上面的例子中,如果不希望在终端中显示函数的执行时间而是希望由调用者来决定如何输出函数的执行时间,可以通过参数化装饰器的方式来做到,代码如下所示。
2、说一下你对闭包的理解。
闭包是支持一等函数的编程语言(Python、JavaScript等)中实现词法绑定的一种技术。当捕捉闭包的时候,它的自由变量(在函数外部定义但在函数内部使用的变量)会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。简单的说,可以将闭包理解为能够读取其他函数内部变量的函数。正在情况下,函数的局部变量在函数调用结束之后就结束了生命周期,但是闭包使得局部变量的生命周期得到了延展。使用闭包的时候需要注意,闭包会使得函数中创建的对象不会被垃圾回收,可能会导致很大的内存开销,所以闭包一定不能滥用。
3、说一下Python中的多线程和多进程的应用场景和优缺点
线程是操作系统分配CPU的基本单位,进程是操作系统分配内存的基本单位。通常我们运行的程序会包含一个或多个进程,而每个进程中又包含一个或多个线程。多线程的优点在于多个线程可以共享进程的内存空间,所以进程间的通信非常容易实现;但是如果使用官方的CPython解释器,多线程受制于GIL(全局解释器锁),并不能利用CPU的多核特性,这是一个很大的问题。使用多进程可以充分利用CPU的多核特性,但是进程间通信相对比较麻烦,需要使用IPC机制(管道、套接字等)。
多线程适合那些会花费大量时间在I/O操作上,但没有太多并行计算需求且不需占用太多内存的I/O密集型应用。多进程适合执行计算密集型任务(如:视频编码解码、数据处理、科学计算等)、可以分解为多个并行子任务并能合并子任务执行结果的任务以及在内存使用方面没有任何限制且不强依赖于I/O操作的任务。
Python中实现并发编程通常有多线程、多进程和异步编程三种选择。异步编程实现了协作式并发,通过多个相互协作的子程序的用户态切换,实现对CPU的高效利用,这种方式也是非常适合I/O密集型应用的。
4、Python中如何实现字符串替换操作?
Python中实现字符串替换大致有两类方法:字符串的replace方法和正则表达式的sub方法。
方法一:使用字符串的replace方法。
message = 'hello, world!'
print(message.replace('o', 'O').replace('l', 'L').replace('he', 'HE'))
5、使用正则表达式的sub方法。
import re
message = 'hello, world!'
pattern = re.compile('[aeiou]')
print(pattern.sub('#', message))
6、写一个函数实现字符串反转,尽可能写出你知道的所有方法
# 写一个函数实现字符串反转,尽可能写出你知道的所有方法。
# 点评:烂大街的题目,基本上算是送人头的题目。
# 方法一:反向切片
def reverse_string(content):
return content[::-1]
# 方法二:反转拼接
def reverse_string(content):
return ''.join(reversed(content))
# 方法三:递归调用
def reverse_string(content):
if len(content) <= 1:
return content
return reverse_string(content[1:]) + content[0]
# 方法四:双端队列
from collections import deque
def reverse_string(content):
q = deque()
q.extendleft(content)
return ''.join(q)
# 方法五:反向组装
from io import StringIO
def reverse_string(content):
buffer = StringIO()
for i in range(len(content) - 1, -1, -1):
buffer.write(content[i])
return buffer.getvalue()
# 方法六:反转拼接
def reverse_string(content):
return ''.join([content[i] for i in range(len(content) - 1, -1, -1)])
# 方法七:半截交换
def reverse_string(content):
length, content = len(content), list(content)
for i in range(length // 2):
content[i], content[length - 1 - i] = content[length - 1 - i], content[i]
return ''.join(content)
# 方法八:对位交换
def reverse_string(content):
length, content = len(content), list(content)
for i, j in zip(range(length // 2), range(length - 1, length // 2 - 1, -1)):
content[i], content[j] = content[j], content[i]
return ''.join(content)
# 扩展:这些方法其实都是大同小异的,面试的时候能够给出几种有代表性的就足够了。给大家留一个思考题,上面这些方法,哪些做法的性能较好呢?我们之前提到过剖析代码性能的方法,大家可以用这些方法来检验下你给出的答案是否正确。
7、按照题目要求写出对应的函数。
要求:列表中有1000000个元素,取值范围是[1000, 10000),设计一个函数找出列表中的重复元素。
def find_dup(items: list):
dups = [0] * 9000
for item in items:
dups[item - 1000] += 1
for idx, val in enumerate(dups):
if val > 1:
yield idx + 1000
点评:这道题的解法和计数排序的原理一致,虽然元素的数量非常多,但是取值范围[1000, 10000)并不是很大,只有9000个可能的取值,所以可以用一个能够保存9000个元素的dups列表来记录每个元素出现的次数,dups列表所有元素的初始值都是0,通过对items列表中元素的遍历,当出现某个元素时,将dups列表对应位置的值加1,最后dups列表中值大于1的元素对应的就是items列表中重复出现过的元素。
8、一行求和
a=sum([1,2,3,4])
b=sum(range(1,5))
c=sum(x for x in [1,2,3,4] )
d=sum(x for x in [1,2,3,4] if x % 2==0)
一、基础语法与数据类型
-
Python 中列表(List)和元组(Tuple)的区别?
-
列表:可变(可增删改元素),用
[]
定义。 -
元组:不可变,用
()
定义,适合存储常量数据。
-
-
什么是可变类型和不可变类型?举例说明。
-
可变类型:对象内容可修改,如
list
、dict
、set
。 -
不可变类型:对象内容不可修改,如
int
、str
、tuple
。
-
-
解释
is
和==
的区别-
is
:比较对象的内存地址是否相同。 -
==
:比较对象的值是否相等。
-
二、函数与作用域
-
Python 的函数参数传递是值传递还是引用传递?
-
是“对象引用传递”:对不可变参数(如数字、字符串)的修改不会影响原对象,对可变参数(如列表)的修改会影响原对象。
-
-
*args
和**kwargs
的作用是什么?-
*args
:接收任意数量的位置参数,转为元组。 -
**kwargs
:接收任意数量的关键字参数,转为字典。
-
-
解释闭包(Closure)的概念
-
函数嵌套时,内部函数可以访问外部函数的变量,即使外部函数已执行完毕。
-
三、面向对象编程(OOP)
-
Python 中的类方法(
@classmethod
)、静态方法(@staticmethod
)和实例方法的区别?-
实例方法:默认第一个参数是
self
(实例对象)。 -
类方法:第一个参数是
cls
(类对象),用@classmethod
装饰。 -
静态方法:无默认参数,用
@staticmethod
装饰,与类无绑定。
-
-
什么是继承和多态?
-
继承:子类继承父类的属性和方法。
-
多态:子类可重写父类方法,实现不同行为。
-
四、高级特性
-
解释装饰器(Decorator)的原理,写一个简单例子
python
复制
def my_decorator(func):def wrapper():print("Before function call")func()print("After function call")return wrapper@my_decorator def say_hello():print("Hello!")
-
生成器(Generator)和迭代器(Iterator)的区别?
-
生成器:用
yield
关键字实现,惰性计算数据。 -
迭代器:实现
__iter__
和__next__
方法的对象。
-
五、内存管理与并发
-
Python 的垃圾回收机制是什么?
-
主要基于引用计数,当对象引用计数归零时被回收。辅以标记-清除(解决循环引用)和分代回收。
-
-
GIL(全局解释器锁)是什么?对多线程有什么影响?
-
GIL 是同一时刻只允许一个线程执行 Python 字节码的锁,导致多线程无法充分利用多核 CPU,适合 I/O 密集型任务,CPU 密集型任务建议用多进程。
-
六、常见代码题
-
反转字符串
python
复制
s = "hello" reversed_s = s[::-1] # "olleh"
-
用生成器实现斐波那契数列
python
复制
def fibonacci(n):a, b = 0, 1for _ in range(n):yield aa, b = b, a + b
七、库与框架
-
__init__.py
文件的作用?-
标记目录为 Python 包,可包含初始化代码或定义
__all__
变量。
-
-
列举常用的第三方库及其用途
-
requests
:HTTP 请求。 -
pandas
:数据分析。 -
Django/Flask
:Web 开发。
-
八、综合问题
-
如何优化 Python 程序的性能?
-
使用内置函数和库、避免全局变量、利用生成器、使用 C 扩展(如 NumPy)、异步编程等。
-
-
Python 的深拷贝和浅拷贝的区别?
-
浅拷贝(
copy.copy()
):只复制父对象,子对象仍引用原对象。 -
深拷贝(
copy.deepcopy()
):递归复制所有子对象。
-