1.对不定长参数的理解
不定长参数有两种:*args和**kwargs;
*args:是不定长参数,用来将参数打包成tuple给函数体调用;
**kwargs:是关键字参数,打包关键字参数成dict给函数体调用;
在定义函数的时候不确定要传入的参数个数会有多少个的时候就可以使用不定长参数作为形参。
2.对缺省参数的理解
缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。
3.什么是lambda函数,它有什么好处?写一个匿名函数求两个数的和。
lambda函数是匿名函数;使用lambda函数能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤;
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数
1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用, 连名字都很随意的情况下;
2、匿名函数,一般用来给 filter, map 这样的函数式编程服务;
3、作为回调函数,传递给某些应用,比如消息处理
f = lambda x,y:x+y
print(f(2017,2018))
4.深拷贝和浅拷贝的区别
(1)python中的拷贝分为浅拷贝和深拷贝,若不特殊说明拷贝一般是浅拷贝
(2)浅拷贝是将对象顶层拷贝,拷贝了引用,并没有拷贝内容,原对象改变新对象也跟着改变
(3)深拷贝是对一个对象的所有层次的拷贝(递归),但是修改原来的值,新对象不受影响
(4)浅拷贝对于可变类型和不可变类型是不同的,对于可变类型只拷贝顶层,不可变类型依然是原来的对象
5.什么是可变、不可变?元组里添加字典,会改变id吗
(1)可变不可变指的是内存中的值是否可以被改变,不可变类型指的是对象所在内存块里面的值不可以改变,有数值、字符串、元组;可变类型则是可以改变,主要有列表、字典和集合;
(2)元组的顶层元素中包含可变类型,在可变类型中修改或添加字典id不会改变
6.生成器、迭代器的区别
(1)在Python中,一边循环一边计算的机制,称为生成器:generator,生成器是可以迭代对象,但是生成器可以通过send传值返回到前面;
(2)迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
(3)迭代器是一个可以记住遍历的位置的对象。
7.range()和xrange()函数的用法?
(1)range(start,stop,step)函数按照从start到stop每隔step生成一个数值,生成的是列表对象,一次性将所有数据都返回;
(2)xrange(start,stop,step)函数按照从start到stop每隔step生成一个数值,返回的是可迭代对象,每次调用返回其中的一个值
两者用法相同,不同的是 range 返回的结果是一个列表,而 xrange 的结果是一个生成器,前者是 直接开辟一块内存空间来保存列表,后者是边循环边使用,只有使用时才会开辟内存空间,所以当列表很长时,使用 xrange 性能要比 range 好。
8.对装饰器的理解
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
9.并行(parallel)和并发(concurrency)?线程是并发还是并行,进程是并发还是并行?
(1)线程是并发,进程是并行;进程之间相互独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源
(2)并行:同一时刻多个任务同时在运行
(3)并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情况
(4)实现并行的库有:multiprocessing
(5)实现并发的库有:threading
(6)程序需要执行较多的读写、请求和回复任务,需要大量的IO操作,IO密集型操作使用并发更好 ;CPU运算量大的程序程序,使用并行会更好
10.什么是线程安全,什么是互斥锁?
(1)每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
(2)同一个进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果
11.python的内存管理机制及调优手段
内存管理机制:引用计数、垃圾回收、内存池
(1)引用计数
引用计数是一种非常高效的内存管理手段, 当一个Python对象被引用时其引用计数增加1, 当其不再被一个变量引用时则计数减1. 当引用计数等于0时对象被删除.
(2)垃圾回收
1.引用计数
引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。 不过如果出现循环引用的话,引用计数机制就不再起有效的作用了
2.标记清除
如果两个对象的引用计数都为1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非0,但实际上有效的引用计数为0。所以先将循环引用摘掉,就会得出这两个对象的有效计数。
3.分代回收
从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。
(3)内存池
1.Python的内存机制呈现金字塔形状,-1,-2层主要由操作系统进行操作;
2.第0层是C中的malloc,free等内存分配和释放函数进行操作;
3.第1层和第2层是内存池,由Python的接口函数PyMem_Malloc函数实现,当对象小于256K时由该层直接分配内存;
4.第3层是最上层,也就是我们对Python对象的直接操作;
Python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
Python内部默认的小块内存与大块内存的分界点定在256个字节,当申请的内存小于256字节时,PyObject_Malloc会在内存池中申请内存;当申请的内存大于256字节时,PyObject_Malloc的行为将蜕化为malloc的行为。当然,通过修改Python源代码,我们可以改变这个默认值,从而改变Python的默认内存管理行为。
(4)调优手段
- 1.手动垃圾回收
- 2.调高垃圾回收阈值
- 3.避免循环引用(手动解循环引用和使用弱引用)
12.read、readline和readlines的区别
- read:读取整个文件
- readline:读取下一行,使用生成器方法
- readlines:读取整个文件到一个迭代器以供我们遍历
13.解释一下什么是闭包
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。
14.描述Python GIL的概念,以及它对Python多线程的影响?
GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行字节码。
线程释放GIL锁的情况:
在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL
Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
Python使用多进程是可以利用多核的CPU资源的。
多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
15.Python中有日志吗?怎么使用?
有日志;
python自带logging模块,调用logging.basicConfig()方法,配置需要的日志等级和相应的参数,python解释器会按照配置的参数生成相应的日志。
16.4G 内存怎么读取一个 5G 的数据?
方法一: 可以通过生成器,分多次读取,每次读取数量相对少的数据(比如 500MB)进行处理,处理结束后 在读取后面的 500MB 的数据。
方法二: 可以通过 linux 命令 split 切割成小文件,然后再对数据进行处理,此方法效率比较高。可以按照行数切割,可以按照文件大小切割。
17.在except中return后还会不会执行finally中的代码?怎么抛出自定义异常?
会继续处理 finally 中的代码;用 raise 方法可以抛出自定义异常。
18.常用的 Python 标准库都有哪些?
os 操作系统,time 时间,random 随机,pymysql 连接数据库,threading 线程,multiprocessing 进程,queue 队列。
第三方库: django 和 flask 也是第三方库,requests,virtualenv,selenium,scrapy,xadmin,celery, re,hashlib,md5。
常用的科学计算库(如 Numpy,Scipy,Pandas)。
19.__init__ 和__new__的区别?
- init 在对象创建后,对对象进行初始化。
- new 是在对象创建之前创建一个对象,并将该对象返回给 init。
20.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):从特定序列中随机取一个元素,这里的序列可以是字符串,列表, 元组等。
21.说明一下 os.path 和 sys.path 分别代表什么?
- os.path 主要是用于对系统路径文件的操作。
- sys.path 主要是对 Python 解释器的系统环境参数的操作(动态的改变 Python 解释器搜索路径)。
22.Python 中的 os 模块常见方法?
- os.remove()删除文件
- os.rename()重命名文件
- os.walk()生成目录树下的所有文件名
- os.chdir()改变目录
- os.mkdir/makedirs 创建目录/多层目录
- os.rmdir/removedirs 删除目录/多层目录
- os.listdir()列出指定目录的文件
- os.getcwd()取得当前工作目录
- os.chmod()改变目录权限
- os.path.basename()去掉目录路径,返回文件名
- os.path.dirname()去掉文件名,返回目录路径
- os.path.join()将分离的各部分组合成一个路径名
- os.path.split()返回(dirname(),basename())元组
- os.path.splitext()(返回 filename,extension)元组
- os.path.getatime\ctime\mtime 分别返回最近访问、创建、修改时间
- os.path.getsize()返回文件大小
- os.path.exists()是否存在
- os.path.isabs()是否为绝对路径
- os.path.isdir()是否为目录
- os.path.isfile()是否为文件
23.Python 的 sys 模块常用方法?
- sys.argv 命令行参数 List,第一个元素是程序本身路径
- sys.modules.keys() 返回所有已经导入的模块列表
- sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback 当前处理的异常详细信息
- sys.exit(n) 退出程序,正常退出时 exit(0)
- sys.hexversion 获取 Python 解释程序的版本值,16 进制格式如:0x020403F0
- sys.version 获取 Python 解释程序的版本信息
- sys.maxint 最大的 Int 值
- sys.maxunicode 最大的 Unicode 值
- sys.modules 返回系统导入的模块字段,key 是模块名,value 是模块
- sys.path 返回模块的搜索路径,初始化时使用 PYTHONPATH 环境变量的值
- sys.platform 返回操作系统平台名称
- sys.stdout 标准输出
- sys.stdin 标准输入
- sys.stderr 错误输出
- sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息
- sys.exec_prefix 返回平台独立的 python 文件安装的位置
- sys.byteorder 本地字节规则的指示器,big-endian 平台的值是'big',little-endian 平台的值是 'little'
- sys.copyright 记录 python 版权相关的东西
- sys.api_version 解释器的 C 的 API 版本
- sys.version_info 元组则提供一个更简单的方法来使你的程序具备 Python 版本要求功能
24.模块和包是什么?
在 Python 中,模块是搭建程序的一种方式。每一个 Python 代码文件都是一个模块,并可以引用 其他的模块,比如对象和属性。
一个包含许多 Python 代码的文件夹是一个包。一个包可以包含模块和子文件夹。
25. Python 是强语言类型还是弱语言类型?
Python 是强类型的动态脚本语言。
强类型:不允许不同类型相加。
动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。
脚本语言:一般也是解释型语言,运行代码只需要一个解释器,不需要编译。
26.谈一下什么是解释性语言,什么是编译性语言?
计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言, 计算机才能执行高级语言编写的程序。
解释性语言在运行程序的时候才会进行翻译。 编译型语言写的程序在执行之前,需要一个专门的编译过程,把程序编译成机器语言(可执行文件)。
27.什么是 Python?
- Python 是一种编程语言,它有对象、模块、线程、异常处理和自动内存管理,可以加入其他语言的对比。
- Python 是一种解释型语言,Python 在代码运行之前不需要解释。
- Python 是动态类型语言,在声明变量时,不需要说明变量的类型。
- Python 适合面向对象的编程,因为它支持通过组合与继承的方式定义类。
- 在 Python 语言中,函数是第一类对象。
- Python 代码编写快,但是运行速度比编译型语言通常要慢。
- Python 用途广泛,常被用走"胶水语言",可帮助其他语言和组件改善运行状况。
- 使用 Python,程序员可以专注于算法和数据结构的设计,而不用处理底层的细节。
28.你所遵循的代码规范是什么?请举例说明其要求?
PEP8 规范。
1. 变量
常量:大写加下划线 USER_CONSTANT。
私有变量 : 小写和一个前导下划线 _private_value。
Python 中不存在私有变量一说,若是遇到需要保护的变量,使用小写和一个前导下划线。但这只是程序员之间的一个约定,用于警告说明这是一个私有变量,外部类不要去访问它。但实际上,外部类还是可以访问到这个变量。
内置变量 : 小写,两个前导下划线和两个后置下划线 __class__
两个前导下划线会导致变量在解释期间被更名。这是为了避免内置变量和其他变量产生冲突。用户 定义的变量要严格避免这种风格。以免导致混乱。
2. 函数和方法
总体而言应该使用,小写和下划线。但有些比较老的库使用的是混合大小写,即首单词小写,之后 每个单词第一个字母大写,其余小写。但现在,小写和下划线已成为规范。
私有方法 :小写和一个前导下划线
这里和私有变量一样,并不是真正的私有访问权限。同时也应该注意一般函数不要使用两个前导下 划线(当遇到两个前导下划线时,Python 的名称改编特性将发挥作用)。
特殊方法 :小写和两个前导下划线,两个后置下划线
这种风格只应用于特殊函数,比如操作符重载等。
函数参数 : 小写和下划线,缺省值等号两边无空格
3. 类
类总是使用驼峰格式命名,即所有单词首字母大写其余字母小写。类名应该简明,精确,并足以从 中理解类所完成的工作。常见的一个方法是使用表示其类型或者特性的后缀,例如:
SQLEngine,MimeTypes 对于基类而言,可以使用一个 Base 或者 Abstract 前缀 BaseCookie, AbstractGroup
4. 模块和包
除特殊模块 __init__ 之外,模块名称都使用不带下划线的小写字母。
若是它们实现一个协议,那么通常使用 lib 为后缀,例如:
- import smtplib
- import os
- import sys
5. 关于参数
5.1 不要用断言来实现静态类型检测。断言可以用于检查参数,但不应仅仅是进行静态类型检测。 Python 是动态类型语言,静态类型检测违背了其设计思想。断言应该用于避免函数不被毫无意义的调用。
5.2 不要滥用 *args 和 **kwargs。*args 和 **kwargs 参数可能会破坏函数的健壮性。它们使签名变得模糊,而且代码常常开始在不应该的地方构建小的参数解析器。
6. 其他
6.1 使用 has 或 is 前缀命名布尔元素
- is_connect = True
- has_member = False
6.2 用复数形式命名序列
- members = ['user_1', 'user_2']
6.3 用显式名称命名字典
- person_address = {'user_1':'10 road WD', 'user_2' : '20 street huafu'}
6.4 避免通用名称
- 诸如 list, dict, sequence 或者 element 这样的名称应该避免。
6.5 避免现有名称
- 诸如 os, sys 这种系统已经存在的名称应该避免。
7. 一些数字
一行列数 : PEP 8 规定为 79 列。根据自己的情况,比如不要超过满屏时编辑器的显示列数。
一个函数 : 不要超过 30 行代码, 即可显示在一个屏幕类,可以不使用垂直游标即可看到整个函数。
一个类 : 不要超过 200 行代码,不要有超过 10 个方法。一个模块不要超过 500 行。
8. 验证脚本
可以安装一个 pep8 脚本用于验证你的代码风格是否符合 PEP8。
29.说一下字典和 json 的区别?
字典是一种数据结构,json 是一种数据的表现形式,字典的 key 值只要是能 hash 的就行,json 的 必须是字符串。
30.存入字典里的数据有没有先后排序?
存入的数据不会自动排序,可以使用 sort 函数对字典进行排序。
31.现有字典 d={‘a’:24,’g’:52,’l’:12,’k’:33}请按字典中的 value 值进行排序?
sorted(d.items(),key = lambda x:x[1])
32.如何理解 Python 中字符串中的\字符?
有三种不同的含义: 1、转义字符 2、路径名中用来连接路径名 3、编写太长代码手动软换行。
33.给定两个列表,怎么找出他们相同的元素和不同的元素?
list1 = [1,2,3]
list2 = [3,4,5]
set1 = set(list1)
set2 = set(list2)
print(set1&set2)
print(set1^set2)
- A、B 中相同元素:print(set(A)&set(B))
- A、B 中不同元素:print(set(A)^set(B))
34.Python 中类方法、类实例方法、静态方法有何区别?
类方法:是类对象的方法,在定义时需要在上方使用“@classmethod”进行装饰,形参为 cls, 表示类对象,类对象和实例对象都可调用;
类实例方法:是类实例化对象的方法,只有实例对象可以调用,形参为 self,指代对象本身;
静态方法:是一个任意函数,在其上方使用“@staticmethod”进行装饰,可以用对象直接调用, 静态方法实际上跟该类没有太大关系。
35.内存泄露是什么?如何避免?
指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的 消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
有 __del__() 函数的对象间的循环引用是导致内存泄漏的主凶。
不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。
通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。
36. Python 函数调用的时候参数的传递方式是值传递还是引用传递?
Python 的参数传递有:位置参数、默认参数、可变参数、关键字参数。
函数的传值到底是值传递还是引用传递,要分情况:
不可变参数用值传递:
像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变 不可变对象
可变参数是引用传递的:
比如像列表,字典这样的对象是通过引用传递、和 C 语言里面的用指针传递数组很相似,可变对象能在函数内部改变。
37.对缺省参数的理解 ?
缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时, 所传入的参数会替代默认参数。
*args 是不定长参数,他可以表示输入参数是不确定的,可以是任意多个。
**kwargs 是关键字参数,赋值的时候是以键 = 值的方式,参数是可以任意多对在定义函数的时候 不确定会有多少参数会传入时,就可以使用两个参数。
38.为什么函数名字可以当做参数用?
Python 中一切皆对象,函数名是函数在内存中的空间,也是一个对象。
39.Python 中 pass 语句的作用是什么?
在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进 行任何操作。
40. map 函数和 reduce 函数?
①从参数方面来讲:
- map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map 的第一个参数位置的函数)可以接收一个或多个参数。
- reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。
②从对传进去的数值作用来讲:
- map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
- reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用 (累积计算)。
41.递归函数停止的条件?
递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择 是继续调用自身,还是 return;返回终止递归。
终止的条件:
1. 判断递归的次数是否达到某一限定值
2. 判断运算的结果是否达到某个范围等,根据设计的目的来选择
42.回调函数,如何通信的?
回调函数是把函数的地址作为参数传递给另一个函数,将整个函数当作一个对象,赋值给调用的函 数。
43. Python 主要的内置数据类型都有哪些? print dir( ‘a ’) 的输出?
内建类型:布尔类型、数字、字符串、列表、元组、字典、集合;
输出字符串‘a’的内建方法;
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
44. 函数装饰器有什么作用?
装饰器本质上是一个 Python 函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。 比如:插入日志、性能测试、事务处理、缓存、 权限的校验等场景有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。
45.生成器、迭代器的区别?
迭代器是一个更抽象的概念,任何对象,如果它的类有 next 方法和 iter 方法返回自己本身,对于 string、list、 dict、tuple 等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器对象调用 iter()函数,iter() 是 python 的内置函数。iter()会返回一个定义了 next()方法的迭代器对象,它在容器中逐个访问容器内元素,next() 也是 python 的内置函数。在没有后续元素时,next()会抛出一个 StopIteration 异常。
生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
区别:生成器能做到迭代器能做的所有事,而且因为自动创建了 iter()和 next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。
46.Python 中 yield 的用法?
yield 就是保存当前程序执行状态。你用 for 循环的时候,每次取一个元素的时候就会计算一次。用 yield 的函数 叫 generator,和 iterator 一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。generator 每次计算需要上一次计算结果,所以用 yield,否则一 return,上次计算结果就没了。
47. Python 中的可变对象和不可变对象?
不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。
Python 中,数值类型(int 和 float)、字符串 str、元组 tuple 都是不可变类型。而列表 list、字典 dict、集合 set 是可变类型
48. Python 中 is 和==的区别?
is 判断的是 a 对象是否就是 b 对象,是通过 id 来判断的。
==判断的是 a 对象的值是否和 b 对象的值相等,是通过 value 来判断的。
49.Python 的魔法方法
魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现 (重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。 它们经常是两个下划线包围来命名的(比如 __init__,__lt__),Python 的魔法方法是非常强大的,所以了解其使用方法也变得 尤为重要!
__init__ 构造器,当一个实例被创建的时候初始化的方法。但是它并不是实例化调用的第一个方法。
__new__才是实例化对象调用的第一个方法,它只取下 cls 参数,并把其他参数传给 __init__。 __new__很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。
__call__ 允许一个类的实例像函数一样被调用 。
__getitem__ 定义获取容器中指定元素的行为,相当于 self[key] 。
__getattr__ 定义当用户试图访问一个不存在属性的时候的行为 。
__setattr__ 定义当一个属性被设置的时候的行为 。
__getattribute__ 定义当一个属性被访问的时候的行为 。
50.谈谈你对面向对象的理解?
面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个有很重要东西,叫做类。 面向对象有三大特性:封装、继承、多态。
51.Python 里 match 与 search 的区别?
match()函数只检测 RE 是不是在 string 的开始位置匹配,
search()会扫描整个 string 查找匹配;
也就是说 match()只有在 0 位置匹配成功的话才有返回, 如果不是开始位置匹配成功的话,match()就返回 none。
52.用 Python 匹配 HTML tag 的时候,<.*> 和 <.*?> 有什么区别?
<.*>是贪婪匹配,会从第一个“<”开始匹配,直到最后一个“>”中间所有的字符都会匹配到,中间可能会包含 “<>”。
<.*?>是非贪婪匹配,从第一个“<”开始往后,遇到第一个“>”结束匹配,这中间的字符串都会匹配到,但是 不会有“<>”。
53.进程
进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间 片、pid。
创建进程:
1.首先要导入 multiprocessing 中的 Process;
2.创建一个 Process 对象;
3.创建 Process 对象时,可以传递参数;
1.p = Process(target=XXX, args=(元组,) , kwargs={key:value})
2.target = XXX 指定的任务函数,不用加()
3.args=(元组,) , kwargs={key:value} 给任务函数传递的参数
4.使用 start()启动进程;
5.结束进程。
Process 语法结构:
- Process([group [, target [, name [, args [, kwargs]]]]])
- target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码
- args:给 target 指定的函数传递的参数,以元组的形式进行传递
- kwargs:给 target 指定的函数传递参数,以字典的形式进行传递
- name:给进程设定一个名字,可以省略
- group:指定进程组,大多数情况下用不到
Process 创建的实例对象的常用方法有:
- start():启动子进程实例(创建子进程)
- is_alive():判断进程子进程是否还在活着
- join(timeout):是否等待子进程执行结束,或者等待多少秒
- terminate():不管任务是否完成,立即终止子进程
Process 创建的实例对象的常用属性:
- name:当前进程的别名,默认为 Process-N,N 为从 1 开始递增的整数
- pid:当前进程的 pid(进程号)
54.谈谈你对多进程,多线程,以及协程的理解,项目是否用?
其实多线程,多进程,在实际开发中用到的很少,除非是那些对项 目性能要求特别高的,下载文件时,可以用多线程技术, 或者用多线程写爬虫,提升效率。
进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最 小单位,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大。
线程: 调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个 线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和 栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
55.什么是多线程竞争?
线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即: 数据几乎同步会被多个线程占用,造成数据混乱 ,即所谓的线程不安全
那么怎么解决多线程竞争问题?-- 锁。
锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。
锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
锁的致命问题:死锁。
56.解释一下什么是锁,有哪几种锁?
锁(Lock)是 Python 提供的对线程控制的对象。有互斥锁、可重入锁。
57.什么是死锁呢?
若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁, 互相干等着,程序无法执行下去,这就是死锁。
GIL 锁(有时候,面试官不问,你自己要主动说,增加 b 格,尽量别一问一答的尬聊,不然最后等 到的一句话就是:你还有什么想问的么?)
GIL 锁 全局解释器锁(只在 cpython 里才有)
作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以 cpython 里的多线程其实是伪 多线程!
所以 Python 里常常使用协程技术来代替多线程,协程是一种更轻量级的线程, 进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块 gevent 下切换是遇到了 耗时操作才会切换。
三者的关系:进程里有线程,线程里有协程。
58.说说下面几个概念:同步,异步,阻塞,非阻塞?
同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。
异步:多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!
阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。
同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。
59.Python 中的进程与线程的使用场景?
多进程适合在 CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。
多线程适合在 IO 密集型操作(读写数据操作较多的,比如爬虫)。
60.IO 密集型和 CPU 密集型区别?
- IO 密集型:系统运作,大部分的状况是 CPU 在等 I/O (硬盘/内存)的读/写。
- CPU 密集型:大部份时间用来做计算、逻辑判断等 CPU 动作的程序称之 CPU 密集型。
61.请描述方法重载与方法重写?
1)方法重载:是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。 重载是让类以统一的方式处理不同类型数据的一种手段。
2)方法重写:子类不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
62.请描述抽象类和接口类的区别和联系?
1)抽象类:规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。可以将抽象类理解为毛坯房,门窗、墙面的样式由你自己来定,所以抽象类与作为基类的普通类的区别在于约束性更强。
2)接口类:与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本区别在于用途:与不同个体间沟通的规则(方法),你要进宿舍需要有钥匙,这个钥匙就是你与宿舍的接口,你的同室也有这个接口,所以他也能进入宿舍,你用手机通话,那么手机就是你与他人 交流的接口。
3)区别和关联:
1. 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类中可以有非抽象方法。抽象类是声明方法的存在而不去实现它的类。
2. 接口可以继承,抽象类不行。
3.接口定义方法,没有实现的代码,而抽象类可以实现部分方法。
4.接口中基本数据类型为 static 而抽类象不是。
5.接口可以继承,抽象类不行。
6.可以在一个类中同时实现多个接口。
7.接口的使用方式通过 implements 关键字进行,抽象类则是通过继承 extends 关键字进行。
63.Python 关键字 yield 的用法?
yield 就是保存当前程序执行状态。你用 for 循环的时候,每次取一个元素的时候就会计算一次。用 yield 的函数 叫 generator,和 iterator 一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。generator 每次计算需要上一次计算结果,所以用 yield,否则一 return,上次计算结果就没了。
64.Python 线程和进程的区别?
1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资 源.
4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于 创建或撤消线程时的开销。
65.Python 里面如何拷贝一个对象?(赋值,浅拷贝,深拷贝的区别)
1)赋值(=),就是创建了对象的一个新的引用,修改其中任意一个变量都会影响到另一个。
2)浅拷贝:创建一个新的对象,但它包含的是对原始对象中包含项的引用(如果用引用的方式修改 其中一个对象,另外一个也会修改改变){1,完全切片方法;2,工厂函数,如 list();3,copy 模块 的 copy()函数}。
3)深拷贝:创建一个新的对象,并且递归的复制它所包含的对象(修改其中一个,另外一个不会改变){copy 模块的 copy.deepcopy()函数}。
66.Python 里面 match()和 search()的区别?
re 模块中 match(pattern,string[,flags]),检查 string 的开头是否与 pattern 匹配。
re 模块中 research(pattern,string[,flags]),在 string 搜索 pattern 的第一个匹配值。
67.OOP 编程三大特点是什么,多态应用的基础是什么?
1)封装 :就是将一个类的使用和实现分开,只保留部分接口和方法与外部联系。
2)继承:子类自动继承其父级类中的属性和方法,并可以添加新的属性和方法或者对部分属性和方 法进行重写。继承增加了代码的可重用性。
3)多态:多个子类中虽然都具有同一个方法,但是这些子类实例化的对象调用这些相同的方法后却 可以获得完全不同的结果,多态性增强了软件的灵活性。(多态的概念依赖于继承)。
68.Python 中静态函数、类函数、成员函数的区别?并写一个示例?
定义:
静态函数(@staticmethod):即静态方法,主要处理与这个类的逻辑关联, 如验证数据;
类函数(@classmethod):即类方法, 更关注于从类中调用方法, 而不是在实例中调用方法, 如构造 重载;
成员函数::实例的方法, 只能通过实例进行调用。
69.简述 Python 中 is 和= =的区别
Python 中的对象包含三要素:id、type、value。
其中 id 用来唯一标识一个对象,type 标识对象的类型,value 是对象的值。is 判断的是 a 对象是否 就是 b 对象,是通过 id 来判断的。==判断的是 a 对象的值是否和 b 对象的值相等,是通过 value 来判 断的。
70.Python 中 list、tuple、dict、set 有什么区别,主要应用在什么样的场景?
区别:
1、list、tuple 是有序列表;dict、set 是无序列表;
2、list 元素可变、tuple 元素不可变;
3、dict 和 set 的 key 值不可变,唯一性;
4、set 只有 key 没有 value;
5、set 的用途:去重、并集、交集等;
6、list、tuple:+、*、索引、切片、检查成员等;
7、dict 查询效率高,但是消耗内存多;list、tuple 查询效率低、但是消耗内存少
应用场景:
list,:简单的数据集合,可以使用索引;
tuple:把一些数据当做一个整体去使用,不能修改;
dict:使用键值和值进行关联的数据;
set:数据只出现一次,只关心数据是否出现, 不关心其位置;
71.描述 yield 使用场景?
生成器。
当有多个返回值时,用 return 全部一起返回了,需要单个逐一返回时可以用 yield。
72.Python 中 pass 语句的作用是什么?
在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进 行任何操作。
73.正则表达式贪婪与非贪婪模式的区别?
在形式上非贪婪模式有一个“?”作为该部分的结束标志。
在功能上贪婪模式是尽可能多的匹配当前正则表达式,可能会包含好几个满足正则表达式的字符串, 非贪婪模式,在满足所有正则表达式的情况下尽可能少的匹配当前正则表达式。
74.a = “abbbccc”,用正则匹配为 abccc,不管有多少 b,就出现一次?
思路:不管有多少个 b 替换成一个
re.sub(r'b+', 'b', a)
75. Python字典的底层原理和优缺点?
字典对象的核心是散列表。散列表是一个稀疏矩阵,数组的每个单元叫做bucket。每个bucket有两部分组成:一个是键对象的引用,一个是值对象的引用。所有bucket结构和大小一致,我们可以通过偏移量读取指定bucket。
优缺点:
1.字典在内存中开销巨大,典型的空间换时间
2.键查询速度很快
3.往字典里面添加新建可能导致扩容,导致散列表中键的次序发生变化。因此不要在变量字典的同时进行字典的修改
4.键必须可散列,如数字、元组、字符串;自定义对象需要满足支持hash
76.kafka是如何保证消息不被重复消费的?
一、kafka自带的消费机制
kafka有个offset的概念,当每个消息被写进去后,都有一个offset,代表他的序号,然后consumer消费该数据之后,隔一段时间,会把自己消费过的消息的offset提交一下,代表我已经消费过了。下次我要是重启,就会继续从上次消费到的offset来继续消费。
但是当我们直接kill进程了,再重启。这会导致consumer有些消息处理了,但是没来得及提交offset。等重启之后,少数消息就会再次消费一次。
其他MQ也会有这种重复消费的问题,那么针对这种问题,我们需要从业务角度,考虑它的幂等性。
二、通过保证消息队列消费的幂等性来保证
举个例子,当消费一条消息时就往数据库插入一条数据。如何保证重复消费也插入一条数据呢?
那么我们就需要从幂等性角度考虑了。幂等性,我通俗点说,就一个数据,或者一个请求,无论来多次,对应的数据都不会改变的,不能出错。
怎么保证消息队列消费的幂等性?
我们需要结合业务来思考,比如下面的例子:
1.比如某个数据要写库,你先根据主键查一下,如果数据有了,就别插入了
2.比如你是写redis,那没问题了,反正每次都是set,天然幂等性
3.对于消息,我们可以建个表(专门存储消息消费记录)
生产者,发送消息前判断库中是否有记录(有记录说明已发送),没有记录,先入库,状态为待消费,然后发送消息并把主键id带上。
消费者,接收消息,通过主键ID查询记录表,判断消息状态是否已消费。若没消费过,则处理消息,处理完后,更新消息记录的状态为已消费。
77.什么是线程安全?如何保证线程安全?
线程安全:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
如何保证呢:
1、使用线程安全的类;
2、使用synchronized同步代码块,或者用Lock锁;
3、多线程并发情况下,线程共享的变量改为方法局部级变量;