Skip to content

Latest commit

 

History

History
277 lines (221 loc) · 16 KB

Python-面试问题.md

File metadata and controls

277 lines (221 loc) · 16 KB

Python-面试问题

参考链接

100 道真实面试题

2016 年阿里校招 Python 面试

阿里 Python 工程师面试题

Python 36 问


  • 面:Python 中什么元素为假?
  • :(0,空字符串,空列表、空字典、空元组、None, False)

  • 面:Python 中查看某个关键字的属性?
  • :dir ( key ),help() 函数返回帮助文档和参数说明,dir() 函数返回对象中的所有成员 (任何类型)


  • 面:Python 中的 pass 语句有什么作用?
  • :我们在写代码时,有时可能只写了函数声明而没想好函数怎么写,但为了保证语法检查的正确必须输入一些东西。在这种情况下,我们使用 pass 语句。

  • 面:举例说明异常模块中 try except else finally 的相关意义?
  • :try..except..else 没有捕获到异常,执行 else 语句;try..except..finally 不管是否捕获到异常,都执行 finally 语句。

  • 面:with 方法打开处理文件帮我们做了什么?yield 的使用-生成器?
  • :功能类似 try,except,finally 中 finally 的用法,with:除了有更优雅的语法,真正强大的地方:with 还可以很好的处理上下文环境产生的异常。with 后面的函数需要有 __ exit __ 方法,当程序出现异常的时候会帮助我们打印异常,清理资源,关闭文件。with 参考博客yield 参考博客

  • 面:Python 中断言方法举例?
  • :使用 assert 断言是学习 Python 一个非常好的习惯,Python assert 断言句语格式及用法很简单。在没完善一个程序之前,我们不知道程序在哪里会出错,与其让它在运行最崩溃,不如在出现错误条件时就崩溃。

  • 面:列出 Python 中可变数据类型 和 不可变数据类型,并简述原理?
  • :不可变数据类型:数值型、字符串型、和 元组;不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象(一个地址)可变数据类型:列表 和 字典,允许变量的值发生变化,即如果对变量进行 append、+= 等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。

  • 面:赋值,浅拷贝,深拷贝的区别?

    **赋值:**简单地拷贝对象的引用,两个对象的 id (内存中的地址)相同,并且共用一个对象,删除 a 也不会删除数组,原因见下一题 引用计数机制。

    **浅拷贝-copy():**创建一个新的组合对象,这个新对象与原对象共享内存中的子对象。当改变原对象时,才会创建一个实例。仅仅是最顶层开辟了新的空间,里层的元素地址还是一样的。

    **深拷贝-deepcopy():**创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。

#赋值
a = [1,2,3]
b = a #赋值 id(a) == id(b),直接引用计数

#浅拷贝
import copy 
a = 1  #对于不可变数据类型 数字a=1; 字符串a='hello'; 元组a=(1,2,3),不存在啥浅拷贝的,和赋值一样
b = copy.copy(a) #id(a) == id(b)

a = [1,2,3] #对于可变数据类型 列表,字典,仅仅是最顶层开辟了新的空间,里层的元素地址还是一样的。
b = copy.copy(a) # id(a) != id(b)
id(a[0]) == id(b[0]) #True 里面的不可变类型地址是一样的

#深拷贝,逻辑同上,但是浅拷贝不会递归拷贝元素是地址的值,浅拷贝对 c 也是简单的拷贝。
import copy
c = [4,5]
a = [1,2,3,c]
b = copy.deepcopy(a) #会递归 a 中的引用,改变 c 的值,b 不会改变。

  • 面:简述 Python 引用计数机制?

  • :GC(Garbage Collector(垃圾收集器))机制:以下三种都是; Python 垃圾回收主要以引用计数为主,标记-清除 和 分代清除 为辅的机制,其中 标记-清除 和 分代回收 主要是为了处理循环引用的难题。 引用计数算法:当有 1 个变量保存了对象的引用时,此对象的引用计数就会加 1,变为 0 后才会真正清除对象。 优点:高效、实时性[为 0 直接清除,其他可能需要等到合适的时机]、易于实现 缺点:消耗资源、无法解决循环引用;解决:标记-清除 和 分代清除

    循环引用参考博客,下面简要介绍。

#循环引用问题
list1 = [];list2 = []
list1.append(list2) 
list2.append(list1)
'''
针对循环引用的情况:我们有一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。
『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC 会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么 GC 又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
'''

  • 面:Python 中的 值传递 与 引用传递?
  • :Python 不允许程序员选择采用 传值 还是 传引用。Python 参数传递采用的肯定是 传对象引用 的方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典、列表)的引用,就能修改对象的原始值——相当于通过 传引用 来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能 直接修改原始对象——相当于通过 传值 来传递对象。 当人们复制 列表 或 字典 时,就复制了对象列表的引用值,如果改变引用的值,则修改了原始的参数。

基本概念

  • 面:避免转义给字符串加哪个字母表示原始字符串?
print(r'\abc') #输出原始字符串 \abc,只有在不对 \ 进行转义的时候用到
  • 面:单引号,双引号,三引号的区别?
name = 'tom'; sex = "male" #单引号和双引号单独使用没什么区别

#下面才是单双引号设计的本质目的,这样里面 'A' 就可以不用添加 \,变为 \'A\' 
sentense = "We all know that 'A' and 'B' are two capital letters."

#三个引号不经常用,用于原格式输出
article = '''从哪来?
到哪去?
怎么去?
'''
  • 面:Python2 和 Python3 的区别?
  1. Python3 使用 print 必须要以小括号包裹打印内容。
  2. 2.x 中 range 函数返回一个列表,3.x 返回一个可迭代对象。
  3. 2.x 中 sort 有 cmp 参数可接受两个参数的函数,3.x 中只有 key 接受一个参数的函数。
  4. 2.x 默认编码:ascii 解决办法:在首行 # -- encoding:utf-8--;3.x 中编码:utf-8。
  5. 2.x 整数除法为 /,3.x 为 //。

  • 面:提高 Python 运行效率的方法?
  • :1.使用生成器,因为可以节约大量内存。2.循环代码优化,避免在循环中调用太多函数,可以用变量代替。

  • 面:列举 3 条以上 PEP8 编码规范?
  1. 不要在行尾加分号, 也不要用分号将两条命令放在同一行。
  2. 不要使用反斜杠连接行,与左括号对齐,Python 会将圆括号的行隐式的连接起来。
  3. 顶层函数和类的定义,前后用两个空行隔开。
  4. 一般使用 4 个空格来缩进代码

  • 面:Python 中标识符的命名规则?
  • :Python 中的标识符可以是任意长度,但必须遵循以下命名规则:
  1. 只能以下划线或者 A-Z/a-z 中的字母开头。
  2. 其余部分只能使用 A-Z/a-z/0-9。
  3. Python 标识符区分大小写。
  4. 关键字不能作为标识符。

  • 面:IOError、AttributeError、ImportError、IndentationError IndexError、KeyError、SyntaxError、NameError分别代表什么异常?
  • :IOError:输入输出异常 AttributeError:试图访问一个对象没有的属性 ImportError:无法引入模块或包,基本是路径问题 IndentationError:语法错误,代码没有正确的对齐 IndexError:下标索引超出序列边界 KeyError:试图访问你字典里不存在的键 SyntaxError:Python 代码逻辑语法出错,不能执行 NameError:使用一个还未赋予对象的变量

  • 面:Python中的身份运算符 is 和 == 的区别?
  • :is 判断两个对象 id 是否相同,== 判断两个对象值是否相同,具体参考我的博客

  • 面:Python 中运算符?
[+,-,*,/,%,//,**] #算术运算符
[>,>=,<,<=,==,!=] #比较(关系)运算符
[&|~^,<<,>>] #位运算符
[in] #成员运算符
[and,or,not] #逻辑运算符

  • 面:在 Python 中使用多进制数字?
bin(10) #转为二进制,python 中二进制表示0b1010
oct(10) #转为八进制, 0o12
hex(10) #转为十六进制,0xa

  • 面:什么是元组的解封装?
x,y,z = (1,2,3) #用变量取出 tuple 里面的值

内部函数

  • 面:列出几种魔法方法并简要介绍用途?
  • :两边的下划线省略了, init :对象初始化方法 new :创建对象时候执行的方法,单列模式会用到 str :当使用 print 输出对象的时候,只要自己定义了 __ str __(self) 方法,那么就会打印从在这个方法中 return 的数据 del :删除对象执行的方法

  • 面:Python 中的 sort 是用什么排序实现的,时间复杂度是多少?
  • :TimSort 算法是一种起源于 归并排序 和 插入排序 的混合排序算法,设计初衷是为了在真实世界中的各种数据中可以有较好的性能。基本工作过程是:1.扫描数组,确定其中的单调上升段和严格单调下降段,将严格下降段反转;2.定义最小基本片段长度,短于此的单调片段通过 插入排序集中为长于此的段;3.反复归并一些相邻片段,过程中避免归并长度相差很大的片段,直至整个排序完成,所用分段选择策略可以保证O(n log n)时间复杂性。 可以看到,原则上TimSort是归并排序,但小片段的合并中用了插入排序。 注意:2.x 中 cmp 可以接受两个参数的函数,3.x 中 key 接受一个参数的函数,需要用key = cmp_to_key(func) 进行模拟。

  • 面:Python 的高阶函数有哪些?
#更多可参考 Python 常用函数
map(lambda x: x*x ,[1,2,3])
zip([1,2,3],[4,5,6])
filter(lambda x: True if x%2==0 else False, [1,2,3,4])
import functools
functools.reduce(lambda x,y: x+y, [1,2,3,4])

#问:中的 *args,**kwargs 什么意思?
fun(*args,**kwargs) 
#主要用于函数定义,将不定数量的参数传递给fun函数,参数存在args中,args是一个元组(tuple)。允许你将不定长度的键值对作为参数传递给一个函数,参数则存储为一个字典(dict)。

  • 面:Python 中生成随机整数、随机小数、0 - 1 之间小数方法?
import random
random.randint(a,b) #生成区间内的整数包括区间 [a,b]
random.random() #生成 [0.0,1.0) 中的随机小数
import numpy
numpy.random.randn(5) #返回一个列表,生成 5 个随机数

进阶问题

  • 面:简述 Python GIL 的概念, 以及它对 Python 多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。?
  • 目的:为了利用多核,Python 开始支持多线程。而解决多线程之间数据完整性和状态同步,最简单方法自然就是加锁,于是有了 GIL 这把超级大锁。 影响:开始当然是为了支持多线程,但后来发现对效率有很多影响。看到过一个测试, 一个循环 1 亿次的计数器函数。一个通过单线程执行两次,一个多线程执行。多线程竟然慢了大约45%。 原因:按照 Python 社区的想法,为了让各个线程能够平均利用 CPU 时间,Python 会计算当前已执行。这种模式在只有一个 CPU 核心的情况下毫无问题。任何一个线程被唤起时都能成功获得到 GIL,但当 CPU 有多个核心的时候,问题就来了。从 release GIL 到 acquire GIL 之间几乎是没有间隙的。所以当其他在其他核心上的线程被唤醒时,大部分情况下主线程已经又再一次获取到 GIL了。这个时候被唤醒执行的线程只能白白的浪费 CPU 时间。参考博客

  • 面:什么是猴子补丁?
  • :在运行时动态修改类和模块,这种场景也比较多,比如我们引用团队通用库里的一个模块,又想丰富模块的功能,除了继承之外也可以考虑用 Monkey Patch。个人感觉 Monkey Patch 带来了便利的同时也有搞乱源代码优雅的风险。

  • 面:如何在 Python 中管理内存?
  • :Python 用一个私有堆内存空间来放置所有对象和数据结构,我们无法访问它,由解释器来管理它,不过使用一些核心 API,我们可以访问一些 Python 内存管理工具控制内存分配。

  • 面:当退出 Python 时是否释放所有内存分配?
  • :答案是否定的。那些具有对象循环引用或者全局命名空间引用的变量,在 Python 退出是往往不会被释放另外不会释放 C 库保留的部分内容。

  • 面:一句话解释什么样的语言能够用(decorator)装饰器?
  • :函数可以作为参数传递的语言,可以使用装饰器
def prin_fun_name(func):
	def new_method(*args, **kw):#适用于任意参数的装饰器
		print('used function' + func.__name__)
		return func(*args, **kw)
	return new_method

  • 面:请解释 Python 中的闭包?
  • :1. 假如你需要写一个带参数的装饰器,那么一般都会生成闭包。
  1. 我个人认为,闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。参考博客

  • 面:解释 Python 中 metaclass 关键字?
  • 参考博客

  • 面:?

  • 面:?

  • 面:?

  • 面:?

  • 面:?