前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python面试必刷题系列(4)

Python面试必刷题系列(4)

作者头像
小萌哥
发布2020-07-20 15:35:05
6650
发布2020-07-20 15:35:05
举报
文章被收录于专栏:算法研习社算法研习社

你会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(默认会继承)。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 算法研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • python中函数与方法的区别?
  • 函数装饰器有什么用?列举说明
  • @classmethod和@staticmethod的区别?
  • Python中的鸭子类型了解吗?
  • 了解Python的MRO原理吗?
  • Python传参是传值还是传址?
  • 你知道哪些魔法函数?用过吗?
  • 说说isinstance的作用?
  • Python中的接口如何实现?
  • 说说常见的异常类型以及产生原因。
  • 说说python的垃圾回收机制?
    • 1. 引用计数器机制:
      • 2. 标记-清除:
        • 3. 分代回收:
        • 新式类和经典类的区别
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档