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

写在之前

大家好呀,我是帅蛋。

作为一名数据分析师,Python 是我吃饭的家伙什,算起来使用 Python 差不多已经有五六年的时间了。

Python 面试的时候,会涉及到很多的八股文,我准备在牛客网连载一个新的系列,叫【最强Python面试题】。

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

  • Python 基础面试题
  • Python 进阶
  • Python 后台开发
  • 爬虫
  • 机器学习

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

关于更新频率,每天我会更新 10 道题左右,总共会有差不多 200 道。

无论是准备面试还是自己学习,这份面试题绝对值得你去看,去学习。

大家可以关注我,再关注我,使劲关注我,不要错过每天的更新~

顺便提一句,我所有和面试相关的内容都会放在#帅蛋的面试空间# 中,大家可以关注下这个话题~

以下是正文

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

1、什么是 Python?

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

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

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

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

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

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

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

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

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

(1) 赋值

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

a = [1,2,"hello",['python', 'C++']]
b = a

在上述情况下,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 类:

class User1:
    def __init__(self, id, name, sex, status):
        self.id = id
        self.name = name
        self.sex = sex
        self.status = status

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

class User2:
    __slots__ = ['id', 'name', 'sex', 'status']
    def __init__(self, id, name, sex, status):
        self.id = id
        self.name = name
        self.sex = sex
        self.status = status

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

u1 = User1('01', 'rocky', '男', 1)
u2 = User2('02', 'leey', '男', 1)

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

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'id', 'name', 'sex', 'status']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'id', 'name', 'sex', 'status']

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

set(dir(u1)) - set(dir(u2))

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

{'__weakref__', '__dict__'}

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

下面我们来看一下 dict:

u1.__dict__

输出结果如下所示:

{'id': '01', 'name': 'rocky', 'sex': '男', 'status': 1}

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

u1.__dict__

现在输出的结果为:

{'id': '01', 'name': 'rocky', 'sex': '男', 'status': 1, 'level': 10}

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

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

import sys
sys.getsizeof(u1.__dict__)

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

112

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

u2.level = 10

显示的结果如下所示:

AttributeError: 'User2' object has no attribute 'level'

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),「级别」是一个逻辑上的概念,用来区分日志的重要程度。

import logging

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

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

WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

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

配置日志格式

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

import logging

logging.basicConfig(filename= 'test.log', level= logging.INFO)

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

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

INFO:root:info message
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

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

Logger:日志记录器,是应用程序中可以直接使用的接口。
Handler:日志处理器,用以表明将日志保存到什么地方以及保存多久。
Formatter:格式化,用以配置日志的输出格式。

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

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

import logging

logging.basicConfig(
   level= logging.DEBUG,
   format = '%(asctime)s : %(levelname)s : %(message)s',
   filename= "test.log"
)

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

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

2018-10-19 22:50:35,225 : DEBUG : debug message
2018-10-19 22:50:35,225 : INFO : info message
2018-10-19 22:50:35,225 : WARNING : warn message
2018-10-19 22:50:35,225 : ERROR : error message
2018-10-19 22:50:35,225 : CRITICAL : critical message

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

[loggers]
keys = root

[handlers]
keys = logfile

[formatters]
keys = generic

[logger_root]
handlers = logfile

[handler_logfile]
class = handlers.TimedRotatingFileHandler
args = ('test.log', 'midnight', 1, 10)
level = DEBUG
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s] %(message)s

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

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

import logging
import logging.config

logging.config.fileConfig('logging.conf')

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

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

2018-10-19 23:00:02,809 WARNI [root:8] warn message
2018-10-19 23:00:02,809 ERROR [root:9] error message
2018-10-19 23:00:02,809 CRITI [root:10] critical message

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

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

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

list---->tuple tuple(list)
tuple---->list list(tuple)

以上就是今天的内容,我是帅蛋,我们明天见~

❤️ 欢迎关注我,有问题,找帅蛋,我最看不得别人迷茫!

❤️ 如果你觉得有帮助,希望爱学习的你不要吝啬三连击哟[点赞 + 收藏 + 评论]~

还有小小公众号 【编程文青李狗蛋】,聊聊迷茫吹吹牛皮~

全部评论
来啦来啦,新的系列他来了!大家帮我顶顶顶~
5 回复 分享
发布于 2022-07-18 16:29
厉害,催更
1 回复 分享
发布于 2022-07-18 16:42
21天培养一个新习惯,每天给李狗蛋催更
点赞 回复 分享
发布于 2022-07-18 16:32
专门建立一个收藏夹
点赞 回复 分享
发布于 2022-07-18 16:34
文章写得很棒,期待大佬更多精彩博文
点赞 回复 分享
发布于 2022-07-18 16:40
催更,催更。
点赞 回复 分享
发布于 2022-07-18 17:00
专门建立一个收藏夹😂
点赞 回复 分享
发布于 2022-07-18 20:07
🤣关注了 随带催更
点赞 回复 分享
发布于 2022-07-18 20:36
关注+催更
点赞 回复 分享
发布于 2022-07-19 16:26
已关注,希望秋招顺利上岸
点赞 回复 分享
发布于 2022-07-19 20:13
这个地方是不是有点问题呀,没有看懂欸
点赞 回复 分享
发布于 2022-07-20 11:47
更多精彩内容,请查看#帅蛋的面试空间#
点赞 回复 分享
发布于 2022-07-21 12:18
老哥有md文档吗
点赞 回复 分享
发布于 2022-07-21 16:02
🤣加油,还有提升空间
点赞 回复 分享
发布于 2022-07-26 17:38
感谢 希望拿到秋招Python offer
点赞 回复 分享
发布于 2022-07-28 08:43
请问有PDF版嘛?想打印出来看
点赞 回复 分享
发布于 2022-08-05 19:34
我没找到关注在哪🤣
点赞 回复 分享
发布于 2022-08-08 23:58
加油!等着你更新机器学习!
点赞 回复 分享
发布于 2022-08-10 14:46

相关推荐

Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
11-14 16:13
已编辑
重庆科技大学 测试工程师
Amazarashi66:不进帖子我都知道🐮❤️网什么含金量
点赞 评论 收藏
分享
评论
180
783
分享
牛客网
牛客企业服务