写点什么

附答案 | 最强 Python 面试题之 Python 基础题 (1)

作者:KEY.L
  • 2022 年 7 月 21 日
  • 本文字数:4989 字

    阅读完需:约 16 分钟

这些面试题是我结合自己的经验整理的,主要就是下面这 5 个方面:

  • Python 基础面试题

  • Python 进阶

  • Python 后台开发

  • 爬虫

  • 机器学习

对每道面试题我都会附带详细的答案,有些我觉得重要的内容会详细讲解,虽然是面试八股文,我还是希望大家不是只“知其然”,更得“知其所以然”

以下是正文

今天是 Python 基础面试题第一弹,大家记得点赞收藏,一起加油

1、什么是 Python?

Python 是一种编程语言,它有对象、模块、线程、异常处理和自动内存管理,可以加入其他语言的对比。

Python 是一种解释型语言,Python 在代码运行之前不需要解释。

Python 是动态类型语言,在声明变量时,不需要说明变量的类型。

Python 适合面向对象的编程,因为它支持通过组合与继承的方式定义类。

在 Python 语言中,函数是第一类对象。

Python 代码编写快,但是运行速度比编译型语言通常要慢。

Python 用途广泛,常被用走"胶水语言",可帮助其他语言和组件改善运行状况。

使用 Python,程序员可以专注于算法和数据结构的设计,而不用处理底层的细节。

2、赋值、浅拷贝和深拷贝的区别?

(1) 赋值

在 Python 中,对象的赋值就是简单的对象引用,这点和 C++不同,如下所示:

复制代码

在上述情况下,a 和 b 是一样的,他们指向同一片内存,b 不过是 a 的别名,是引用。

我们可以使用 b is a 去判断,返回 True,表明他们地址相同,内容相同,也可以使用 id() 函数来查看两个列表的地址是否相同。

赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了 b 这个名字之外,没有其他的内存开销。修改了 a,也就影响了 b,同理,修改了 b,也就影响了 a。

(2) 浅拷贝

浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。

浅拷贝有三种形式:切片操作、工厂函数、copy 模块中的 copy 函数。

比如上述的列表 a,切片操作:b = a[:] 或者 b = [x for x in a];

工厂函数:b = list(a);

copy 函数:b = copy.copy(a);

浅拷贝产生的列表 b 不再是列表 a 了,使用 is 判断可以发现他们不是同一个对象,使用 id 查看,他们也不指向同一片内存空间。但是当我们使用 id(x) for x in a 和 id(x) for x in b 来查看 a 和 b 中元素的地址时,可以看到二者包含的元素的地址是相同的。

在这种情况下,列表 a 和 b 是不同的对象,修改列表 b 理论上不会影响到列表 a。

但是要注意的是,浅拷贝之所以称之为浅拷贝,是它仅仅只拷贝了一层,在列表 a 中有一个嵌套的 list,如果我们修改了它,情况就不一样了。

比如:a[3].append('java'),查看列表 b,会发现列表 b 也发生了变化,这是因为,我们修改了嵌套的 list,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发生变化,指向的都是用一个位置。

(3) 深拷贝

深拷贝只有一种形式,copy 模块中的 deepcopy() 函数。

深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。

同样的对列表 a,如果使用 b = copy.deepcopy(a),再修改列表 b 将不会影响到列表 a,即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。

(4) 注意点

对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。

如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。

3、init 和 new 的区别?

当我们使用「类名()」创建对象的时候,Python 解释器会帮我们做两件事情:第一件是为对象在内存分配空间,第二件是为对象进行初始化。「分配空间」是 new 方法,初始化是 init 方法。

new 方法在内部其实做了两件时期:第一件事是为「对象分配空间」,第二件事是「把对象的引用返回给 Python 解释器」。当 Python 的解释器拿到了对象的引用之后,就会把对象的引用传递给 init 的第一个参数 self,init 拿到对象的引用之后,就可以在方法的内部,针对对象来定义实例属性。

之所以要学习 new 方法,就是因为需要对分配空间的方法进行改造,改造的目的就是为了当使用「类名()」创建对象的时候,无论执行多少次,在内存中永远只会创造出一个对象的实例,这样就可以达到单例设计模式的目的。

4、Python 的变量、对象以及引用?

首先把结论抛出来:

  • 变量是到内存空间的一个指针,也就是拥有指向对象连接的空间;

  • 对象是一块内存,表示它们所代表的值;

  • 引用就是自动形成的从变量到对象的指针。

以下是具体解释:

在 Python 中使用变量的时候不需要提前声明变量及其类型,变量还是会正常工作。在 Python 中,这个是以一种非常流畅的方式完成,下面以 a = 1 为例我们来看一下它到底是个什么情况。

首先是怎么知道创建了变量:对于变量 a,或者说是变量名 a,当程序第一次给它赋值的时候就创建了它,其实真实情况是 Python 在代码运行之前就先去检测变量名,我们不去具体深究这些,你只需要当作是「最开始的赋值创建了变量」。

再者是怎么知道变量是什么类型:其实这个很多人都没有搞清楚,「类型」这个概念不是存在于变量中,而是存在于对象中。变量本身就是通用的,它只是恰巧在某个时间点上引用了当时的特定对象而已。就比如说在表达式中,我们用的那个变量会立马被它当时所引用的特定对象所替代。

上面这个是动态语言明显区别于静态语言的地方,其实对于刚开始来说,如果你适应将「变量」和「对象」分开,动态类型你也就可以很容易理解了。

我们还是以 a = 1 为例,其实从上面的讲述中,我们很容易的可以发现对于 a = 1 这个赋值语句 Python 是如何去执行它的:创建一个代表值 1 的对象 --> 创建一个变量 a --> 将变量 a 和对象 1 连接。 下面我用一个图来更清晰的表示一下:

由上图我们可以看出,变量 a 其实变成了对象 1 的一个引用。如果你学过指针的话,你就会发现在内部「变量其实就是到对象内存空间的一个指针」。

同样还是上图,我们还可以看出在 Python 中「引用」是从变量到对象的连接,它就是一种关系,在内存中以指针的形式实现。

5、创建百万级实例如何节省内存?

可以定义类的 slot 属性,用它来声明实例属性的列表,可以用来减少内存空间的目的。

具体解释:

首先,我们先定义一个普通的 User 类:

复制代码

然后再定义一个带 slot 的类:

复制代码

接下来创建两个类的实例:

复制代码

我们已经知道 u1 比 u2 使用的内存多,我们可以这样来想,一定是 u1 比 u2 多了某些属性,我们分别来看一下 u1 和 u2 的属性:

复制代码

复制代码

乍一看好像差别不大,我们下面具体来看一下差别在哪:

复制代码

通过做集合的差集,我们得到 u1 和 u2 在属性上的具体差别:

复制代码

在我们不使用弱引用的时候,weakref 并不占用多少内存,那最终这个锅就要 dict 来背了。

下面我们来看一下 dict:

复制代码

输出结果如下所示:

复制代码

输出一个字典,在它内部我们发现了刚刚在类里定义的属性,这个字典就是为了实例动态绑定属性的一个字典,我们怎么动态绑定呢?比如我们现在没有 u1.level 这个属性,那么我们可以为它动态绑定一个 level 属性,比如 u1.level = 10,然后我们再来考察这个字典:

复制代码

现在输出的结果为:

复制代码

这样看到 level 进入到这个字典中。

这样一个动态绑定属性的特性,其实是以牺牲内存为代价的,因为这个 dict 它本身是占用内存的,接下来我们来验证这件事情:

复制代码

我们用 sys 模块下的 getsizeof 方法,它可以得到一个对象使用的内存:

复制代码

我们可以看到这个字典占用了 112 的字节。反观 u2,它没有了 dict 这个属性,我们想给它添加一个属性,也是被拒绝的。

复制代码

显示的结果如下所示:

复制代码

6、Python 里面如何生成随机数?

在 Python 中用于生成随机数的模块是 random,在使用前需要 import. 如下例子可以酌情列举:

random.random():生成一个 0-1 之间的随机浮点数

random.uniform(a, b):生成[a,b]之间的浮点数

random.randint(a, b):生成[a,b]之间的整数

random.randrange(a, b, step):在指定的集合[a,b)中,以 step 为基数随机取一个数

random.choice(sequence):从特定序列中随机取一个元素,这里的序列可以是字符串,列表,元组等。

7、Python 是强语言类型还是弱语言类型?

Python 是强类型的动态脚本语言。

强类型:不允许不同类型相加。动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。脚本语言:一般也是解释型语言,运行代码只需要一个解释器,不需要编译。

8、谈一下什么是解释性语言,什么是编译性语言?

计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。

解释性语言在运行程序的时候才会进行翻译。

编译型语言写的程序在执行之前,需要一个专门的编译过程,把程序编译成机器语言(可执行文件)。

9、Python 中有日志吗?怎么使用?

Python 中有日志,Python 自带 logging 模块,调用 logging.basicConfig()方法,配置需要的日志等级和相应的参数,Python 解释器会按照配置的参数生成相应的日志。

补充知识:

Python 的标准日志模块

Python 标准库中提供了 logging 模块供我们使用。在最简单的使用中,默认情况下 logging 将日志打印到屏幕终端,我们可以直接导入 logging 模块,然后调用 debug,info,warn,error 和 critical 等函数来记录日志,默认日志的级别为 warning,级别比 warning 高的日志才会被显示(critical > error > warning > info > debug),「级别」是一个逻辑上的概念,用来区分日志的重要程度。

复制代码

上述代码的执行结果如下所示:

复制代码

我在上面说过,用 print 的话会产生大量的信息,从而很难从中找到真正有用的信息。而 logging 中将日志分成不同的级别以后,我们在大多数时间只保存级别比较高的日志信息,从而提高了日志的性能和分析速度,这样我们就可以很快速的从一个很大的日志文件里找到错误的信息。

配置日志格式

我们在用 logging 来记录日志之前,先来进行一些简单的配置:

复制代码

运行上面的代码以后,会在当前的目录下新建一个 test.log 的文件,这个文件中存储 info 以及 info 以上级别的日志记录。运行一次的结果如下所示:

复制代码

上面的例子中,我是用 basicConfig 对日志进行了简单的配置,其实我们还可以进行更为复杂些的配置,在此之前,我们先来了解一下 logging 中的几个概念:

复制代码

上述三者的关系是:一个 Logger 使用一个 Handler,一个 Handler 使用一个 Formatter。那么概念我们知道了,该如何去使用它们呢?我们的 logging 中有很多种方式来配置文件,简单的就用上面所说的 basicConfig,对于比较复杂的我们可以将日志的配置保存在一个配置文件中,然后在主程序中使用 fileConfig 读取配置文件。

基本的知识我们知道了,下面我们来做一个小的题目:日志文件保存所有 debug 及其以上级别的日志,每条日志中要有打印日志的时间,日志的级别和日志的内容。请先自己尝试着思考一下,如果你已经思考完毕请继续向下看:

复制代码

上述代码的一次运行结果如下:

复制代码

我刚刚在上面说过,对于比较复杂的我们可以将日志的配置保存在一个配置文件中,然后在主程序中使用 fileConfig 读取配置文件。下面我们就来看一个典型的日志配置文件(配置文件名为 logging.conf):

复制代码

在上述的日志配置文件中,首先我们在 [loggers] 中声明了一个叫做 root 的日志记录器(logger),在 [handlers] 中声明了一个叫 logfile 的日志处理器(handler),在 [formatters] 中声明了一个名为 generic 的格式化(formatter)。之后在 [logger_root] 中定义 root 这个日志处理器(logger) 所使用的日志处理器(handler) 是哪个,在 [handler_logfile] 中定义了日志处理器(handler) 输出日志的方式、日志文件的切换时间等。最后在 [formatter_generic] 中定义了日志的格式,包括日志的产生时间,级别、文件名以及行号等信息。

有了上述的配置文件以后,我们就可以在主代码中使用 logging.conf 模块的 fileConfig 函数加载日志配置:

复制代码

上述代码的运行一次的结果如下所示:

复制代码

10、Python 是如何进行类型转换的?

内建函数封装了各种转换函数,可以使用目标类型关键字强制类型转换,进制之间的转换可以用 int('str',base='n')将特定进制的字符串转换为十进制,再用相应的进制转换函数将十进制转换为目标进制。

可以使用内置函数直接转换的有:

复制代码

用户头像

KEY.L

关注

还未添加个人签名 2022.07.01 加入

还未添加个人简介

评论

发布
暂无评论
附答案 | 最强Python面试题之Python基础题(1)_7月月更_KEY.L_InfoQ写作社区