Python基础面试题,Python面试基础知识
高考结束不久,网上各种求职者和工地搬砖的人开始活跃起来。这个时候,如果我不出来活跃一下,就有点不合时宜了。所以我特意整理了一下这个Python面试问题的基本文章。我不在乎能不能整理出高级的。我估计看完一整本也看不懂他们!(篇幅比较长,可以通过目录选择自己想看的内容~)
标题001:如何在Python中实现singleton模式?主题002:不使用中间变量,而是交换两个变量“a”和“b”的值。题目003:写一个函数删除一个列表中的重复元素,要求删除重复后元素的相对位置保持不变。话题004:假设你用的是官方的CPython,说出下面这段代码的运行结果。话题005:什么是lambda函数,并举例说明其应用场景。话题006:谈谈Python中的浅拷贝和深拷贝。话题007:007:Python如何实现内存管理?话题008:说说你对Python中迭代器和生成器的理解。话题009:正则表达式的匹配方法和搜索方法有什么区别?话题010:下面这段代码的执行结果是什么?话题011:Python中为什么没有函数重载?话题012:用Python代码实现Python内置函数max。话题013:写一个函数,统计每个数字在传入列表中出现的次数,并返回对应的字典。话题014:用Python代码实现遍历文件夹的操作。话题015:2元、3元、5元三种面额的货币。如果需要换99元,有多少种换法?题目016:给定矩阵的阶n,写一个函数,输出一个螺旋数字矩阵。题目017:阅读下面的代码,写出程序的运行结果。题目018:说出以下代码的运行结果。话题19:说说你用过Python标准库中的哪些模块?话题20:方法` _ _ init _ _ 和方法` __new__ 有什么区别?题目21:输入年、月、日,判断这个日期是一年中的哪一天。话题22:日常工作中静态代码分析用什么工具?话题23:说说你知道的Python中的魔法方法?话题24:函数参数`* arg 和`* **kwargs分别代表什么?主题25:写一个装饰器,记录一个函数的执行时间。话题26:什么是鸭式打字?话题27:谈谈Python中变量的作用域。话题28:说说你对闭包的理解。话题29:谈谈Python中多线程和多处理的应用场景和优缺点。话题30:说出Python 2和Python 3的区别。话题31:谈谈你对“猴子打补丁”的理解。题目32:阅读下面的代码,说出运行结果。题目33:写一个函数来计算逆波兰表达式。不能用Python的内置函数。主题34:如何在Python中实现字符串替换操作?话题35:如何分析Python代码的执行性能?题目36:如何使用` random 模块生成随机数,实现随机无序和随机抽样?解释线程池是如何工作的。题目38:举例说明出现“KeyError”、“TypeError”和“ValueError”的情况。题目39:说出以下代码的运行结果。话题40:如何读取大文件,比如内存只有4G,如何读取8G大小的文件?话题41:说说你对Python中模块和包的理解。话题42:说说你知道的Python编码规范。主题43:如果你运行下面的代码,你会报错吗?如果您报告了一个错误,请说明是哪种错误。如果不报错,请告知代码的执行结果。题目44:按降序排列下面给出的字典中的关键字。话题45:谈论namedtuple的用法和功能。题目46:根据题目要求,写出相应的函数。题目47:根据题目要求,写出相应的函数。题目48:根据题目要求,写出相应的装饰品。题目49:写一个函数实现字符串反转。尽可能把你知道的方法都写出来。
题目50:根据题目要求,写出相应的函数。主题001:如何用Python实现singleton模式?注释:单例模式意味着一个类只能创建一个唯一的实例。这个话题在面试中出现得非常频繁,因为它不仅考察了单例模式,还考察了你对Python的掌握程度。我建议你用decorator和元类来实现singleton模式,因为这两种方法是最通用的,也可以顺便展示你对decorator和元类中两个关键知识点的理解。
方法一:用decorator实现singleton模式。
从functools导入包装
def singleton(cls):
单纯形类装饰器
实例={}
@wraps(cls)
def包装(*args,**kwargs):
如果cls不在实例中:
instances[cls]=cls(*args,**kwargs)
返回实例[cls]
返回包装
@单身
班长:
Pass extension: Decorator是Python中一种非常有特色的语法,它使用一个函数来修饰另一个函数或类,为其添加额外的功能。通常,通过修饰实现的功能是横切关注点,即与正常业务逻辑没有必然联系,可以动态添加或删除的功能。Decorator可以为代码提供缓存、代理、上下文等服务,这是代理模式在设计模式中的实践。编写装饰器时,带装饰函数的函数(上面代码中的包装器函数)通常是用functools模块中的包装器来装饰的。这个装饰器最重要的功能是向被装饰的类或函数动态添加一个__wrapped__属性,这将在被装饰之前保留类或函数。这样,当我们不需要装修功能的时候,就可以通过它取消装修工了。比如我们可以用President=president。_ _ wrapped _ _取消president类的单例处理。需要提醒大家的是,上面的singleton不是线程安全的。如果您想要线程安全,您需要锁定创建该对象的代码。在Python中,可以使用线程模块的RLock对象来提供锁,并且可以使用Lock对象的acquire和release方法来锁定和解锁。当然,使用lock对象的with context语法来执行隐式锁定和解锁操作更简单。
方法二:用元类实现单体模式。
类SingletonMeta(类型):
自定义单例元类
def __init__(cls,*args,**kwargs):
cls。__instance=无
超级()。__init__(*args,**kwargs)
def __call__(cls,*args,**kwargs):
如果cls。_ _实例为无:
cls。__instance=super()。__call__(*args,**kwargs)
返回cls。_ _实例
班长(meta pass扩展:Python是一种面向对象的编程语言。在面向对象的世界里,一切都是对象。对象是由类创建的,而类本身就是对象。像类这样的对象是由元类创建的。当我们定义一个类时,如果没有为一个类指定父类,默认父类是object,如果没有为一个类指定元类,默认元类是type。通过自定义元类,我们可以改变一个类的默认行为,就像上面的代码中,我们通过元类__call__的神奇方法,改变了President类的构造函数。
补充:关于singleton模式,面试中可能会问到它的应用场景。通常,一个对象的状态是由其他对象共享的,因此可以将其设计为单例。比如项目中使用的数据库连接池对象和配置对象通常都是singletons,这样才能保证从各地获取的数据库连接和配置信息完全一致;而且由于对象只有一个实例,从根本上避免了重复创建对象带来的时间和空间开销,也避免了资源的多次占用。再比如,一个项目中的日志操作通常使用singleton模式,因为共享的日志文件总是打开的,只有一个实例可以操作,否则写日志时会出现混乱。
话题002:不用一个中间变量,而是交换两个变量A和b的值点评:典型的送人头话题。通常,需要一个中间变量来交换两个变量。如果不允许使用中间变量,在其他编程语言中可以使用XOR运算来交换两个变量的值,但是在Python中有一种更简单明了的方式。
方法1:
a=^ b
^ b
A=a b方法2:
a,b=b,a扩展:需要注意的是,a,b=b,a的这种做法其实并不是元组解包,虽然很多人都这么认为。Python字节码指令有ROT_TWO指令来支持这个操作,类似的还有ROT_THREE。对于3个以上的元素,比如a,b,c,d=b,c,d,a,将使用元组创建和元组解包。要知道你的代码对应的字节码指令,可以使用Python标准库中dis模块的dis函数反汇编你的Python代码。
题目003:写一个函数删除一个列表中的重复元素,要求删除重复后元素的相对位置保持不变。点评:这个话题经常出现在初级Python的求职面试中。题目源于本书第一章第10题《Python Cookbook》。很多面试问题其实都是这本书里的原题,建议你有时间仔细研究一下这本书。
定义重复数据删除(项目):
no_dup_items=[]
seen=set()
对于项目中的项目:
如果看不到项目:
no_dup_items.append(项目)
seen.add(项目)
No _ dup _ items如果你愿意,你也可以把上面的函数转换成一个生成器。代码如下所示。
定义重复数据删除(项目):
seen=set()
对于项目中的项目:
如果看不到项目:
产量项目
Seen.add(item)扩展:由于Python中集合的底层使用了hash存储,集合的in和not in成员操作在性能上远远优于list的操作,所以我们使用集合来保存上面代码中已经出现的元素。集合中的元素必须是可哈希对象,因此当列表元素不是可哈希对象时,上面的代码将失败。为了解决这个问题,您可以向函数添加一个参数,该函数可以设计为返回哈希代码或可哈希对象的函数。
话题004:假设你用的是官方的CPython,说出下面这段代码的运行结果。点评:下面这个程序对实际开发没有任何意义,但却是CPython的一个大洞。这个问题旨在考察面试官对官方Python解释器了解多少。
a,b,c,d=1,1,1000,1000
打印(a是b,c是d)
def foo():
e=1000
f=1000
打印(e是f,e是d)
g=1
打印(g是a)
fo()的运算结果:
对还是错
对还是错
上面代码中a是b的结果为真而c是d的结果为假,实在令人费解。为了优化性能,CPython解释器使用一个名为small_ints的对象池来缓存经常使用的整数对象。small_ints缓存的整数值被设置为[-5,256]的区间,即无论在哪里引用这些整数,都不需要重新创建int对象,而是直接引用缓存池中的对象。如果整数不在这个范围内,那么即使两个整数的值相同,也是不同的对象。
为了进一步提高性能,CPython的底层做了另外的设置。对于同一个代码块中的整数,其值不在small_ints的缓存范围内,如果同一个代码块中已经有一个相同值的integer对象,那么直接引用该对象,否则创建一个新的int对象。需要注意的是,这个规则适用于数值型,但是对于字符串要考虑字符串的长度,这个你可以自己证明。
扩展:如果用PyPy(另一个Python解释器实现,支持JIT,改进了CPython的缺点,性能上优于CPython,但对三方库的支持略差)运行上面的代码,会发现输出都是真的。
话题005:什么是lambda函数,并举例说明其应用场景。点评:本题主要想考察Lambda函数的应用场景。潜台词是问你有没有在项目中使用过Lambda函数,在什么场景下会使用Lambda函数,以此来判断你写代码的能力。因为Lambda函数通常用在高阶函数中,所以它的主要作用是通过将函数传入函数或者让函数返回函数来实现代码解耦。
Lambda函数也叫匿名函数,是一个函数简单的小函数,一行代码就可以实现。Python中的Lambda函数只能写一个表达式,这个表达式的执行结果就是函数的返回值,不写return关键字。Lambda函数没有名字,所以不会和其他函数有命名冲突。
延伸:面试的时候可能还会要求你用Lambda函数实现一些功能,也就是一行代码实现题目要求的功能,比如一行代码实现求阶乘的功能,一行代码实现求最大公约数的功能等等。
fac=lambda x:_ _ import _ _( functools )。减少(整数。__mul__,范围(1,x 1),1)
gcd=x,y: y % x和gcd(y % x,x)或x
其实Lambda函数的主要目的是将一个函数转移到另一个高阶函数中(比如Python内置的filter,map等。)对功能进行解耦,增强其灵活性和通用性。在下面的例子中,通过使用filter和map函数,实现了从列表中过滤奇数并将它们平方以形成新列表的操作。因为使用了更高阶的函数,所以过滤和映射数据的规则都是由该函数的调用者通过另一个函数传入的,所以这些过滤和映射函数没有与过滤和映射数据的特定规则耦合。
items=[12,5,7,10,8,19]
items=list(map(lambda x: x ** 2,filter(lambda x: x % 2,items)))
Print(items) # [25,49,361]扩展:用list的生成器实现上面的代码会更简单明了。代码如下所示。
items=[12,5,7,10,8,19]
items=[x ** 2 for x in items if x % 2]
打印(项目)# [25,49,361]
话题006:谈谈Python中的浅拷贝和深拷贝。点评:这个题目本身出现的频率很高,但就题目而言并没有什么技术含量。对于这类面试问题,在回答的时候,一定要让自己的回答超出面试官的预期,这样才能获得更好的印象分。所以回答这个问题的要点,不仅仅是要说出浅拷贝和深拷贝的区别,还要说出深拷贝过程中可能遇到的两大问题。然后,你可以讲讲列表和字典是如何实现复制操作的,如何通过序列化和逆序实现深度复制。最后,你可以在设计模式中提到原型模式及其在项目中的应用。
轻拷贝通常只拷贝对象本身,而深拷贝不仅拷贝对象,还递归地拷贝与对象关联的对象。深度复制可能会遇到两个问题:一是如果一个对象直接或间接引用自身,会导致无休止的递归复制;第二,深度复制也可以复制最初设计为由多个对象共享的数据。浅复制和深复制操作是通过Python复制模块中的copy和deepcopy函数实现的,其中deepcopy可以通过memo字典保存复制的对象,从而避免了刚才提到的自引用递归问题;此外,指定类型对象的复制行为可以通过copyreg模块的pickle函数自定义。
deepcopy函数的本质其实是对象的一次性序列化和一次性返回序列化。在面试问题中,我们还测试了具有自定义函数的对象的深度复制操作。显然,我们可以使用pickle模块的转储和加载来做到这一点。代码如下所示。
进口泡菜
my _ deep _ copy=lambdaobj:pickle . loads(pickle . dumps(obj))列表的切片操作[:]相当于list对象的浅层拷贝,而dictionary copy方法可以实现dictionary对象的浅层拷贝。复制对象实际上是一种更快的创建对象的方法。在Python中,通过构造函数创建对象是一个两阶段的构造,首先分配内存空间,然后初始化。在创建对象时,我们也可以在“原型”对象的基础上创建一个新的对象,通过复制原型对象(复制内存)来完成对象的创建和初始化。这样效率更高,这是设计模式中的原型模式。在Python中,我们可以通过元类的方式实现原型模式,代码如下。
导入副本
类PrototypeMeta(类型):
实现原型模式的“元类”
def __init__(cls,*args,**kwargs):
超级()。__init__(*args,**kwargs)
#将clone方法绑定到对象以复制对象。
cls.clone=lambda self,is_deep=True: \
copy . deep copy(self)if is _ deep else copy . copy(self)
班级人员(元通行证
p1=人员()
P2=p1.clone() #深层拷贝
P3=p1.clone(is_deep=False) #浅复制话题007:007:Python如何实现内存管理?点评:当面试官问这个问题的时候,一个展示自己的机会就摆在面前。你得先问面试官“你说的是官方的CPython解释器吗?”。这个反问可以说明你了解Python解释器的不同实现版本,也知道面试官想问的是CPython。当然,许多面试官对不同Python解释器的底层实现之间的差异没有任何概念。所以,不要以为面试官一定比你强。有了这个自信,你就能更好的完成面试。
Python提供了自动内存管理,也就是说内存空间的分配和释放是在运行时由Python解释器自动进行的。自动内存管理的功能大大减轻了程序员的工作量,也可以在一定程度上帮助程序员解决内存泄漏的问题。以CPython解释器为例,其内存管理有三个关键点:引用计数、标签清理和代收集。
参考:对于CPython解释器来说,Python中的每个对象其实都是一个PyObject结构,里面有一个名为ob_refcnt的引用计数器成员变量。在程序运行期间,ob_refcnt的值将被更新,以反映有多少变量被引用到该对象。当一个对象的引用计数值为0时,它的内存将被释放。
typedef struct _object {
_ PyObject _ HEAD _ EXTRA
Py _ ssize _ t ob _ refcnt
struct _ typeobject * ob _ type
} PyObject以下情况将导致引用计数增加1:
被引用的对象将创建的对象作为参数传递给函数,对象作为元素存储在容器中。以下情况将导致参考计数减少1:
Del语句用来表明被删除的对象引用被重新分配给其他对象的对象引用。当一个对象离开它的作用域时,保存该对象本身的容器就被销毁了。删除对象时,可以通过sys模块的getrefcount函数获取对象的引用计数。计数的内存管理方式遇到循环引用会很致命,需要其他垃圾收集算法来补充。
标记:CPython使用“标记和清除”算法来解决容器类型可能导致的循环引用问题。该算法在垃圾收集中可分为两个阶段:标记阶段,遍历所有对象,如果对象可达(被其他对象引用),则标记为可达;在清除阶段,对象被再次遍历,如果发现某个对象没有被标记为可达,它将被回收。CPython的底层维护了两个双头链表,其中一个存放要扫描的容器对象(姑且称之为链表A),另一个存放临时不可达对象(姑且称之为链表B)。为了实现“标记-清除”算法,链表中的每个节点除了记录当前引用计数的ref_count变量之外,还有一个gc_ref变量。这个gc_ref是ref_count的副本,所以初始值是ref_count的大小。垃圾回收时,首先遍历链表A中的节点,当前对象引用的所有对象的gc_ref减1。该步骤主要用于消除循环引用对引用计数的影响。再次遍历链表A中的节点。如果节点的gc_ref值为0,那么这个对象被标记为“暂时不可达”(GC _ temporarily _ unreachable),并被移动到链表B中;如果一个节点的gc_ref不为0,那么这个对象将被标记为“REACHABLE”(GC _ REACHABLE)。对于可达对象,该节点的可达节点将被递归地标记为“可达”。链表B中标记为“reachable”的节点要放回链表a中,经过两次遍历,链表B中的节点就是需要释放内存的节点。
生成回收:在循环引用对象的回收中,整个应用程序将被挂起。为了减少应用暂停时间,Python通过世代回收(空间换时间)提高垃圾收集效率。逐代回收的基本思想是:一个物体存在的时间越长,它就越不可能是垃圾,所以我们应该尽量不回收这样的物体。CPython将对象分为三代,分别命名为0、1、2。每个新对象都是第0代。如果对象在垃圾收集扫描中幸存,它将被移动到第1代,并且第1代中存在的对象将较少被垃圾收集扫描。如果此对象在第一代被扫描进行垃圾收集时仍然存在,它将被移动到第二代,在第二代中,它将被扫描进行垃圾收集的频率会降低。逐代垃圾扫描的阈值可以通过gc模块的get_threshold函数得到,该函数返回一个三元组,分别表示多少次内存分配操作会导致0代垃圾收集、0代垃圾收集后的1代垃圾收集、1代垃圾收集后的2代垃圾收集。需要注意的是,如果第二代垃圾回收执行一次,后辈就会执行垃圾回收。如果要修改这些阈值,可以通过gc模块的set_threshold函数来实现。
话题008:说说你对Python中迭代器和生成器的理解。点评:很多面试官会写迭代器和生成器,但是不能解释清楚迭代器和生成器到底是什么。如果你也有同样的困惑,可以参考下面的回答。
迭代器是实现迭代器协议的对象。与其他编程语言不同,Python没有用于定义协议或表达约定的关键字。接口、协议之类的词不在Python语言的关键词列表中。Python通过魔术方法表达约定,我们称之为协议,两个魔术方法__next__和__iter__代表迭代器协议。可以通过for-in循环从迭代器对象中获取值,或者使用next函数从迭代器对象中获取下一个值。它是生成器迭代器语法的升级版本,迭代器可以用更简单的代码实现。
扩展:生成斐波那契数列的迭代器经常在访谈中写。可以参考下面的代码。
类别纤维(对象):
def __init__(self,num):
self.num=数字
self.a,self.b=0,1
self.idx=0
def __iter__(self):
回归自我
def __next__(自己):
if self.idx self.num:
self.a,self.b=自我
self.idx=1
回归自我
引发StopIteration()
如果用生成器的语法重写上面的代码,代码会简单得多,也优雅得多。
定义纤维(数量):
a,b=0,1
for _ in范围(数量):
a,b=b,a b
产生
话题009:正则表达式的匹配方法和搜索方法有什么区别?点评:正则表达式是字符串处理的重要工具,所以也是面试中经常考察的知识点。在Python中,有两种使用正则表达式的方法。一种是直接调用re模块中的函数,传入正则表达式和要处理的字符串;一种是通过re模块的编译函数创建一个正则表达式对象,然后通过对象调用方法,传入要处理的字符串。如果经常使用正则表达式,我们建议使用re.compile函数创建一个正则表达式对象,这样可以减少频繁编译同一个正则表达式带来的开销。
match方法是从字符串的开头开始匹配正则表达式,并返回Match对象或None。search方法扫描整个字符串以查找匹配模式,并返回一个match对象或None。
话题010:下面这段代码的执行结果是什么?def乘法():
return[lambda x:I * x for I in range(4)]
打印([m (100) for m in multiple ()])运行结果:
[300,300,300,300]上面代码的运行结果很容易被误判为[0,100,200,300]。首先需要注意的是,multiply函数返回的是一个带有生产语法的列表。列表中存储了4个Lambda函数,将返回传递的参数与I相乘的结果,需要注意的是,这里存在闭包现象,乘法函数中局部变量I的生命周期被延长。由于I的最终值是3,所以当列表中的Lambda函数被m(100)调优时,它将返回300,对于所有四个调用都是如此。
如果想得到[0,100,200,300]的结果,可以用以下方法修改乘法函数。
方法一:用生成器让函数得到I的当前值。
def乘法():
return(lambda x:I * x for I in range(4))
Print ([m (100)代表m的倍数()])或
def乘法():
对于范围(4)中的I:
产生x:x * I
Print ([m (100) for m in multiple ()])方法二:使用部分函数,避免完全封闭。
从functools导入部分
从操作员导入__mul__
def乘法():
return[范围(4)中I的partial(__mul__,I)]
Print([m(100) for m in multiply()])话题011:Python中为什么没有函数重载?点评:C、Java、C#等很多编程语言都支持函数重载。所谓函数重载,是指在同一个作用域内存在多个同名函数,这些函数有不同的参数列表(不同数量或类型的参数或者两者都有),并且可以相互区分。重载也是一种多态性,因为它通常是由编译时参数的数量和类型决定要调用哪个重载函数,所以也称为编译时多态性或预绑定。这个问题的潜台词其实是问面试官是否有其他编程语言的经验,是否理解Python是一种动态类型语言,是否知道Python中函数的变量参数和关键字参数的概念。
Python一开始是解释性语言,函数重载通常发生在编译语言中。其次,Python是一种动态类型语言。函数的参数没有类型约束,所以重载不能根据参数的类型来区分。此外,在Python中,函数的参数可以有默认值,并且可以使用变量参数和关键字参数。因此,即使没有函数重载,根据调用方传入的参数,一个函数也应该有不同的行为。
话题012:用Python代码实现Python内置函数max。点评:这个题目看似简单,其实是对面试官基本功的比较研究。因为Python内置的max函数可以通过传入迭代对象来求最大值,也可以传入两个或两个以上的参数来求最大值;最重要的是,还可以通过命名关键字参数key来指定用于元素比较的函数,还可以通过命名关键字参数default来指定iterable对象为空时返回的默认值。
以下代码仅供参考:
def my_max(*args,key=None,default=None):
获取iterable对象中的最大元素或两个或多个参数中的最大元素。
:param args:一个可迭代对象或多个元素
:param key:提取用于元素比较的特征值的函数,默认为无。
:param default:如果iterable对象为空,则返回默认值;如果没有给定默认值,将引发ValueError异常。
:return:返回可迭代对象的最大元素或多个元素。
如果len(args)==1且len(args[0])==0:
如果默认:
返回默认值
否则:
raise value error(“max()arg是一个空序列”)
items=args[0]if len(args)==1 else args
max_elem,max_value=项目[0],项目[0]
如果关键:
max_value=key(max_value)
对于项目中的项目:
价值=项目
如果关键:
值=关键字(项目)
如果值max_value:
max_elem,max_value=项目,值
Max _ Elem话题013:写一个函数,统计每个数字在传入列表中出现的次数,并返回对应的字典。点评:送人头的标题,不做解释。
定义计数_字母(项目):
结果={}
对于项目中的项目:
if isinstance(item,(int,float)):
结果[项目]=result.get(项目,0) 1
Return也可以通过使用Python标准库中collections模块的Counter类直接解决这个问题。Counter是dict的子类,它以传入序列中的每个元素为键,以元素出现的次数为值来构造字典。
从集合导入计数器
定义计数_字母(项目):
计数器=计数器(项目)
return { key:key的值,counter.items中的值()\
IsInstance (key,(int,float))}主题014:用Python代码实现遍历文件夹的操作。点评:基本上也是送人头的话题。只要用过os模块,就应该知道怎么做。
Python的标准库os模块的walk函数提供了遍历文件夹的功能,返回一个生成器。
导入操作系统
g=OS . walk(/Users/Hao/Downloads/)
对于g中的path、dir_list、file_list:
对于目录列表中的目录名:
print(os.path.join(path,dir_name))
对于文件列表中的文件名:
Print (os.path.join (path,file _ name))说明:os.path模块提供了很多路径操作的工具函数,在项目开发中经常用到。如果题目明确要求不能使用os.walk函数,可以使用os.listdir函数获取指定目录下的文件和文件夹,然后使用os.isdir函数通过循环遍历来确定哪些文件夹是。对于文件夹,可以通过递归调用进行迭代,也可以实现遍历一个文件夹的操作。
话题015:2元、3元、5元三种面额的货币。如果需要换99元,有多少种换法?点评:还有一个非常类似的题目:“一个孩子走楼梯,一次可以走1步、2步或3步。走完10步有几种走法?”这两个题目的思路是一样的。如果是递归函数写的就很简单了。
从functools导入lru_cache
@lru_cache()
def change_money(合计):
如果total==0:
返回1
如果总计为0:
返回0
return change _ money(total-2)change _ money(total-3)
change_money的解释(total-5):在上面的代码中,我们用lru_cache decorator修饰了递归函数change_money。如果不做这个优化,上面代码的渐近时间复杂度将是,如果参数total的值是99,这个计算量非常巨大。Lru_cache decorator缓存函数的执行结果,可以减少重复操作带来的开销。这就是空间换时间的策略,也是动态规划的编程思路。
题目016:写一个函数,给定矩阵的阶n,输出一个螺旋数字矩阵。例如:n=2,返回:
1 2
例如:n=3,返回:
1 2 3
8 9 4
7 6 5
题目本身并不复杂,以下代码仅供参考。
定义显示_螺旋_矩阵(n):
matrix=[[0] * n for _ in range(n)]
行,列=0,0
数字,方向=1,0
而num=n ** 2:
如果矩阵[行][列]==0:
矩阵[行][列]=数字
数量=1
如果方向==0:
如果列n - 1和矩阵[行][列1]==0:
col=1
否则:
方向=1
elif方向==1:
如果行n - 1和矩阵[行1][列]==0:
行=1
否则:
方向=1
elif方向==2:
如果列0和矩阵[行][列1]==0:
列=1
否则:
方向=1
否则:
如果行0和矩阵[行-1][列]==0:
行-=1
否则:
方向=1
方向%=4
对于矩阵中的x:
对于x中的y:
print(y,end=\t )
Print()话题017:阅读下面的代码,写出程序的运行结果。项目=[1,2,3,4]
打印([i for i in items if i 2])
打印([i for i in items if i % 2])
print([(x,y) for x,y in zip(abcd ,(1,2,3,4,5))])
print({x: fitem{x ** 2} for x in (2,4,6)})
print(Len({ x for x in hello world if x not in abcdefg })点评:生成式(演绎法)是Python的特色语法之一,几乎是面试必备。Python可以通过生成字面语法创建列表、集合和字典。
[3, 4]
[1, 3]
[(a ,1),( b ,2),( c ,3),( d ,4)]
{2:第4项,4:第16项,6:第36项 }
6话题018:说出以下代码的运行结果。类别父级:
x=1
类别Child1(父级):
及格
类别Child2(父级):
及格
print(Parent.x,Child1.x,Child2.x)
Child1.x=2
print(Parent.x,Child1.x,Child2.x)
Parent.x=3
Print (parent.x,child1.x,child2.x)点评:运行上面的代码首先会输出1 1 1,这个你应该没有疑问。接下来,类Child1的属性x被Child1.x=2重新绑定并赋给2,所以Child1.x将输出2,而Parent和Child2不受影响。执行Parent.x=3会将父类的x属性重新赋值为3。由于Child2的x属性继承自父类,因此Child2.x的值也是3;在我们重新绑定Child1的x属性之前,它的x属性值将不会受到Parent.x=3或之前的值2的影响。
1 1 1
1 2 1
2 3 3话题19:说说你用过Python标准库中的哪些模块?点评:Python标准库中有很多模块。建议你根据自己以往的项目经验,介绍一下自己用过的标准库和第三方库,因为这些是你最熟悉的,也是经得起面试官挖掘的。
模块名
介绍
[计]系统复制命令(system的简写)
与Python解释器相关的变量和函数,例如:sys.version,sys.exit()
骨
与操作系统相关的函数,如os.listdir()和os.remove()
是
与正则表达式相关的函数,如re.compile()和re.search()
数学
与数学运算相关的函数,比如math.pi,math.e,math.cos
记录
与日志系统相关的类和函数,如日志。记录器和日志记录。处理者
json/pickle
实现对象序列化和反序列化的模块,如json.loads和json.dumps
摘要算法
封装各种哈希总结算法的模块,如hashlib.md5和hashlib.sha1
人人贷
包括与URL相关的子模块,如urllib.request和urllib.parse
循环器
提供各种迭代器的模块,如itertools.cycle和itertools.product
函数工具
与函数相关的工具模块,如functools.partial和functools.lru_cache
收集/堆q
封装通用数据结构和算法的模块,如collections.deque
线程/多重处理
多线程/多进程相关类和函数的模块,例如:线程。线
并发。期货/异步
与并发/异步编程相关的类和函数的模块,如ThreadPoolExecutor
base64
提供BASE-64编码相关功能的模块,例如:bas64.encode
战斗支援车
与读写CSV文件相关的模块,如csv.reader和csv.writer
profile/cProfile/pstats
与代码性能分析相关的模块,如cProfile.run和pstats。统计数据
单元测试
与单元测试相关的模块,如unittest。测试案例
话题_ _ init _ _和__new__方法有什么区别?在Python中调用构造函数来创建对象属于两阶段构造过程。首先执行__new__方法获取保存对象所需的内存空间,然后通过__init__填充内存空间数据(对象属性的初始化)。__new__方法的返回值是创建的Python对象(引用),而__init__方法的第一个参数就是这个对象(引用),所以对象的初始化可以在__init__中完成。__new__是类方法,它的第一个参数是类,__init__是对象方法,它的第一个参数是对象。
题目21:输入年、月、日,判断这个日期是一年中的哪一天。方法1:不要使用标准库中的模块和函数。
定义为闰年(年):
确定指定年份是否为闰年,在正常年份返回False,在闰年返回True。
返回年份% 4==0和年份% 100!=0或年份% 400==0
定义星期几(年、月、日):
计算一年中的某一天,此时传入日期为
#使用嵌套列表保存平年和闰年中每个月的天数
一个月中的第几天=[
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
]
days=days _ of _ month[is _ leap _ year(year)][:month-1]
返回(天数)日期方法2:使用标准库中的datetime模块。
导入日期时间
定义星期几(年、月、日):
end=datetime.date(年、月、日)
start=datetime.date(year,1,1)
返回(结束-开始)。days 1话题22:日常工作中静态代码分析用什么工具?点评:静态代码分析工具可以从代码中提取各种静态属性,使开发人员对代码的复杂性、可维护性和可读性有更好的理解。这里提到的静态属性包括:
编码是否符合编码规范,例如:PEP-8。代码中的潜在问题,包括:语法错误、缩进问题、缺失导入、变量覆盖等。代码中有异味。代码的复杂性。代码的逻辑问题。Pylint和Flake8主要用于工作中的静态代码分析。Pylint可以检查出代码错误、味道不好、代码不标准等问题。新版本还提供了代码复杂度统计,可以生成检查报告。Flake8封装了Pyflakes(检查代码逻辑错误)、McCabe(检查代码复杂度)和Pycodestyle(检查代码是否符合PEP-8规范)工具,可以执行这三个工具提供的检查。
话题23:说说你知道的Python中的魔法方法?点评:魔术法,也称魔术法,是Python中的一种特色语法,也是面试中的高频问题。
魔法方法
功能
__new__ 、__init__ 、__del__
与创建和销毁对象相关。
__add__、sub__、mul__、div__、floordiv__、mod__
算术运算符相关性
__eq__ 、__ne__ 、__lt__ 、__gt__ 、__le__ 、__ge__
关系运算符相关性
__pos__
、__neg__、__invert__
一元运算符相关
__lshift__、__rshift__、__and__、__or__、__xor__
位运算相关
__enter__、__exit__
上下文管理器协议
__iter__、__next__、__reversed__
迭代器协议
__int__、__long__、__float__、__oct__、__hex__
类型/进制转换相关
__str__、__repr__、__hash__、__dir__
对象表述相关
__len__、__getitem__、__setitem__、__contains__、__missing__
序列相关
__copy__、__deepcopy__
对象拷贝相关
__call__、__setattr__、__getattr__、__delattr__
其他魔术方法
题目24:函数参数*arg和**kwargs分别代表什么?Python中,函数的参数分为位置参数、可变参数、关键字参数、命名关键字参数。*args代表可变参数,可以接收0个或任意多个参数,当不确定调用者会传入多少个位置参数时,就可以使用可变参数,它会将传入的参数打包成一个元组。**kwargs代表关键字参数,可以接收用参数名=参数值的方式传入的参数,传入的参数的会打包成一个字典。定义函数时如果同时使用*args和**kwargs,那么函数可以接收任意参数。
题目25:写一个记录函数执行时间的装饰器。点评:高频面试题,也是最简单的装饰器,面试者必须要掌握的内容。
方法一:用函数实现装饰器。
from functools import wraps
from time import time
def record_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time()
result = func(*args, **kwargs)
print(f{func.__name__}执行时间: {time() - start}秒)
return result
return wrapper方法二:用类实现装饰器。类有__call__魔术方法,该类对象就是可调用对象,可以当做装饰器来使用。
from functools import wraps
from time import time
class Record:
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time()
result = func(*args, **kwargs)
print(f{func.__name__}执行时间: {time() - start}秒)
return result
return wrapper说明:装饰器可以用来装饰类或函数,为其提供额外的能力,属于设计模式中的代理模式。
扩展:装饰器本身也可以参数化,例如上面的例子中,如果不希望在终端中显示函数的执行时间而是希望由调用者来决定如何输出函数的执行时间,可以通过参数化装饰器的方式来做到,代码如下所示。
from functools import wraps
from time import time
def record_time(output):
"""可以参数化的装饰器"""
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time()
result = func(*args, **kwargs)
output(func.__name__, time() - start)
return result
return wrapper
return decorate题目26:什么是鸭子类型(duck typing)?鸭子类型是动态类型语言判断一个对象是不是某种类型时使用的方法,也叫做鸭子判定法。简单的说,鸭子类型是指判断一只鸟是不是鸭子,我们只关心它游泳像不像鸭子、叫起来像不像鸭子、走路像不像鸭子就足够了。换言之,如果对象的行为跟我们的预期是一致的(能够接受某些消息),我们就认定它是某种类型的对象。
在Python语言中,有很多bytes-like对象(如:bytes、bytearray、array.array、memoryview)、file-like对象(如:StringIO、BytesIO、GzipFile、socket)、path-like对象(如:str、bytes),其中file-like对象都能支持read和write操作,可以像文件一样读写,这就是所谓的对象有鸭子的行为就可以判定为鸭子的判定方法。再比如Python中列表的extend方法,它需要的参数并不一定要是列表,只要是可迭代对象就没有问题。
说明:动态语言的鸭子类型使得设计模式的应用被大大简化。
题目27:说一下Python中变量的作用域。Python中有四种作用域,分别是局部作用域(Local)、嵌套作用域(Embedded)、全局作用域(Global)、内置作用域(Built-in),搜索一个标识符时,会按照LEGB的顺序进行搜索,如果所有的作用域中都没有找到这个标识符,就会引发NameError异常。
题目28:说一下你对闭包的理解。闭包是支持一等函数的编程语言(Python、JavaScript等)中实现词法绑定的一种技术。当捕捉闭包的时候,它的自由变量(在函数外部定义但在函数内部使用的变量)会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。简单的说,可以将闭包理解为能够读取其他函数内部变量的函数。正在情况下,函数的局部变量在函数调用结束之后就结束了生命周期,但是闭包使得局部变量的生命周期得到了延展。使用闭包的时候需要注意,闭包会使得函数中创建的对象不会被垃圾回收,可能会导致很大的内存开销,所以闭包一定不能滥用。
题目29:说一下Python中的多线程和多进程的应用场景和优缺点。线程是操作系统分配CPU的基本单位,进程是操作系统分配内存的基本单位。通常我们运行的程序会包含一个或多个进程,而每个进程中又包含一个或多个线程。多线程的优点在于多个线程可以共享进程的内存空间,所以进程间的通信非常容易实现;但是如果使用官方的CPython解释器,多线程受制于GIL(全局解释器锁),并不能利用CPU的多核特性,这是一个很大的问题。使用多进程可以充分利用CPU的多核特性,但是进程间通信相对比较麻烦,需要使用IPC机制(管道、套接字等)。
多线程适合那些会花费大量时间在I/O操作上,但没有太多并行计算需求且不需占用太多内存的I/O密集型应用。多进程适合执行计算密集型任务(如:视频编码解码、数据处理、科学计算等)、可以分解为多个并行子任务并能合并子任务执行结果的任务以及在内存使用方面没有任何限制且不强依赖于I/O操作的任务。
扩展:Python中实现并发编程通常有多线程、多进程和异步编程三种选择。异步编程实现了协作式并发,通过多个相互协作的子程序的用户态切换,实现对CPU的高效利用,这种方式也是非常适合I/O密集型应用的。
题目30:说一下Python 2和Python 3的区别。点评:这种问题千万不要背所谓的参考答案,说一些自己最熟悉的就足够了。
Python 2中的print和exec都是关键字,在Python 3中变成了函数。Python 3中没有long类型,整数都是int类型。Python 2中的不等号 在Python 3中被废弃,统一使用!=。Python 2中的xrange函数在Python 3中被range函数取代。Python 3对Python 2中不安全的input函数做出了改进,废弃了raw_input函数。Python 2中的file函数被Python 3中的open函数取代。Python 2中的/运算对于int类型是整除,在Python 3中要用//来做整除除法。Python 3中改进了Python 2捕获异常的代码,很明显Python 3的写法更合理。Python 3生成式中循环变量的作用域得到了更好的控制,不会影响到生成式之外的同名变量。Python 3中的round函数可以返回int或float类型,Python 2中的round函数返回float类型。Python 3的str类型是Unicode字符串,Python 2的str类型是字节串,相当于Python 3中的bytes。Python 3中的比较运算符必须比较同类对象。Python 3中定义类的都是新式类,Python 2中定义的类有新式类(显式继承自object的类)和旧式类(经典类)之分,新式类和旧式类在MRO问题上有非常显著的区别,新式类可以使用__class__属性获取自身类型,新式类可以使用__slots__魔法。Python 3对代码缩进的要求更加严格,如果混用空格和制表键会引发TabError。Python 3中字典的keys、values、items方法都不再返回list对象,而是返回view object,内置的map、filter等函数也不再返回list对象,而是返回迭代器对象。Python 3标准库中某些模块的名字跟Python 2是有区别的;而在三方库方面,有些三方库只支持Python 2,有些只能支持Python 3。题目31:谈谈你对“猴子补丁”(monkey patching)的理解。“猴子补丁”是动态类型语言的一个特性,代码运行时在不修改源代码的前提下改变代码中的方法、属性、函数等以达到热补丁(hot patch)的效果。很多系统的安全补丁也是通过猴子补丁的方式来实现的,但实际开发中应该避免对猴子补丁的使用,以免造成代码行为不一致的问题。
在使用gevent库的时候,我们会在代码开头的地方执行gevent.monkey.patch_all(),这行代码的作用是把标准库中的socket模块给替换掉,这样我们在使用socket的时候,不用修改任何代码就可以实现对代码的协程化,达到提升性能的目的,这就是对猴子补丁的应用。
另外,如果希望用ujson三方库替换掉标准库中的json,也可以使用猴子补丁的方式,代码如下所示。
import json, ujson
json.__name__ = ujson
json.dumps = ujson.dumps
json.loads = ujson.loads单元测试中的Mock技术也是对猴子补丁的应用,Python中的unittest.mock模块就是解决单元测试中用Mock对象替代被测对象所依赖的对象的模块。
题目32:阅读下面的代码说出运行结果。class A:
def who(self):
print(A, end=)
class B(A):
def who(self):
super(B, self).who()
print(B, end=)
class C(A):
def who(self):
super(C, self).who()
print(C, end=)
class D(B, C):
def who(self):
super(D, self).who()
print(D, end=)
item = D()
item.who()点评:这道题考查到了两个知识点:
Python中的MRO(方法解析顺序)。在没有多重继承的情况下,向对象发出一个消息,如果对象没有对应的方法,那么向上(父类)搜索的顺序是非常清晰的。如果向上追溯到object类(所有类的父类)都没有找到对应的方法,那么将会引发AttributeError异常。但是有多重继承尤其是出现菱形继承(钻石继承)的时候,向上追溯到底应该找到那个方法就得确定MRO。Python 3中的类以及Python 2中的新式类使用C3算法来确定MRO,它是一种类似于广度优先搜索的方法;Python 2中的旧式类(经典类)使用深度优先搜索来确定MRO。在搞不清楚MRO的情况下,可以使用类的mro方法或__mro__属性来获得类的MRO列表。super()函数的使用。在使用super函数时,可以通过super(类型, 对象)来指定对哪个对象以哪个类为起点向上搜索父类方法。所以上面B类代码中的super(B, self).who()表示以B类为起点,向上搜索self(D类对象)的who方法,所以会找到C类中的who方法,因为D类对象的MRO列表是D -- B -- C -- A -- object。ACBD题目33:编写一个函数实现对逆波兰表达式求值,不能使用Python的内置函数。点评:逆波兰表达式也称为“后缀表达式”,相较于平常我们使用的“中缀表达式”,逆波兰表达式不需要括号来确定运算的优先级,例如5 * (2 + 3)对应的逆波兰表达式是5 2 3 + *。逆波兰表达式求值需要借助栈结构,扫描表达式遇到运算数就入栈,遇到运算符就出栈两个元素做运算,将运算结果入栈。表达式扫描结束后,栈中只有一个数,这个数就是最终的运算结果,直接出栈即可。
import operator
class Stack:
"""栈(FILO)"""
def __init__(self):
self.elems = []
def push(self, elem):
"""入栈"""
self.elems.append(elem)
def pop(self):
"""出栈"""
return self.elems.pop()
@property
def is_empty(self):
"""检查栈是否为空"""
return len(self.elems) == 0
def eval(expr):
"""逆波兰表达式求值"""
operators = {
+: operator.add,
-: operator.sub,
*: operator.mul,
/: operator.truediv
}
stack = Stack()
for item in expr.split():
if item.isdigit():
stack.push(float(item))
else:
num2 = stack.pop()
num1 = stack.pop()
stack.push(operators[item](num1, num2))
return stack.pop()题目34:Python中如何实现字符串替换操作?Python中实现字符串替换大致有两类方法:字符串的replace方法和正则表达式的sub方法。
方法一:使用字符串的replace方法。
message = hello, world!
print(message.replace(o, O).replace(l, L).replace(he, HE))方法二:使用正则表达式的sub方法。
import re
message = hello, world!
pattern = re.compile([aeiou])
print(pattern.sub(#, message))扩展:还有一个相关的面试题,对保存文件名的列表排序,要求文件名按照字母表和数字大小进行排序,例如对于列表filenames = [a12.txt, a8.txt, b10.txt, b2.txt, b19.txt, a3.txt] ,排序的结果是[a3.txt, a8.txt, a12.txt, b2.txt, b10.txt, b19.txt]。提示一下,可以通过字符串替换的方式为文件名补位,根据补位后的文件名用sorted函数来排序,大家可以思考下这个问题如何解决。
题目35:如何剖析Python代码的执行性能?剖析代码性能可以使用Python标准库中的cProfile和pstats模块,cProfile的run函数可以执行代码并收集统计信息,创建出Stats对象并打印简单的剖析报告。Stats是pstats模块中的类,它是一个统计对象。当然,也可以使用三方工具line_profiler和memory_profiler来剖析每一行代码耗费的时间和内存,这两个三方工具都会用非常友好的方式输出剖析结构。如果使用PyCharm,可以利用“Run”菜单的“Profile”菜单项对代码进行性能分析,PyCharm中可以用表格或者调用图(Call Graph)的方式来显示性能剖析的结果。
下面是使用cProfile剖析代码性能的例子。
example.py
import cProfile
def is_prime(num):
for factor in range(2, int(num ** 0.5) + 1):
if num % factor == 0:
return False
return True
class PrimeIter:
def __init__(self, total):
self.counter = 0
self.current = 1
self.total = total
def __iter__(self):
return self
def __next__(self):
if self.counter self.total:
self.current += 1
while not is_prime(self.current):
self.current += 1
self.counter += 1
return self.current
raise StopIteration()
cProfile.run(list(PrimeIter(10000)))如果使用line_profiler三方工具,可以直接剖析is_prime函数每行代码的性能,需要给is_prime函数添加一个profiler装饰器,代码如下所示。
@profiler
def is_prime(num):
for factor in range(2, int(num ** 0.5) + 1):
if num % factor == 0:
return False
return True安装line_profiler。
pip install line_profiler使用line_profiler。
kernprof -lv example.py运行结果如下所示。
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 @profile
2 def is_prime(num):
3 86624 48420.0 0.6 50.5 for factor in range(2, int(num ** 0.5) + 1):
4 85624 44000.0 0.5 45.9 if num % factor == 0:
5 6918 3080.0 0.4 3.2 return False
6 1000 430.0 0.4 0.4 return True题目36:如何使用random模块生成随机数、实现随机乱序和随机抽样?点评:送人头的题目,因为Python标准库中的常用模块应该是Python开发者都比较熟悉的内容,这个问题回如果答不上来,整个面试基本也就砸锅了。
random.random()函数可以生成[0.0, 1.0)之间的随机浮点数。random.uniform(a, b)函数可以生成[a, b]或[b, a]之间的随机浮点数。random.randint(a, b)函数可以生成[a, b]或[b, a]之间的随机整数。random.shuffle(x)函数可以实现对序列x的原地随机乱序。random.choice(seq)函数可以从非空序列中取出一个随机元素。random.choices(population, weights=None, *, cum_weights=None, k=1)函数可以从总体中随机抽取(有放回抽样)出容量为k的样本并返回样本的列表,可以通过参数指定个体的权重,如果没有指定权重,个体被选中的概率均等。random.sample(population, k)函数可以从总体中随机抽取(无放回抽样)出容量为k的样本并返回样本的列表。扩展:random模块提供的函数除了生成均匀分布的随机数外,还可以生成其他分布的随机数,例如random.gauss(mu, sigma)函数可以生成高斯分布(正态分布)的随机数;random.paretovariate(alpha)函数会生成帕累托分布的随机数;random.gammavariate(alpha, beta)函数会生成伽马分布的随机数。
题目37:解释一下线程池的工作原理。点评:池化技术就是一种典型空间换时间的策略,我们使用的数据库连接池、线程池等都是池化技术的应用,Python标准库currrent.futures模块的ThreadPoolExecutor就是线程池的实现,如果要弄清楚它的工作原理,可以参考下面的内容。
线程池是一种用于减少线程本身创建和销毁造成的开销的技术,属于典型的空间换时间操作。如果应用程序需要频繁的将任务派发到线程中执行,线程池就是必选项,因为创建和释放线程涉及到大量的系统底层操作,开销较大,如果能够在应用程序工作期间,将创建和释放线程的操作变成预创建和借还操作,将大大减少底层开销。线程池在应用程序启动后,立即创建一定数量的线程,放入空闲队列中。这些线程最开始都处于阻塞状态,不会消耗CPU资源,但会占用少量的内存空间。当任务到来后,从队列中取出一个空闲线程,把任务派发到这个线程中运行,并将该线程标记为已占用。当线程池中所有的线程都被占用后,可以选择自动创建一定数量的新线程,用于处理更多的任务,也可以选择让任务排队等待直到有空闲的线程可用。在任务执行完毕后,线程并不退出结束,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程长时间处于闲置状态时,线程池可以自动销毁一部分线程,回收系统资源。基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。