python面试基础知识问题

1.简述函数式编程

函数
  函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

函数式编程(Functional Programming)
  函数式编程是一种抽象程度很高的编程范式。纯粹的函数式编程语言编写的函数没有变量的,因此任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用的。而非纯粹的函数(允许使用变量的),由于函数内部的变量状态不确定,同样的输入,有可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的特点:允许把函数本身作为参数传入另一个函数,还允许返回一个函数。

Python对函数式编程提供部分的支持,由于Python允许使用变量,所以Python不是纯函数式编程语言。

2.什么是匿名函数,有什么局限性

匿名函数就是不需要显示的定义函数,即不需要函数名。在 Python 中可以通过 lambda 关键字来定义匿名函数。

因为函数没有名字,不必担心函数名冲突。与 map()reduce()filter()结合使用可以使代码更加 pythoner。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。也可以把匿名函数作为返回值返回。

匿名函数也存在限制,就是只能有一个表达式,不用写 return,返回值就是该表达式的结果。其代码的可读性也比较差差,有时会难于理解。

3.如何捕获异常,常用的异常机制有哪些?

Python 捕捉异常可以使用 try/except 语句。

try/except 语句用来检测 try 语句块中的错误,从而让 except 语句捕获异常信息并处理。

如果你不想在异常发生时结束你的程序,只需在 try 里捕获它。

try:
<语句>        #运行别的代码
except <名字><语句>        #如果在try部份引发了'name'异常
except <名字><数据>:
<语句>        #如果引发了'name'异常,获得附加的数据
else:
<语句>        #如果没有异常发生
finally:
<语句>        #退出 try 会执行

elsefinally 不是必须的。

常用的异常机制

默认处理机制

如果我们没有对异常进行任何预防,那么在程序执行的过程中发生异常,就会中断程序,调用 Python 默认的异常处理器,并在终端输出异常信息。

try…except

发现 try 语句,进入 try 语句块执行,发生异常,回到 try 语句层,寻找后面是否有 except 语句。找到 except 语句后,会调用这个自定义的异常处理器。except 将异常处理完毕后,程序继续往下执行。

try…finally

finally 语句表示,无论异常发生与否,finally 中的语句都要执行。但是,由于没有 except 处理器,finally 执行完毕后程序便中断。

assert

遇到 assert 后面紧跟的语句是 True 还是 False ,如果是 True 则继续执行,如果是 False 则中断程序,调用默认的异常处理器,同时输出 assert 语句逗号后面的提示信息。

with…as

处理流对象时,with…as 语句十分的非常方便。with 语句块完毕之后,会隐藏地自动关闭流。

如果 with 语句或语句块中发生异常,会调用默认的异常处理器处理,但流还是会正常关闭。

4.copy()deepcopy() 的区别

copy() 对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。

deepcopy() 的时候会将复杂对象的每一层复制一个单独的个体出来。

5.简述 Python 的作用域以及 Python 搜索变量的顺序

作用域又可以被称为命名空间,指变量起作用的范围。Python 变量作用域可以分为四种,分别为局部作用域(local)嵌套作用域(enclosed)全局作用域(global)内置作用域(built-in)

四种作用域中变量的调用顺序采取“就近原则”,即为 LEGB

局部作用域指某个函数内部的范围。

嵌套一般是指一个函数嵌套另一个函数的情况,外层函数包含变量的作用范围称为嵌套作用域;也可以指一个类中包含多个函数时的情况。

全局作用域范围指的是在一个.py文件内部,在模块顶部声明即可成为全局作用域。
全局作用域中的变量在函数中一般是不可更改的,例如整数,字符等,但对于列表和字典来说可以更改。如想引用并改变全局变量,可使用global关键字。

内置作用域是python事先定义的内置模块,例如built-in 模块内的变量,程序启动之后由python虚拟机自动加载,在程序的任何地方都可以使用,例如print函数,随着解释器存在或消亡。

6.新式类和旧式类的区别,如何确保使用的是新式类

python的新式类是2.2版本引进来的,我们可以将之前的类叫做经典类或者旧式类。新式类的引入是为了统一类(class)和类型(type)。

新式类都从 object 继承,经典类不需要。新式类的MRO(method resolution order 基类搜索顺序)算法采用C3算法广度优先搜索,而旧式类的MRO算法是采用深度优先搜索

新式类相同父类只执行一次构造函数,经典类重复执行多次。

python3.x中取消了经典类。

7.简述 __new____init__ 的区别

__new__ 是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法。
__init__ 是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候。是一个实例方法。

也就是: __new__ 先被调用,__init__ 后被调用,__new__ 的返回值(实例)将传递给__init__ 方法的第一个参数,然后 __init__ 给这个实例设置一些参数。

8.Python 垃圾回收机制

引用计数器

在Python的C源码中有一个名为 refchain 的环状双向链表,Python程序中一旦创建对象都会把这个对象添加到 refchain 这个链表中。它保存着所有的对象。

refchain 中的所有对象内部都有一个 ob_refcnt 用来保存当前对象的引用计数器,当值被多次引用时候,不会在内存中重复创建数据,而是引用计数器 +1 。 当对象被销毁时候同时会让引用计数器 -1,如果引用计数器为 0,则将对象从 refchain 链表中摘除,同时在内存中进行销毁(暂不考虑缓存等特殊情况)。

标记清楚和分代回收

基于引用计数器进行垃圾回收非常方便和简单,但是存在循环引用的问题,导致无法正常的回收一些数据。

为了解决循环引用的问题,引入了标记清除技术,专门针对那些可能存在循环引用的对象进行特殊处理,可能存在循环应用的类型有:列表、元组、字典、集合、自定义类等那些能进行数据嵌套的类型。

标记清除:创建特殊链表专门用于保存 列表、元组、字典、集合、自定义类等对象,之后再去检查这个链表中的对象是否存在循环引用,如果存在则让双方的引用计数器均 - 1

分代回收:对标记清除中的链表进行优化,将那些可能存在循引用的对象拆分到3个链表,链表称为:0/1/2 三代,每代都可以存储对象和阈值,当达到阈值时,就会对相应的链表中的每个对象做一次扫描,除循环引用各自减1并且销毁引用计数器为 0 的对象。

特别注意:0代和1、2代的threshold和count表示的意义不同。其中源码中 0/1/2代默认的大小分别为700、10、10
0代,count表示0代链表中对象的数量,threshold表示0代链表对象个数阈值,超过则执行一次0代扫描检查。
1代,count表示0代链表扫描的次数,threshold表示0代链表扫描的次数阈值,超过则执行一次1代扫描检查。
2代,count表示1代链表扫描的次数,threshold表示1代链表扫描的次数阈值,超过则执行一2代扫描检查。

当0代大于阈值后,底层不是直接扫描0代,而是先判断2、1是否也超过了阈值。

  • 如果2、1代未达到阈值,则扫描0代,并让1代的 count + 1 。
  • 如果2代已达到阈值,则将2、1、0三个链表拼接起来进行全扫描,并将2、1、0代的count重置为0.
  • 如果1代已达到阈值,则讲1、0两个链表拼接起来进行扫描,并将所有1、0代的count重置为0.

对拼接起来的链表在进行扫描时,主要就是剔除循环引用和销毁垃圾,详细过程为:

  • 扫描链表,把每个对象的引用计数器拷贝一份并保存到 gc_refs 中,保护原引用计数器。
  • 再次扫描链表中的每个对象,并检查是否存在循环引用,如果存在则让各自的 gc_refs 减 1 。
  • 再次扫描链表,将 gc_refs0 的对象移动到 unreachable 链表中;不为 0 的对象直接升级到下一代链表中。
  • 处理unreachable链表中的对象的 析构函数 和 弱引用,不能被销毁的对象升级到下一代链表,能销毁的保留在此链表。
    • 析构函数,指的就是那些定义了 __del__ 方法的对象,需要执行之后再进行销毁处理。
    • 弱引用,
  • 最后将 unreachable 中的每个对象销毁并在refchain链表中移除(不考虑缓存机制)。

至此,垃圾回收的过程结束。

缓存机制

实际上反复的创建和销毁会使程序的执行效率变低。Python中引入了“缓存机制”机制。

例如:引用计数器为 0 时,不会真正销毁对象,而是将他放到一个名为 free_list 的链表中,之后会再创建对象时不会在重新开辟内存,而是在 free_list 中将之前的对象来并重置内部的值来使用。

float类型,维护的 free_list 链表最多可缓存100个float对象。
int类型,不是基于 free_list,而是维护一个 small_ints 链表保存常见数据(小数据池),小数据池范围:-5 <= value < 257。即:重复使用这个范围的整数时,不会重新开辟内存。
str类型,维护 unicode_latin1[256] 链表,内部将所有的ascii字符缓存起来,以后使用时就不再反复创建。
list类型,维护的 free_list 数组最多可缓存80个list对象。
tuple类型,维护一个 free_list 数组且数组容量20,数组中元素可以是链表且每个链表最多可以容纳2000个元组对象。元组的free_list数组在存储数据时,是按照元组可以容纳的个数为索引找到free_list数组中对应的链表,并添加到链表中。
dict类型,维护的 free_list 数组最多可缓存80个dict对象。

9.Python中的 @property 有什么作用?如何实现成员变量的只读属性?

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数。

Python内置的 @property 装饰器就是负责把一个方法变成属性调用的。@property 本身又创建了另一个装饰器 @score.setter,负责把一个 setter 方法变成属性赋值。只定义 getter 方法,不定义 setter 方法就是一个只读属性。

10.有用过 with statement 吗?它的好处是什么?具体如何实现的?

with语句的作用是通过某种方式简化异常处理,它是所谓的上下文管理器的一种,with 语句会在嵌套的代码执行之后,自动关闭文件。

11.Python 的数据结构有哪些?

Python中的内置数据结构(Built-in Data Structure):列表list、元组tuple、字典dict、集合set,涵盖的仅有部分重点。

list:

  • 列表中的每个元素都可变的,意味着可以对每个元素进行修改和删除;
  • 列表是有序的,每个元素的位置是确定的,可以用索引去访问每个元素;
  • 列表中的元素可以是Python中的任何对象;
  • 可以为任意对象就意味着元素可以是字符串、整数、元组、也可以是list等Python中的对象。

tuple

  • 用法与List类似,但Tuple一经初始化,就不能修改,只能对元素进行查询

dict:

  • 字典中的数据必须以键值对的形式出现,即k,v: key:必须是可哈希的值,value:任何值
  • 键不可重复,值可重复,键若重复字典中只会记该键对应的最后一个值
  • 字典中键(key)是不可变的,何为不可变对象,不能进行修改;而值(value)是可以修改的,可以是任何对象。
  • 在dict中是根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了

set

  • 集合更接近数学上集合的概念。集合中每个元素都是无序的、不重复的任意对象。
  • 可以通过集合去判断数据的从属关系,也可以通过集合把数据结构中重复的元素减掉。集合可做集合运算,可添加和删除元素。
  • 集合内数据无序,即无法使用索引和分片
  • 集合内部数据元素具有唯一性,可以用来排除重复数据
  • 集合内的数据:str,int,float,tuple,冰冻集合等,即内部只能放置可哈希数据

12.Python 中列表和元组的区别?元组是否真的不可变?

  1. 列表是动态数组,它们不可变且可以重设长度(改变其内部元素的个数)。
  2. 元组是静态数组,它们不可变,且其内部数据一旦创建便无法改变。
  3. 元组缓存于Python运行时环境,这意味着我们每次使用元组时无须访问内核去分配内存。

元组不可变是指当前变量存放的元素不可变,存放的元素可以是数字、字符、列表、元组、字典;如果你定义的元组最外层变量里面包含可变类型元素,那么这个元组是可变的。

13.什么是生成器和迭代器?它们的区别是什么?

迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter() 和 next()。字符串,列表或元组对象都可用于创建迭代器。把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__()


在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

调用一个生成器函数,返回的是一个迭代器对象。

14.什么是闭包?装饰器又是什么?装饰器的作用?

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可 以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。

在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。


装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。

装饰器这一语法体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大。

15.什么是类?什么是继承?

类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

继承: 所表达的是类之间的相关关系,这种关系使得子类可以继承父类特征和行为,使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。

你可能感兴趣的:(Python)