Python面试必刷题系列(4)
你会Python嘛? 我会! 那你给我讲下Python装饰器吧! Python装饰器啊…. 我没用过哎
以上是一个哥们面试的时候发生的真实对白。
本篇是python必刷面试题
系列的第4篇文章,集中讲解了面试时重点考察的python基础原理和语法特性,如python的垃圾回收机制、多态原理、MRO以及装饰器和静态方法等语法特性。相信认真读完本文,你不仅可以轻松化解类似上面场景中小尴尬,对今后写出更加高效、优雅的代码也有很大帮助。
python中函数与方法的区别?
-
分类:
方法
,一般可以认为是对象里面定义的函数,比如一个对象的普通方法、私有方法、属性方法、魔法方法、类方法等,而函数
则是那些和对象无关的,比如lambda函数、python内置函数等等。 - 作用域:通过实例化的对象进行方法的调用,调用后开辟的空间不会释放,而函数则不同,函数执行完后,为其开辟的内存空间立即释放(存储到了栈里)。
- 调用方式:函数是通过“函数名()”的方式调用,方法通过“对象.方法名()”的方式进行调用。
判别方法可参考下面的实例:
from types import MethodType, FunctionType
def aaa():
pass
class A(object):
def __init__(self):
pass
def bbb(self):
pass
a = A()
print(isinstance(aaa, MethodType)) # False
print(isinstance(aaa, FunctionType)) # True
print(isinstance(a.bbb, MethodType)) # False
print(isinstance(a.bbb, FunctionType)) # True
函数装饰器有什么用?列举说明
本质:仍然是一个 Python 函数,实现由由闭包
支撑,装饰器的返回值也是一个函数对象。
作用:让函数在无需修改任何代码的前提下给其增加功能。
应用场景:有切面需求的场景,如下:
- 计算函数的运行时间
- 计算函数的运行次数
- 给函数插入运行日志
- 让函数实现事务一致性:让函数要么一起运行成功,要么一起运行失败
- 实现缓存处理
- 权限校验:在函数外层套上权限校验的代码,实现权限校验
优点:抽离与函数功能本身无关的雷同代码,并重复重用。
实例:实现一个@timer装饰器,记录每个函数的运行时间。
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
print("[Time out]: %.4f s" % (time.time() - start_time))
return res
return wrapper
@timer
def sum(a, b):
time.sleep(2)
return a + b
print("计算结果:", sum(1, 2))
# 运行结果:
[Time out]: 2.0019 s
计算结果: 3
@classmethod和@staticmethod的区别?
Python中3种方式定义类方法, 常规方式(self), @classmethod修饰方式, @staticmethod修饰方式。
- 普通的类方法,需要通过self参数隐式的传递当前类的
实例对象
。 - @classmethod修饰的方法,需要传递当前
类对象
参数cls(调用时可以不写)。 - @staticmethod修饰的方法,定义与普通函数是一样的,不需要传
实例对象
和类对象
。
总结:
- @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()
# 输出结果
static_foo
1
class_foo
1
foo
注意: @classmethod方法和@staticmethod方法既可以通过类调用,也可以通过类的实例对象调用,输出结果是一致的。
Python中的鸭子类型了解吗?
鸭子类型(duck typing),是python面向对象的一种多态机制。一种通俗的解释方法,“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
从原理上理解:
由于python是解释型语言,在运行时,边"翻译"边执行,当执行时遇到一个对象,将要调用对象的一个方法或者获取其属性时,只要这个对象实例存在这些方法或属性,那个程序就可以成功执行。因此,我们不用管一个对象是classA的实例化对象还是classB的实例化对象,我们只关心这个对象的属性或行为是否能够满足程序执行的需求。
打个比方就是,程序现在需要一个像鸭子一样的对象来执行游泳、走、叫的功能,但是这时候传第过来的是一个鸟,这个鸟具有这些功能,而且执行的效果和鸭子完全一样!!那么,我们就可以认为这个鸟就是一个鸭子类型
。
了解Python的MRO原理吗?
MRO
,全称是Method Resolution Order(方法解析顺序),它指的是对于一棵类继承树,当调用最底层类对象所对应实例对象的一个方法时,Python解释器在继承树上搜索该方法的顺序。
对于一棵类继承树,可以调用最底层类对象的方法mro()或访问最底层类对象的特殊属性_ mro _,来获得这颗类继承树的MRO。
当子类通过super()调用其父类中的方法时,该方法的搜索顺序基于以该子类为最底层类对象的类继承树的MRO。
如果想调用指定某个父类中被重写的方法,可以给super()传入两个实参:super(A_type, obj),其中,第一个实参A_type是个类对象,第二个实参obj是个实例对象,这样,被指定的父类是:
obj所对应类对象的MRO中,A_type类后面的那个类对象。
下面通过一个实例,理解一下Python中多重继承关系下的MRO。
类继承关系示例
# 首先定义A-F共6个类,继承关系如上图。
class A(object):
def fun(self):
print('A.fun')
class B(object):
def fun(self):
print('B.fun')
class C(object):
def fun(self):
print('C.fun')
class D(A, B):
def fun(self):
print('D.fun')
class E(B, C):
def fun(self):
print('E.fun')
class F(D, E):
def fun(self):
print('F.fun')
# 打印最底层F类的mro
print(F.mro())
# F类实例对象作为底层对象时,查找其mro中位于A类后面的那个类对象
super(A, F()).fun()
输出结果如下:
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
E.fun
也就是说,当查找F类中一个方法时,查找的顺序是:F->D->A->E->B->C->object,object是所有类的基类。
至于这个搜索顺序如何生成,其实是采用的C3算法
:每次将继承树中入度为0的结点放入列表,如果有多个结点符合,左侧优先。
其过程如下:
类继承关系示例
- 首先将入度(指向该节点的箭头数量)为零的节点放入列表,并将F节点及与F节点有关的箭头从上图树中删除;
- 继续找入度为0的节点,找到D和E,左侧优先,故而现将D放入列表,并从上图树中删除D,这是列表中就有了F、D;
- 继续找入度为0的节点,有A和E满足,左侧优先,所以是A,将A从上图中取出放入列表,列表中顺序为F、D、A;
- 接下来入度为0的节点只剩下E,取出E放入列表;
- 只剩下B和C节点,且入度都为0,左侧优先,将B先放入列表,最后才是C;
- 但是别忘了,Python所有类都有一个共同的父类,那就是object类,所以,最好还会把object放入列表末尾。
- 最终生成列表中元素顺序为:F->D->A->E->B->C->object。
Python传参是传值还是传址?
1. 传值、传址的概念和区别:
传值就是传入一个参数的值,传址就是传入一个参数的地址,也就是内存的地址(相当于指针)。他们的区别是如果函数里面对传入的参数赋值,函数外的全局变量是否相应改变,用传值传入的参数是不会改变的,用传址传入就会改变。
2. python中的传参形式:传址
Python采用的是“传对象引用”的方式,相当于传值
和传址
的一种综合。
如果函数收到的是一个可变对象(比如dict或者list)的引用,就能修改对象的原始值——相当于传址。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用(其实也是对象地址!!!),就不能直接修改原始对象——相当于是传值。
所以python的传值和传址是比如根据传入参数的类型来选择的:
- 传值的参数类型:数字,字符串,元组(immutable)
- 传址的参数类型:列表,字典(mutable)
你知道哪些魔法函数?用过吗?
python有很多内置魔法方法,一般表现为双下划线开头和结尾。这些魔法方法会让对象持有特殊行为,使python的自由度变得更高。下面是常用的一些魔法函数:
允许一个类的实例像函数一样被调用:x(a, b) 调用 x.call(a, b)
说说isinstance的作用?
定义:
def isinstance(object, class_or_type_or_tuple):
...
- 第一个参数(object)为对象。
- 第二个参数为class或type或一个元组(如(int,list,float))。
- 返回值为布尔型(True or flase)。
作用:判断一个对象是否为另一个对象或其子类的实例。注意哈,Python中万物皆对象,其实里面的东西还不少,可以通过下面的习题检验一下。
print isinstance(1, int)
print isinstance(True, int)
print isinstance(1, (str, bool, int))
print isinstance(int, type)
print isinstance(int, object)
print isinstance(type, object)
print isinstance(object, type)
print isinstance(type, type)
答案:全是True。。。不知道那答对了吗
解析:
- 在python中,bool 类型其实是 int 的子类。
-
isinstance(obj, (A,B))
相当于isinstance(obj, A) or isinstance(obj, A)
- type继承自object,但object也是type的实例,type本身也是type的实例,int、str等内置类型更是type的实例啦~~
篇幅限制,讲的比较粗糙,感兴趣的可以加入交流群讨论,我们每天打卡学习哦~
Python中的接口如何实现?
接口:只是定义了一些方法,而没有去实现,多用于程序设计时,只是设计需要有什么样的功能,但是并没有实现任何功能,这些功能需要被另一个类(B)继承后,由类B去实现其中的某个功能或全部功能。
在python中,其实没必要使用类似java的interface。因为Python里有多继承和使用鸭子类型。
如果非要在python中实现接口,方法如下:
from abc import ABCMeta, abstractmethod
class A(object):
__metaclass__ = ABCMeta # 指定这是一个抽象类
@abstractmethod # 在类中声明一个抽象方法,该类的子类必须实现该方法
def hello(self, name):
pass
class B(A):
# 接口中的所有抽象方法都要实现
def hello(self, name):
print('你好呀,%s' % name)
obj = B()
obj.hello("lihong")
# 如果B中存在未实现的接口方法,实例化时将报错:
# Can't instantiate abstract class B with abstract methods hello
说说常见的异常类型以及产生原因。
异常类 |
含义 |
---|---|
KeyError |
试图访问字典里不存在的键 |
ValueError |
传入一个调用者不期望的值,即使值的类型是正确的 |
TypeError |
在运算或函数调用时,使用了不兼容的类型时引发的异常 |
IndexError |
下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] |
AttributeError |
访问对象属性时引发的异常,如属性不存在或不支持赋值等。 |
NameError |
尝试访问一个没有定义过的变量 |
AssertionError |
断言语句失败 |
SyntaxError |
Python 语法错误 |
NotImplementedError |
尚未实现的方法 |
UnboundLocalError |
访问未初始化的本地变量 |
MemoryError |
内存溢出错误 |
IOError |
输入/输出异常,基本上是无法打开文件 |
说说python的垃圾回收机制?
垃圾回收机制(简称GC)是Python解释器自带一种机制,专门用来回收不可用的变量值所占用的内存空间,主要运用了引用计数机制
来跟踪和回收垃圾。在引用计数的基础上,还可以通过标记-清除
解决容器对象可能产生的循环引用的问题。通过分代回收
以空间换取时间进一步提高垃圾回收的效率。
1. 引用计数器机制:
Python中万物皆对象。每个对象都会记录着自己被引用的个数,当一个对象的引用数为0时,它占据的内存将被回收。
(1)增加引用个数的情况:对象被创建;被引用;被当作参数传入函数;被存储到容器对象中。
(2)减少引用个数的情况:对象的别名被销毁;别名被赋予其他对象;对象离开自己的作用域;对象从容器对象中删除,或者容器对象被销毁。
循环引用问题
: 如果a引用b, b也引用a, 导致相互引用的对象的引用计数永远不为0,内存也就永远不会被释放。
2. 标记-清除:
在了解标记清除算法前,我们需要明确一点,内存中有两块区域:堆区与栈区,在定义变量时,变量名存放于栈区,变量值存放于堆区,内存管理回收的则是堆区的内容。
标记-清除
只关注那些可能会产生循环引用
的对象,比如list、dict、set、class等,因为它们内部可能很持有其它对象的引用。
原理:
- 标记:标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象。
- 清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
3. 分代回收:
python中垃圾回收的时机有两种:手动回收
和自动回收
。
import gc
# 手动回收
gc.collect()
# 检测自动回收是否开启
gc.isenabled()
# 开启自动回收
gc.enable()
# 关闭自动回收
gc.disable()
# 获取自动回收配置
print gc.get_threshold()
# 结果:(700, 10, 10) ,700是垃圾回收启动的阈值,10,10是下面讲
引用计数回收机制,每次回收都非常耗时地遍历全部对象,分代回收的核心思想是:经多次扫描没有被回收的变量,肯定是常用变量,应该降低对其扫描的频率。
python将所有的对象分为0,1,2三代。新建对象都是0代。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
这两个次数即上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。可用set_threshold()来调整。
新式类和经典类的区别
- Python2.x中,从 object 继承的类是新式类,否则是经典类。
- 新式类的 MRO(method resolution order 基类方法搜索顺序)采用 C3 算法广度优先搜索,而旧式类的 MRO 算法是采用深度优先搜索。
- 新式类相同父类只执行一次构造函数,经典类重复执行多次。
值得注意的是,python2.x中,默认都是经典类(不继承子object),Python 3.x 中默认都是新式类,经典类被移除,且不用显式的继承 object(默认会继承)。
- 即将举行的全球区块链峰会强调东西方合作
- 区块链兄弟社区问答精选:关于51%攻击,你了解有多少?
- 编程小技巧
- use vue vuex vue-router, not use webpack
- 从尾到头打印链表
- Webpack+Vue如何导入Jquery和Jquery的第三方插件
- [Hadoop大数据]——Hive部署入门教程
- Vuex原来可以这样上手
- 《Hive编程指南》—— 读后总结
- Event(事件)的传播与冒泡
- [Hadoop大数据]——Hive数据的导入导出
- 区块链技术在电子游戏与博彩行业备受追捧 有望实现数字商品货币化
- vue原来可以这样上手
- [Hadoop大数据]——Hive连接JOIN用例详解
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用docker-compose 搭建 lnmp
- Java接口也有坑?不容忽视!
- 值得练手的JavaGUI项目——色彩调节器的实现【附完整源码】
- 自定义短标签
- Python简单又好玩的项目推荐!【持续更新】
- 使用 JsDelivr作为CDN 加速服务
- Java利用TCP协议实现客户端与服务器通信【附通信源码】
- 增加标签云代替原有标签和分类效果
- 新增搜索功能
- 利用实例巧妙区分“抽象方法”和“虚方法”!
- windows 右键菜单的添加和移除
- Windows 查找被占用端口并结束程序
- Java利用UDP协议建立广播组通信【附通信源码】
- Windows家庭版安装Docker
- C#实现多个子窗体切换效果