|
Python常见面试题(持续更新 23-2-13)
参考资料
https://github.com/taizilongxu/interview_python
https://github.com/hantmac/Python-Interview-Customs-Collection
https://github.com/kenwoodjw/python_interview_question
有些来自上面(但我也做了自己的补充),有些来自网络或书籍
本文不准备写编程题,偏重于理论一些。你要的话去刷leetcode就是了。
倒序描述,限于篇幅,可能要连载
004. 请说出下面代码的返回结果是什么?
参考了 https://www.liujiangblog.com/course/python/44
如有侵权,联系删除
- 示例代码1
- class D:
- pass
- class C(D):
- pass
- class B(C):
- def show(self):
- print("i am B")
- pass
- class G:
- pass
- class F(G):
- pass
- class E(F):
- def show(self):
- print("i am E")
- pass
- class A(B, E):
- pass
- a = A()
- a.show()
复制代码 - 结果
- 你可以整理出这样的一个继承关系
- 从执行结果看先走 是A(B,E)中左侧的B这个分支:可见,在A的定义中,继承参数的书写有先后顺序,写在前面的被优先继承。
- 你可以查看A的__mro__属性
- print(A.__mro__) # 你也可以这样print(A.mro())
- # 是个元组
- (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.G'>, <class 'object'>)
复制代码 - 如果你改成这样
- class A(E,B):
- pass
- print(A.__mro__)
复制代码 - 顺序自然成了这样
- (<class '__main__.A'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
复制代码 - 继承关系下的搜索顺序
- 所以,把代码改为这样,输出你应该毫无疑问了
- class D:
- def show(self):
- print("i am D")
- pass
- class C(D):
- pass
- class B(C):
- pass
- class G:
- pass
- class F(G):
- pass
- class E(F):
- def show(self):
- print("i am E")
- pass
- class A(B, E):
- pass
- a = A()
- a.show() # i am D
复制代码 - 那么这样呢?
- class H:
- def show(self):
- print("i am H")
- pass
- class D(H):
- pass
- class C(D):
- pass
- class B(C):
- pass
- class G(H):
- pass
- class F(G):
- pass
- class E(F):
- def show(self):
- print("i am E")
- pass
- class A(B, E):
- pass
- a = A()
- a.show()
复制代码 - 继承关系是这样的
- 答案是
- 看MRO
- print(A.__mro__)
- (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.H'>, <class 'object'>)
复制代码 - 所以继承树的搜索顺序是这样的
- 而所有的继承其实都是这2种图形的变化
- 比如这样的代码
- class D():
- pass
- class G():
- def show(self):
- print("i am G")
- pass
- class F(G):
- pass
- class C(D):
- pass
- class B(C,F):
- pass
- class E(F):
- def show(self):
- print("i am E")
- pass
- class A(B, E):
- pass
- a = A()
- a.show()
复制代码 - 你先分析下应该输出啥,继承树是怎样的,MRO是如何的?
- 输出
- 继承树
- MRO
- (<class '__main__.A'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
复制代码
- 关于类的继承
- 子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
- 根据父类定义中的顺序,以深度优先的方式逐一查找父类!
- 子类永远在父类前面,如果有多个父类,会根据它们在列表中的顺序被检查,如果对下一个类存在两个合法的选择,选择第一个父类
- MRO:即Method Resolution Order(方法解析顺序)
- 从Python 2.3开始计算MRO一直是用的C3算法
- https://www.python.org/download/releases/2.3/mro/
复制代码 - C3算法的简单解释可以参考码农高天的这个视频:
003. 请说出下面的代码返回结果是什么?为何?如何改进?
知识点: 函数参数的类型
- 示例代码
- def f(a, L=[]):
- L.append(a)
- return L
- print(f(1))
- print(f(1))
复制代码 - 据说是国内某上市互联网公司Python面试真题(略作改动),而实际在python的官网也有
- https://docs.python.org/zh-cn/3.9/tutorial/controlflow.html#default-argument-values
复制代码 - 烂大街了
- 典型的错误答案
- 实际的答案
- 因为Python函数体在被读入内存的时候,默认参数a指向的空列表对象就会被创建,并放在内存里了。因为默认参数a本身也是一个变量,保存了指向对象[]的地址。每次调用该函数,往a指向的列表里添加一个A。a没有变,始终保存的是指向列表的地址,变的是列表内的数据!
- 修改
- def f(a, L=None):
- if L is None:
- L = []
- L.append(a)
- return L
- print(f(1))
- print(f(1))
复制代码
- 官网的重要警告: 默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果
- 这样的代码
- def f(a, L=[]):
- L.append(a)
- return L
- print(f(1))
- print(f(2)) # 被我改成了1,具有欺骗性一点
- print(f(3)) # 去掉了,3个放一起,你都能猜到有点猫腻了,2个虽然也....总归好一点
复制代码 - 上面的函数会累积后续调用时传递的参数
- 不想在后续调用之间共享默认值时,建议用None这样的不可变对象来存储
- def f(a, L=None):
- if L is None:
- L = []
- L.append(a)
- return L
复制代码 002. 请分别说出下面的代码返回结果是什么?为何?
知识点: 作用域
- 示例代码1
- def func(x):
- print(x)
- print(y)
- func(1)
复制代码 - 示例代码2
- y = 1
- def func(x):
- print(x)
- print(y)
- func(1)
复制代码 - 示例代码3
- y = 1
- def func(x):
- print(x)
- print(y)
- y = 2
- func(1)
复制代码
- 示例代码1的执行结果
- NameError: name 'y' is not defined
复制代码 - 示例代码2的执行结果
- 示例代码3的执行结果
- UnboundLocalError: local variable 'y' referenced before assignment
复制代码
解释3
- Python 编译函数的定义体时,它判断 b 是局部变量,依据是y=2,你对它进行了赋值。Python 会尝试从本地环境获取 b。
- 后面调用 func(1)时,func的定义体会获取并打印局部变量x 的值,但是尝试获取局部变量 y 的值时,发现 y 没有 绑定值就报错了。
- 这不是缺陷,这是设计如此(做测试是不是经常听到这句话)
- Python 不要求声明变量,但是假定在函数定义体中赋值的变 量,那就认为它是局部变量
对于代码3的处理
- 示例代码3(更改)
- y = 1
- def func(x):
- global y
- print(x)
- print(y)
- y = 2
- func(1)
复制代码 - 这样解释器就会把 y 当成全局变量,从而找到第一行的y=1并print出来了
从函数的字节码也能看出来这个过程
- 代码1
- def func(x):
- print(x)
- print(y)
- y = 2
- from dis import dis
- dis(func)
复制代码 - 字节码
- 3 0 LOAD_GLOBAL 0 (print) # 加载全局名称print
- 2 LOAD_FAST 0 (x) # 加载本地名称x
- 4 CALL_FUNCTION 1
- 6 POP_TOP
- 4 8 LOAD_GLOBAL 0 (print)
- 10 LOAD_FAST 1 (y) # 加载本地名称y
- 12 CALL_FUNCTION 1
- 14 POP_TOP
- 5 16 LOAD_CONST 1 (2)
- 18 STORE_FAST 1 (y)
- 20 LOAD_CONST 0 (None)
- 22 RETURN_VALUE
复制代码
- 示例代码2
- y = 1
- def func(x):
- print(x)
- print(y)
- from dis import dis
- dis(func)
复制代码 - 字节码
- 3 0 LOAD_GLOBAL 0 (print)
- 2 LOAD_FAST 0 (x)
- 4 CALL_FUNCTION 1
- 6 POP_TOP
- 4 8 LOAD_GLOBAL 0 (print)
- 10 LOAD_GLOBAL 1 (y) # 看这里的变化,是全局变量了
- 12 CALL_FUNCTION 1
- 14 POP_TOP
- 16 LOAD_CONST 0 (None)
- 18 RETURN_VALUE
- 进程已结束,退出代码为 0
复制代码
- 示例代码3
- y = 1def func(x):
- print(x)
- print(y)
- y = 2
- from dis import dis
- dis(func)
复制代码 - 字节码
- 3 0 LOAD_GLOBAL 0 (print)
- 2 LOAD_FAST 0 (x)
- 4 CALL_FUNCTION 1
- 6 POP_TOP
- 4 8 LOAD_GLOBAL 0 (print)
- 10 LOAD_FAST 1 (y) # 又变成了本地
- 12 CALL_FUNCTION 1
- 14 POP_TOP
- 5 16 LOAD_CONST 1 (2)
- 18 STORE_FAST 1 (y)
- 20 LOAD_CONST 0 (None)
- 22 RETURN_VALUE
复制代码 - 示例代码3(更改)
- y = 1
- def func(x):
- global y
- print(x)
- print(y)
- y = 2
- from dis import dis
- dis(func)
复制代码 - 字节码
- 4 0 LOAD_GLOBAL 0 (print)
- 2 LOAD_FAST 0 (x)
- 4 CALL_FUNCTION 1
- 6 POP_TOP
- 5 8 LOAD_GLOBAL 0 (print)
- 10 LOAD_GLOBAL 1 (y)
- 12 CALL_FUNCTION 1
- 14 POP_TOP
- 6 16 LOAD_CONST 1 (2)
- 18 STORE_GLOBAL 1 (y)
- 20 LOAD_CONST 0 (None)
- 22 RETURN_VALUE
复制代码
看不懂字节码不要紧的,当然非要,你可以去参考https://docs.python.org/zh-cn/3/library/dis.html
作用域LEGB相关知识单独考虑弄个博文
001. is和==有什么区别
- ==是对象的值的比较,也就是对象保存的数据。
- is比较的是对象的标识。
- 示例代码1
- a = [1,2,3]
- b = [1,2,3]
- print(a == b) # True
- print(a is b) # False
复制代码 - is的背后是id,is比较为True,说明2个id的返回是一样的
- 在 CPython 中,id() 返回对象的内存地址, 但是在其他 Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在 对象的生命周期中绝不会变。
上面的话引自 8.2 标识、相等性、别名
这些知识涉及对象的引用,相关的面试题如浅拷贝/深拷贝、重载运算符(==)等
浅拷贝也考虑单独剥离弄个博文或主题
- 示例代码2
- >>> a = 257
- >>> b = 257
- >>> a is b
- False
- >>> a == b
- True
- # 但是这个呢?
- >>> c = d = 256
- >>> c is d
- True
复制代码 - 这是因为出于对性能优化的考虑,Python内部会对-5到256的整型维持一个数组,起到一个缓存 的作用。这样,每次你试图创建一个-5到256范围内的整型数字时,Python都会从这个数组中返回相对应的引用,而不是重新开辟一块新的内存空间。
- 但是,如果整型数字超过了这个范围,比如上述例子中的257,Python则会为两个257开辟两块 内存区域,因此a和b的ID不一样,a is b就会返回False了。
- 比较操作符'is'的速度效率,通常要优于'=='。因为'is'操作符不能被重载,这 样,Python就不需要去寻找,程序中是否有其他地方重载了比较操作符,并去调用。执行比较操作符'is',就仅仅是比较两个变量的ID而已。
- 但是'=='操作符却不同,执行a == b相当于是去执行a.__eq__(b),而Python大部分的数据类型都 会去重载__eq_这个函数,其内部的处理通常会复杂一些。比如,对于列表,__eq_函数会去 遍历列表中的元素,比较它们的顺序和值是否相等。
来源:[url=https://www.cnblogs.com/wuxianfeng023/p/171
15981.html]https://www.cnblogs.com/wuxianfeng023/p/171
15981.html[/url]
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|