Python基础
1、列出 5 个常用 Python 标准库?
1 | import os # 操作系统接口 示例:os.system('ls') |
2. Python的内建数据类型有哪些?
1 | # 数值型--int、float、complex |
3. 简述 with 方法打开处理文件帮我我们做了什么?
• with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
• with语句即“上下文管理器”,在程序中用来表示代码执行过程中所处的前后环境 上下文管理器:含有enter和exit方法的对象就是上下文管理器。
• enter():在执行语句之前,首先执行该方法,通常返回一个实例对象,如果with语句有as目标,则将对象赋值给as目标。
• exit():执行语句结束后,自动调用exit()方法,用户释放资源,若此方法返回布尔值True,程序会忽略异常。
• 使用环境:文件读写、线程锁的自动释放等。
4. Python的可变和不可变数据类型?
不可变数据类型:即数据被创建之后,数据的值将不再发生改变,有数值、字符、元祖类型。
可变数据类型:数据别创建之后,数据的值可以发生变化,有列表、字典、集合类型。
5.Python 获取当前日期?
1 | import datetime |
6.统计字符串每个单词出现的次数
解法1
1 | # coding:utf-8 |
解法2
1 | sentence = "I can because i think i can" |
解法3
1 | from collections import Counter |
7. 用 python 删除文件和用 linux 命令删除文件方法
1 | # Python方法 |
8. 写一段自定义异常代码?
1 | # coding:utf-8 |
9. 举例说明异常模块中 try except else finally 的相关意义
1 | # coding:utf-8 |
Python语言特性
1.谈谈对 Python 的了解和其他语言的区别?
Python是一门语法简洁优美,功能强大无比,应用领域非常广泛,具有强大完备的第三方库的一门强类型的动态,可移植,可扩展,可嵌入的解释型编程语言。如果语言经常隐式地转换变量的类型,那这个语言就是弱类型语言,如果很少会这样做,那就是强类型语言。
Python很少会隐式地转换变量的类型,所以Python是强类型的语言。
- 解释性:解释型语言使用解释器将源码逐行解释成机器码并立即执行,不会进行整体性的编译和链接处理,相当于把编译语言中的编译和解释混合到一起同时完成。
- 优点:跨平台容易,只需提供特定平台的解释器;
- 缺点:运行效率较低,因为每次执行相当于都要进行一次编译。
编译型语言和解释型语言的区别
- 简洁优雅: 省略了各种大括号和分号,还有一些关键字,类型说明(自省);
- 面向对象:Python和C++、Java一样都是面向对象编程语言;
- 跨平台:简单搭建Python解释器可以在大部分平台运行。
Python和Java相比:
- Python是动态类型语言,而Java是静态类型语言.
- 动态类型语言不需要事先声明变量的类型,而且变量的数据类型可以被修改
- 静态类型语言需要事先声明,并且不能修改;
Python和C相比:
- 对于使用:Python的类库齐全并且使用简洁,很少代码实现的功能用C可能要很复杂
- 对于速度:Python的运行速度相较于C,绝对是很慢了。Python的CPython解释器是C语言编写的。
2. 简述解释型和编译型编程语言?
编译型语言和解释型语言的区别
简洁优雅: 省略了各种大括号和分号,还有一些关键字,类型说明(自省);
面向对象:Python和C++、Java一样都是面向对象编程语言;
跨平台:简单搭建Python解释器可以在大部分平台运行。
3.Python 的解释器种类以及相关特点?
CPython
当我们从Python官方网站下载并安装好Python 3.x后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。
CPython是使用最广的Python解释器。IPython
IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。
CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。PyPy
PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。
绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。Jython
Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。IronPython
IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。
4. 说说你知道的Python3 和 Python2 之间的区别?
编码
Python2 中字符的类型:
str: 已经编码后的字节序列
unicode: 编码前的文本字符Python3 中字符的类型:
str: 编码过的 unicode 文本字符
bytes: 编码前的字节序列我们可以认为字符串有两种状态,即文本状态和字节(二进制)状态。Python2 和 Python3 中的两种字符类型都分别对应这两种状态,然后相互之间进行编解码转化。编码就是将字符串转换成字节码,涉及到字符串的内部表示;解码就是将字节码转换为字符串,将比特位显示成字符。
在 Python2 中,str 和 unicode 都有 encode 和 decode 方法。但是不建议对 str 使用 encode,对 unicode 使用 decode, 这是 Python2 设计上的缺陷。Python3 则进行了优化,str 只有一个 encode 方法将字符串转化为一个字节码,而且 bytes 也只有一个 decode 方法将字节码转化为一个文本字符串。Python2中需要在文件头打上注释 # coding:utf-8 指定该程序使用的编码格式为UTF-8
Python2中的print是class
Python3中的print是函数
Python 2 的 print 声明已经被
print()
函数取代了,这意味着我们必须包装我们想打印在小括号中的对象。
所以我们输出格式为
1 | print("") # py3 |
input
Python3:input 解析输入为str字符型
Python2: input 解析输入为int型,raw_input解析输入为 str 类型
算术符
Python3中
/
表示真除,%
表示取余,//
结果取整;Python2中带上小数点/
表示真除,%
表示取余,//
结果取整
xrange
Python2中使用xrange()来创建一个迭代器对象,使用range()创建一个list数组;
Python3中使用range()创建迭代器对象,移除了xrange()方法。
5. Python3 和 Python2 中 int 和 long 区别?
Python 3里,只有一种整数类型 int,大多数情况下,它很像Python2里的长整型。
Python 2有为非浮点数准备的 int 和 long 类型。 int 类型的最大值不能超过 sys.maxint,而且这个最大值是平台相关的。
6. xrange 和 range 的区别?
Python2中使用xrange()
来创建一个迭代器对象,使用range()
创建一个list数组;
Python3中使用range()
创建迭代器对象,移除了xrange()
方法。
Python编码规范
7. 什么是 PEP8?
PEP是 Python Enhancement Proposal 的缩写,翻译过来就是 Python增强建议书
PEP8 ,简单说就是一种编码规范,是为了让代码“更好看”,更容易被阅读。
8. 了解 Python 之禅么?
1 | import this |
9.了解 docstring 么?
DocStrings
文档字符串是一个重要工具,用于解释文档程序,帮助你的程序文档更加简单易懂。
我们可以在函数体的第一行使用一对三个单引号 ‘’’ 或者一对三个双引号 “”” 来定义文档字符串。
你可以使用__doc__
(注意双下划线)调用函数中的文档字符串属性。
DocStrings文档字符串使用惯例:它的首行简述函数功能,第二行空行,第三行为函数的具体描述。
1 | # coding:utf-8 |
类的函数称为方法(method),模块里的函数称为函数(function);
每一个包,模块,类,函数,方法都应该包含文档,包括类的__init__
方法;
包的文档写在__init__.py
文件中;
文档有单行文档和多行文档;
单行文档:
- 不要重复函数的声明语句,例如:function(a, b) -> list;
- 指明做什么和返回什么,例如Do X and return a list.;
- 使用三引号,方便换行;
多行文档:
- 如果模块是一个脚本,也就是单文件程序,模块的文档应该写明脚本的使用方法;
- 模块的文档需要写明包含的类,异常,函数;
- 如果是包,在
__init__.py
中,写明包里面包含的模块,子包; - 如果是函数或类方法,应该写明函数或方法的作用,参数,返回,副作用,异常和调用的限 制等;
- 如果是类,写明类的行为,和实例参数,构造方法写在
__init__
中; - 使用三引号,而且两个三引号都应该单独成行。
10. 了解类型注解么?
首先,Python是一种动态语言,变量和函数的参数是不区分类型的。
Python解释器会在运行的时候动态判断变量和参数的类型,这样的好处是编写代码速度很快,很灵活,但是坏处也很明显,不好维护,可能代码写过一段时间重新看就很难理解了,因为那些变量、参数、函数返回值的类型,全都给忘记了。
在阅读别人的代码时,无法看出变量或参数的类型,这样对工作效率带来很大影响。
因此,在Python3中新添加了“类型注解”特性,可以给参数、函数返回值和变量的类型加上注解,该注解仅仅是注释而已,对代码运行不会产生任何影响,真正的变量类型还是由Python解释器决定,你所做的只是提高代码可读性,并不会像静态语言中变量类型定义后就无法修改(强转除外)。
上码:
1 | # coding:utf-8 |
以上代码可以看出,一般变量和函数参数注解格式为“参数:类型”,默认参数是在类型的后面加“=默认值”,函数的返回值注解格式为“-> 类型:”,函数的冒号在注解后方。
类型注解仅仅起到了注释作用,那我们应该如何知道它的正确性呢?
Python提供了一个工具方便我们测试类型注解的正确性
1 | pip install mypy |
使用方法:
1 | mypy demo.py |
若无错误则无输出,反之会输出如下:
1 | D:\code\web\flaskweb>mypy demo.py |
11. 例举你知道 Python 对象的命名规范,例如方法或者类等
变量命名:字母数字下划线,不能以数字开头
单下划线开头变量
单下划线开头的变量标明是一个受保护(protected)的变量,原则上不允许直接访问,但外部类还是可以访问到这个变量。
这只是程序员之间的一个约定,用于警告说明这是一个私有变量,外部类不要去访问它。双下划线开头变量
双下划线开头的,表示的是私有类型(private)的变量。 只能是允许这个类本身进行访问了, 连子类也不可以.
以双下划线开头,并且以双下划线结尾的,是内置变量.
内置变量是可以直接访问的,不是 private 变量,如__init__
,__import__
或是__file__
。★不要自己定义内置变量
xxx_,单下划线结尾的变量一般只是为了避免与 Python 关键字的命名冲突
USER_CONSTANT,大写加下划线,对于不会发生改变的全局变量,使用大写加下划线
函数和方法(类中叫做方法,模块中称作函数)命名:
总体而言应该使用小写和下划线,如:
create_user()
:
私有方法 : 小写和一个前导下划线,如def _create_user(self)
:
私有方法和私有变量一样,并不是真正的私有访问权限。
一般函数不要使用两个前导下划线(当遇到两个前导下划线时,Python 的名称改编特性将发挥作用)。
特殊方法 : 小写和两个前导下划线,两个后置下划线def __init__(self):
这种风格只应用于特殊函数,比如操作符重载等。
函数参数 : 小写和下划线,缺省值等号两边无空格def __init__(self, param=None):
不要滥用*args
和**kwargs
,可能会破坏函数的健壮性
类命名:
类总是使用驼峰格式命名,即所有单词首字母大写其余字母小写
如:Class CreateUser():
类名应该简明,精确,并足以从中理解类所完成的工作。
常见的一个方法是使用表示其类型或者特性的后缀,例如:SQLEngine
,MimeTypes
对于基类而言,可以使用一个Base
或者Abstract
前缀
包和模块:
小写字母、数字和下划线
12.Python 中的注释有几种?
单行注释以“#”开头
1 | # 单行注释 |
多行注释使用三个单引号或者双引号
1 | """ |
单引号和双引号混用,使用一种引号后其注释中的所有引号应该使用另一种
1 | """ |
13. 如何优雅的给一个函数加注释?
见第9条
14. 如何给变量加注释?
变量注释使用行内注释,根据pep8规范应该在代码后至少有两个空格,注释由#和一个空格开始:
1 | user_name = "Robin" # 用户姓名 |
有代码洁癖的同学可以保持注释的对齐,但一行文本不宜超过79个字符(PEP8规范)。
15. Python 代码缩进中是否支持 Tab 键和空格混用。
Python是一门用空格缩进来区分代码层次的语言,其实Python并没有强制要求你用Tab缩进或者用空格缩进,甚至空格按几个都没有强制要求(但在PEP8中建议了使用4个空格作为缩进)
但是却绝对!绝对不能混用Tab和空格 **!
python中不提倡使用tab缩进**
不同编辑器对于TAB的解释是不同的,有的编辑器tab是4个字符宽,有的8个字符宽。
如果有的地方用TAB,有的地方用空格,在不同的地方,原本对齐的代码就可能会不对齐。
16. 是否可以在一句 import 中导入多个库?
可以在一巨import中打入多个库,但是一般我们不这样做,原因有以下几点
- 更易于阅读
- 更易于搜索
- 更易于编辑
- 多行import更易于维护
导入多个模块语句最好以下面方式书写,使用空行将其分割
- python 标准库模块
- python 第三方模块
- 自定义模块
有的程序员喜欢这样导入模块1
from socker import *
这样写的好处就是不需要我们一个个列出socker
需要的方法,但是这样引入的弊端如下
• 占用更多的内存空间,不必要的方法或类可能会进行初始化
• 代码可读性差,模块内部突然冒出一个没有见过也没有归属的方法,很是头疼
17. 在给 Py 文件命名的时候需要注意什么?
在为Python文件命名时,我们需要注意
- 不能与Python中的关键字命名;
- 不能以标准库或常用第三方库命名,除非你想重写这些库;
- 不能用除字母数字下换线之外的字符命名,注意不要使用中文命名任何路径和可执行文件;
- 数字不能作为开头。
18. 例举几个规范 Python 代码风格的工具
Pylint
安装:pip install pylint
使用:pylint demo.py
Black
安装:pip install black
使用:black demo.py
Autopep8
安装:pip install autopep8
使用:autopep8 –in-place –aggressive demo.py
flake8
安装:pip install flake8
使用:flake8 demo.py
Python数据类型-字符串
19. 列举 Python 中的基本数据类型?
1 | # 数值型--int、float、complex |
20. 如何区别可变数据类型和不可变数据类型
可变数据类型:在内存id不变的情况下,数据的值可以改变
不可变数据类型:数据的值不能发生改变,如果值发生改变,那么内存id也会改变,这样就不是同一个数据了。
1 | demo_list = [1, 2, 3] |
21. 将”hello world”转换为首字母大写”Hello World”
1 | #!/usr/bin/python3 |
22. 如何检测字符串中只含有数字?
isdigit()
方法检测字符串是否只由数字组成。
1 | #!/usr/bin/python3 |
23. 将字符串”ilovechina”进行反转
1 | #!/usr/bin/python3 |
25. 有一个字符串开头和末尾都有空格,比如“ adabdw ”,要求写一个函数把这个字符串的前后空格都去掉。
1 | #!/usr/bin/python3 |
26. 获取字符串”123456“最后的两个字符。
1 | #!/usr/bin/python3 |
27. 一个编码为 GBK 的字符串 S,要将其转成 UTF-8 编码的字符串,应如何操作?
1 | #!/usr/bin/python3 |
28. 正则匹配
s=”info:xiaoZhang 33 shandong”,用正则切分字符串输出[‘info’, ‘xiaoZhang’, ‘33’, ‘shandong’]
1
2
3
4
5
6
7
8
9#!/usr/bin/python3
import re
demo_str = "info:xiaoZhang 33 shandong"
# compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象
# 由题可知需要去除出字母数字之外的字符,故使用"\W"做匹配
pattern = re.compile(r'\W')
# re.split()方法按照能够匹配的子串将字符串分割后返回列表
print(pattern.split(demo_str))a = “你好 中国 “,去除多余空格只留一个空格。
1
2
3
4
5#!/usr/bin/python3
import re
a = "你好 中国 "
print(a.rstrip())
29. 怎样将字符串转换为小写
1 | #!/usr/bin/python3 |
(2)单引号、双引号、三引号的区别?
在Python中我们都知道单引号和双引号都可以用来表示一个字符串,比如
1 | str1 = 'python' |
单引号需要加 ‘' 来让编译器判断目前是转义字符,而双引号方便了很多。
反之,如果字符串中有双引号,为了避免使用转义符,可以使用单引号来定义这个字符串。
1 | str5 = 'The teacher said: "Practice makes perfect" is a very famous proverb.' |
三个单引号和三个双引号一般用于多行注释,也可以用来表示字符串:输出多行文本
1 | str1 = """List of name: |
30. 已知 AList = [1,2,3,1,2],对 AList 列表元素去重,写出具体过程。
1 | #!/usr/bin/python3 |
31. 如何实现 “1,2,3” 变成 [“1”,”2”,”3”]
1 | #!/usr/bin/python3 |
32. 给定两个 list,A 和 B,找出相同元素和不同元素
1 | #!/usr/bin/python3 |
33. [[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]
1 | #!/usr/bin/python3 |
34. 合并列表[1,5,7,9]和[2,2,6,8]
1 | #!/usr/bin/python3 |
35. 如何打乱一个列表的元素?
1 | #!/usr/bin/python3 |
Python数据类型-字典
36. 字典操作中 del 和 pop 有什么区别
1 | #!/usr/bin/python3 |
37. 按照字典的内的年龄排序
1 | #!/usr/bin/python3 |
38. 请合并下面两个字典 a = {“A”:1,”B”:2},b = {“C”:3,”D”:4}
1 | #!/usr/bin/python3 |
39. 如何使用生成式的方式生成一个字典,写一段功能代码。
1 | #!/usr/bin/python3 |
40. 如何把元组(“a”,”b”)和元组(1,2),变为字典{“a”:1,”b”:2}
1 | #!/usr/bin/python3 |
Python综合
41. Python 常用的数据结构的类型及其特性?
字典中的键是不可变类型,可变类型list和dict不能作为字典键
一个对象能不能作为字典的key,就取决于其有没有__hash__
方法
42. 如何交换字典 {“A”:1,”B”:2}的键和值?
1 | #!/usr/bin/python3 |
43. Python 里面如何实现 tuple 和 list 的转换?
1 | #!/usr/bin/python3 |
44. 我们知道对于列表可以使用切片操作进行部分元素的选择,那么如何对生成器类型的对象实现相同的功能呢?
1 | #!/usr/bin/python3 |
45. 请将[i for i in range(3)]改成生成器
1 | (i for i in range(3)) # 方括号改为尖括号即可 |
46. a=”hello”和 b=”你好”编码成 bytes 类型
1 | #!/usr/bin/python3 |
47. 下面的代码输出结果是什么?
1 | a = (1, 2, 3, [4, 5, 6, 7], 8) |
元祖是不可变类型,因此不能修改元祖内的值
a[2]=2 使得元祖中对索引值为“2”的元素进行了修改,内存id发生了变化
48. 下面的代码输出的结果是什么?
1 | a = (1, 2, 3, [4, 5, 6, 7], 8) |
列表是可变数据类型,数据的值可以修改的
• 这里只是修改了元祖子对象的值,而不是修改了元祖的值
• 修改可变类型的值不会改变内存id,因此元祖的引用还是没有发生变化
• 可以这么理解,只要不修改元祖中值的内存id,那么就可以进行“修改元祖”操作
• 面试中,面试官可能会问到:元祖是否可以被修改?
• 答:元祖是不可变数据类型,因此不能修改元祖中的值,但是如果元组中有可变数据类型,那么可以修改可变数据类型中的值,修改可变数据类型的值并不会使其内存id发生变化,所以元祖中元素中的内存id也没有改变,因此就做到了“修改元祖”操作。
Python实际操作题
49. Python 交换两个变量的值
1 | a, b = b, a |
50. 在读文件操作的时候会使用 read、readline 或者 readlines,简述它们各自的作用
read
:读取整个文件。readline
:读取下一行,使用生成器方法。readlines
:读取整个文件到一个迭代器以供我们遍历
51. json 序列化时,可以处理的数据类型有哪些?如何定制支持 datetime 类型?
json序列化时,可以处理列表、字典、字符、数值、布尔和None
定制datetime类型
1 | #!/usr/bin/python3 |
52. json 序列化时,默认遇到中文会转换成 unicode,如果想要保留中文怎么办?
1 | #!/usr/bin/python3 |
53. 有两个磁盘文件 A 和 B,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件 C 中。
1 | #!/usr/bin/python3 |
54. 如果当前的日期为 20190530,要求写一个函数输出 N 天后的日期,(比如 N 为 2,则输出 20190601)。
1 | #!/usr/bin/python3 |
55. 写一个函数,接收整数参数 n,返回一个函数,函数的功能是把函数的参数和 n 相乘并把结果返回。
• 闭包是一种特殊的函数,这种函数由多个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包;
• 闭包函数必须返回一个函数对象;
• 闭包函数返回的那个函数必须引用外部变量;
• 闭包可以保存运行环境,即在闭包内的变量是不能被轻易修改的;
• 闭包的好处:提高代码的可复用性。
1 | #!/usr/bin/python3 |
56. 下面代码会存在什么问题,如何改进?
1 | def strappend(num): # 函数作用、参数意义不明,需要加注释 |
57. 一行代码输出 1-100 之间的所有偶数。
1 | print([num for num in range(1,101) if num % 2 == 0]) |
58. with 语句的作用,写一段代码?
with
语句:“上下文管理器”,用于资源访问的场合,作用是资源释放和异常处理(详细内容在第3条问题汇总)
1 | import threading |
59. python 字典和 json 字符串相互转化方法
1 | #!/usr/bin/python3 |
60. 请写一个 Python 逻辑,计算一个文件中的大写字母数量
1 | #!/usr/bin/python3 |
61. 请写一段 Python连接 Mongo 数据库,然后的查询代码。
1 | #!/usr/bin/python3 |
62. 说一说 Redis 的基本类型。
1 | string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 |
63. 请写一段 Pyth61. 请写一段 Python连接 Mongo 数据库,然后的查询代码。
1 | #!/usr/bin/python3 |
62. 说一说 Redis 的基本类型。
string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
63. 请写一段 Python连接 Redis 数据库的代码。
1 | #!/usr/bin/python3 |
64. 请写一段 Python 连接 MySQL 数据库的代码。
1 | #!/usr/bin/python3 |
65. 了解 Redis 的事务么?
事务提供了一种”将多个命令打包,一次性提交并按顺序执行”的机制,提交后在事务执行中不会中断。只有在执行完所有命令后才会继续执行来自其他客户的消息。
Redis通过multi
,exec
,discard
,watch
实现事务功能。
- multi:开始事务
- exec:提交事务并执行
- discard:取消事务
- watch:事务开始之前监视任意数量的键
- unwatch:取消WATCH命令对多有key的监控,所有监控锁将会被取消。
关于ACID:
- 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
- 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
- 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制。
66. 了解数据库的三范式么?
属性不可分割
:字段不能再分割,如“年级班级”可以分割为“年级”和“班级”两个字段唯一主键
:一张表中需要有一个唯一主键用来区分每行数据,如“学生学号 ”消除冗余和传递依赖
:不同表中不能存在重复的字段数据,如“学生”表中的“院系”字段和“班级”表中“院系”字段,我们可以关联两张表的字段而无需在“学生”表中再加一个“院系”。
67. 了解分布式锁么?
在开发中可能会用到多线程和多进程,如果不同线程或者不同进程抢占同一个资源,对其行读写操作可能会导致数据不一致,导致数据不是在我们预想的情况下改变。这里为了保证线程或者进程安全,python中引入了线程锁和进程锁,保证了数据的一致性和完整性。
而为了保证分布式系统的数据安全,可以使用使用分布式锁来解决这一问题(秒杀场景)。分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。分布式锁的实现有很多种,常见的有redis、zookeeper和数据库mysql等。
68. 用 Python 实现一个 Reids 的分布式锁的功能。
1 | #!/usr/bin/python3 |
69. 写一段 Python 使用 Mongo 数据库创建索引的代码。
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
1 | #!/usr/bin/python3 |
Python高级特性
70. 函数装饰器有什么作用?请列举说明?
装饰器主要是在不修改代码前提下进行功能的扩展,满足面向对象的“开闭原则”。
应用场景:
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
- 事务处理
71. Python 垃圾回收机制?
整数
小整数:Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象。单个字母同样也是如此。
大整数:每一个大整数的创建均在内存中会分配一个内存空间,所以大整数的内存空间是需要被回收的。
引用计数为主,标记清除和分代回收为辅:
引用计数:
python里每一个东西都是对象,它们的核心就是一个结构体:
PyObject
PyObject
是每个对象必有的内容,其中ob_refcnt
就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt
就会增加,当引用它的对象被删除,它的ob_refcnt
就会减少。
当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
- 简单
- 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用
标记清除
标记清除Mark—Sweep算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:
第一阶段是标记阶段,GC会把所有的『活动对象』打上标记
第二阶段是把那些没有标记的对象『非活动对象』进行回收。从GCROOT出发,标记所有的可达对象,不可达的就清除掉。
标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance
等,因为对于字符串、数值对象是不可能造成循环引用问题。
分代回收
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
每个分代集合中索引值越大的代表存活时间越长,越不容易被回收。
分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象
72. 魔法函数 call怎么使用?
__call__
允许一个类的实例像函数一样被调用
1 | #!/usr/bin/python3 |
73. 如何判断一个对象是函数还是方法?
在类外声明def为函数
类中声明def:使用类调用为函数,使用实例化对象调用为方法
可以使用isinstance()
判断
1 | #!/usr/bin/python3 |
74. @classmethod 和@staticmethod 用法和区别
@classmethod
是类方法:访问和修改类属性,进行类相关的操作,通过类或示例对象调用,需要传递cls类对象为参数;@staticmethod
是静态方法:不访问类属性和实例属性,通过类或实例调用,相当于一个普通函数。
75. Python 中的接口如何实现?
类定义接口、函数定义接口
76. Python 中的反射了解么?
计算机中的反射,是在运行的时候来自我检查,并对内部成员进行操作。就是说这个变量的类型可以动态的改变,在运行的时候确定它的作用。
在Python中,能够通过一个对象,找出其type
、class
、attribute
或method
的能力,称为反射或自省。
具有反射能力的函数有type()
,isinstance()
,callable()
,dir()
,getattr()
等
Python的反射是一个很强大的功能,个人认为每个Python程序员都应该掌握这一用法
77. metaclass 作用?以及应用场景?
metaclass
,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
连接起来就是:先定义metaclass
,就可以创建类,最后创建实例。
所以,metaclass
允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况.
78. hasattr() getattr() setattr()的用法
这三种方法用于为对象属性的存在判断、获取和添加修改,简言之就是对象属性的“增、改、查”。
hasattr(object, name)
:判断对象是否存在name
属性
1 | class A(): |
getattr(object, name[, default])
:获取object对象name属性的值,若没有name属性,则返回default值
1 | class A(): |
setattr(object, name, value)
给object对象的name属性赋值value,如果对象原本存在给定的属性name,则setattr会更改属性的值为给定的balue,如果不存在属性name,会在对象中创建属性并赋值value
1 | class A(): |
79. 请列举你知道的 Python 的魔法方法及用途。
在Python中,所有以 “
_ _
“ 双下划包起来的方法称为“魔法方法”
魔法方法Python解释器自动给出默认的,因此除非需要改变其内部功能,其它时刻刻使用默认魔法方法
最常用三个:"__init__"
、"__new__"
、"__del__"
__new__
是用来创建类并返回这个类的实例,__init__
将传入的参数来初始化该实例,以及初始化示例属性,与__new__
共同构成了“构造函数”__del__
将实例化后的对象销毁,即为析构函数
类调用:call
__call__
允许一个类像函数一样被调用
属性访问:getattr、setattr、delattr
__getattr__
访问对象不存在的属性时,调用该方法,用于定义访问行为__setattr__
设置对象属性时调用__delattr_
_删除对象属性时调用
上下文管理器:__enter__
和__exit__
这两个方法请看上面第3题。
迭代器方法:__iter__
和`next__`
__iter__
:返回一个容器迭代器,很多情况下会返回迭代器,尤其是当内置的iter()方法被调用的时候,以及当使用for x in container:方式循环的时候。迭代器是它们本身的对象,它们必须定义返回self的iter方法。__next__
:返回迭代器的下一个元素
80. 如何知道一个 Python 对象的类型?
1 | demo_obj = range(1,11) # 创建一个未知类型的对象 |
81. Python 的传参是传值还是传址?
结论先行:
Python对可变对象(字典或列表)传址,
对不可变对象(数字、字符或元祖)传值。
1 | #!/usr/bin/python3 |
82. Python 中的元类(metaclass)使用举例
与77题重复
83. 简述 any()和 all()方法
any()
判断一个tuple或者list是否全为空,全空False, 不全为空返回True,空列表和空元祖为False;all()
判断一个tuple或者list是否全为非空,有一空则False, 全不空True,空列表和空元祖为True。
84. filter 方法求出列表所有奇数并构造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1 | list(filter(lambda x: x % 2 == 1, (i for i in a))) # py3需要使用list()转为list |
86. 什么是猴子补丁?
猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。
为什么叫猴子补丁?
- 这个词原来为Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。
- 还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch。
使用协程时,通常在模块头部加入:gevent.monkey.patch_all()
,用于将标准库中的thread/socket等给替换掉,这样我们在后面使用socket的时候可以跟平常一样使用,无需修改任何代码,但是它变成非阻塞的了。
总结:猴子补丁就是程序功能的追加或者变更。
网上还有一个例子:
之前做的一个游戏服务器,很多地方用的import json
,后来发现ujson
比自带json
快了N倍,于是问题来了,难道几十个文件要一个个把import json
改成import ujson as json
吗?
其实只需要在进程startup的地方monkey patch就行了.是影响整个进程空间的.
同一进程空间中一个module只会被运行一次.
1 | import json |
86. 在 Python 中是如何管理内存的?
Python内存池:内存池的概念就是预先在内存中申请一定数量的,大小相等 的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。
python中的内存管理机制——Pymalloc:python中的内存管理机制都有两套实现,一套是针对小对象,就是大小小于256bits时,pymalloc会在内存池中申请内存空间;当大于256bits,则会直接执行new/malloc的行为来申请内存空间。
内存释放参考垃圾回收
87. 当退出 Python 时是否释放所有内存分配?
循环引用其它对象或引用自全局命名空间的对象的模块,在 Python 退出时并非完全释放。
这里我有疑惑,暂时没有找到合理的解释:垃圾回收中的标记清除不是可以解决循环引用的问题吗?
Python正则表达式
88.使用正则表达式匹配出<h1>百度一下,你就知道中的地址 a=”张明 98 分”,用 re.sub,将 98 替换为 100
使用正则表达式匹配出<html><h1><div>a="张明 98 分"</div></html>' 中的地址 a="张明 98 分
,用re.sub
,将 98 替换为 100
1 | #!/usr/bin/python3 |
89. 正则表达式匹配中(.)和(.?)匹配区别?
- 什么是贪婪匹配:贪婪匹配在匹配字符串时总是尝试匹配尽可能多的字符。
- 什么是非贪婪匹配:与贪婪匹配相反,非贪婪匹配在匹配字符串时总是尝试匹配尽可能少的字符。
- Python里数量词默认是贪婪模式的,在”*”,”?”,”+”,”{m,n}”后面加上?,可使贪婪模式变成非贪婪模式。
1 | #!/usr/bin/python3 |
90.写一段匹配邮箱的正则表达式
1 | #!/usr/bin/python3 |
Python其他内容
91. 解释一下Python 中 pass 语句的作用?
Python中的
pass
是空语句,是为了保持程序结构的完整性;pass
不做任何事情,一般用做占位语句;
一般在搭建程序框架的时候或在判断语句中使用。
92. 简述你对 input()函数的理解
Python3.x中input()函数接受一个标准输入数据,返回为字符串类型。
Python2.x中input()相等于eval(raw_input(prompt)),用来获取控制台的输入。
raw_input()将所有输入作为字符串看待,返回字符串类型。而input()在对待纯数字输入时具有自己的特性,它返回所输入的数字的类型(int,float)。
93. python 中的 is 和==
is
是身份运算符,判断两个对象的内存id是否相等==
是比较运算符,判断两个对象的值是否相等
进行值比较的时候使用==,判断是否是同一对象的时候使用is
94. Python 中的作用域(变量的作用域)
L (Local) 局部作用域
E (Enclosing) 闭包函数外的函数中
G (Global) 全局作用域
B (Built-in) 内建作用域
以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
95. 三元运算写法和应用场景?
三元运算符就是在赋值变量的时候,可以直接加判断,然后赋值格式
条件为真时的结果 if 判段的条件 else 条件为假时的结果
先定义变量:
a = 1
b = 2
1 | # 第一种写法: |
其中我们比较常见的是第一种。
第二三种是挺简洁的,但是写在项目里怕是接手的同事要抓狂了。
96. 了解 enumerate 么?
enumerate()
函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
以下是 enumerate()
方法的语法:enumerate(sequence, [start=0])
参数
sequence
– 一个序列、迭代器或其他支持迭代对象。start
– 下标起始位置。
1 | #!/usr/bin/python3 |
97. 列举 5 个 Python 中的标准模块
请看第一题
98. 如何在函数中设置一个全局变量
使用global
99. pathlib 的用法举例
pathlib
模块提供了一组面向对象的类,这些类可代表各种操作系统上的路径,程序可通过这些类操作路径。
1 | #!/usr/bin/python3 |
100. Python 中的异常处理,写一个简单的应用场景
第9题
101. Python 中递归的最大次数,那如何突破呢?
最大次数为1000次,如何突破请看:
https://code.activestate.com/recipes/474088/
102. 什么是面向对象的 mro
MRO:Method Resolution Order(方法解析顺序)
MRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。
MRO 是在Python多继承和钻石继承问题上的核心内容,它规定了如何,什么时候,怎么样去 调用父类的方法
1 | # 输出类的解析继承关系顺序:类名.__mro__ |
103. isinstance 作用以及应用场景?
isinstance
:判断对象是否是一个已知的类型
isinstance(object, classinfo)
object
– 实例对象。classinfo
– 可以是直接或间接类名、基本类型或者由它们组成的元组。
使用场景举例:
判断对象的数据类型,如参数和返回值判断,根据不同的数据类型
判断类的继承关系,isinstance可以用作判断是否继承了某个父类
type和isinstance
type
只输出当前类名,不管继承关系isinstance
在使用当前类的父类做判断时,输出为True(多重继承适用)
1 | class A: |
104. 什么是断言?应用场景?
官方解释:
Assert statements are a convenient way to insert debugging assertions into a program.
断言语句是将调试断言插入程序的便捷方式
assert condition:在condition为True时不触发,False触发AssertionError错误
1 | assert 1==1 |
如果没有特别的目的,断言应该用于如下情况:
- 防御性的编程
- 运行时对程序逻辑的检测
- 合约性检查(比如前置条件,后置条件)
- 程序中的常量
- 检查文档
105. lambda 表达式格式以及应用场景?
lambda
表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。
lambda表达式:lambda 参数1,参数2…: 参数表达式
适用场景:
- 简单功能的函数实现
- 不需要关注函数命名
- 复用性不高或只用一次的函数
1 | # 举例:输出1到100内的奇数 |
106. 新式类和旧式类的区别
在Python 3.x中取消了经典类,默认都是新式类,并且不必显式的继承object,也就是说:三种写法并无区别,推荐第一种
1 | class Person(object):pass |
Python2.x中,默认都是经典类,只有显式继承了object才是新式类,即:
1 | class Person(object):pass新式类写法 |
新式类和经典类的最大的区别:继承搜索顺寻的变化
新式类多继承搜索顺序(广度优先):先在水平方向查找,然后再向上查找
经典类多继承搜索顺序(深度优先):先深入继承树左侧查找,然后再返回,开始查找右侧
107. dir()是干什么用的?
dir()
函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;
带参数时,返回参数的属性、方法列表。
如果参数包含方法__dir__()
,该方法将被调用。如果参数不包含__dir__()
,该方法将最大限度地收集参数信息。
1 | #!/usr/bin/python3 |
108.一个包里有三个模块,demo1.py,demo2.py,demo3.py,但使用 from tools import *导入模块时,如何保证只有 demo1、demo3 被导入了。
Python包中使用__init__.py
确认导入的包
109. 列举 5 个 Python 中的异常类型以及其含义
1 | BaseException # 所有异常的基类 |
110. copy 和 deepcopy 的区别是什么?
copy
仅拷贝对象本身,而不拷贝对象中引用的其它对象。deepcopy
除拷贝对象本身,而且拷贝对象中引用的其它对象。(子对象)
copy不会为子对象额外创建新的内存空间,当子对象被修改之后,这个子对象的引用都会发生改变;
deepcopy是一个新对象的创建,只是用了和被拷贝对象相同的值,子对象改变不会影响被拷贝对象
111. 代码中经常遇到的args, *kwargs 含义及用法。
args
是 arguments
的缩写,表示位置参数;kwargs
是 keyword arguments
的缩写,表示关键字参数
1 | def demo_func(*args, **kwargs): |
112. Python 中会有函数或成员变量包含单下划线前缀和结尾,和双下划线前缀结尾,区别是什么?
单下划线
单下划线开头的命名方式被常用于模块中,在一个模块中以单下划线开头的变量和方法会被默认划入模块内部范围。当使用
from my_module import *
导入时,单下划线开头的变量和方法是不会被导入的。但使用import my_module
导入的话,仍然可以用my_module._var
这样的形式访问属性或方法。
单下划线结尾的命名方式也存在,但是不常用,其实也不推荐用。这种命名方式的作用就是为了和 python 的一些内置关键词区分开来,假设我们想给一个变量命名为 class,但是这会跟 python 的关键词 class 冲突,所以我们只好退一步使用单下划线结尾命名,也就是 class_。
双下划线
双下划线开头和结尾的是一些 python 的“魔术”对象,如类成员的
__init__、__del__、__add__、__getitem__
等,以及全局的__file__、__name__
等。 python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数,而是按照文档说明来使用。
双下划线开头的命名方式有实际的作用,采用这种命名的变量或方法无法直接通过 “对象名.变量名(方法名)” 这样的方式访问。
113. w、a+、wb 文件写入模式的区别
- r : 读取文件,若文件不存在则会报错
- w: 写入文件,若文件不存在则会先创建再写入,会覆盖原文件
- a : 写入文件,若文件不存在则会先创建再写入,但不会覆盖原文件,而是追加在文件末尾
- rb,wb:分别于r,w类似,用于读写二进制文件
- r+ : 可读、可写,文件不存在也会报错,写操作时会覆盖
- w+ : 可读,可写,文件不存在先创建,会覆盖
- a+ :可读、可写,文件不存在先创建,不会覆盖,追加在末尾
114. 举例 sort 和 sorted 的区别
1 | demo_list = [1, 3, 4, 2, 7, 5] |
115. 什么是负索引?
负索引是指使用负数做为索引,-1代表数组的最后一位
116. pprint 模块是干什么的?
1 | # pprint用于输出一个整齐美观Python数据的结构 |
117. 在 Python 中如何使用多进制数字?
1 | # 1、二进制数字由0和1组成,我们使用0b或0B前缀表示二进制数 |
118. 怎样声明多个变量并赋值?
a, b, = 1, 2
Python的算法与数据结构
119.已知:Alist = [1, 2, 3] Bset = {1, 2, 3}
(1) 从 AList 和 BSet 中 查找 4,最坏时间复杂度那个大?
(2) 从 AList 和 BSet 。中 插入 4,最坏时间复杂度那个大?
- python的列表内部实现是数组(具体实现要看解析器, CPython的实现 ),因此就有数组的特点。超过容量会增加更多的容量,set, get 是O(1),但del, insert, in的性能是O(n)。
- 关于字典需要了解的是hash函数和哈希桶。一个好的hash函数使到哈希桶中的值只有一个,若多个key hash到了同一个哈希桶中,称之为哈希冲突。查找值时,会先定位到哈希桶中,再遍历hash桶。更详细的信息请点这里。在hash基本没有冲突的情况下get, set, delete, in方面都是O(1)。
- 集合内部实现是dict的。在in操作上是O(1), 这一点比list要强。
- 由此可知:
- (1)查找操作set优于list;
- (2)插入操作两个相同。
120. 用 Python 实现一个二分查找的函数
1 | def binary_Search(search_list: list, search_num: int): |
121.python 单例模式的实现方法
1 | class SingleCase(object): |
122. 使用 Python 实现一个斐波那契数列
1 | #!/usr/bin/python3 |
123. 找出列表中的重复数字 127. 找出列表中的单个数字
1 | from collections import Counter |
124. 写一个冒泡排序
1 | def bubble_sort(parm_list): |
125. 写一个快速排序
1 | def quick_sort(parm_list): |
126. 写一个拓扑排序
在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点。
- 先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。
- 一直做改操作,直到所有的节点都被分离出来。
- 如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。
1 | def topology_sort(relation_parm): |
Python爬虫相关
127. 在 requests 模块中,requests.content 和 requests.text 什么区别
.content
中间存的是字节码 .text
存的是.content
编码后的字符串
128. 简要写一下 lxml 模块的使用方法框架
1 | from lxml import etree |
129. 说一说 scrapy 的工作流程
- 首先Spiders(爬虫)将需要发送请求的
url(requests)
经ScrapyEngine(引擎)
交给Scheduler(调度器)
。 Scheduler(排序,入队)
处理后,经ScrapyEngine
,DownloaderMiddlewares(可选,主要有User_Agent, Proxy代理)交给Downloader。Downloader
向互联网发送请求,并接收下载响应(response)。将响应(response)经ScrapyEngine
,SpiderMiddlewares(可选)交给Spiders
。Spiders
处理response,提取数据并将数据经ScrapyEngine
交给ItemPipeline
保存(可以是本地,可以是数据库)。- 提取url重新经
ScrapyEngine
交给Scheduler
进行下一个循环。直到无Url请求程序停止结束。
130. scrapy 的去重原理
- Scrapy本身自带有一个中间件;
- scrapy源码中可以找到一个dupefilters.py去重器;
- 需要将dont_filter设置为False开启去重,默认是false去重,改为True,就是没有开启去重;
- 对于每一个url的请求,调度器都会根据请求得相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中的指纹信息进 行 比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中;5.如果set()集合中没有存在这个加密后的数据,就将这个Request对象放入队列中,等待被调度。
131. scrapy 中间件有几种类,你用过哪些中间件
spider中间件
(主职过滤)对Request、Response的主要作用在过滤,可以对特定路径的URL请求丢弃、对特定页面响应过滤、同时对一些不含有指定信息的item过滤,当然pipeline也能实现item的过滤。
下载中间件
(主职加工)主要作用是加工,如给Request添加代理、添加UA、添加cookie,对Response返回数据编码解码、压缩解压缩、格式化等预处理。
user-agend中间件、代理ip中间件、selenium中间件、cookie中间件
132. 你写爬虫的时候都遇到过什么?反爬虫措施,你是怎么解决的?
反爬策略1:通过UA限制或者其他头信息限制
解决方案:构建用户代理池或其他头信息反爬策略2:通过访问者IP限制
解决方案:构建IP代理池反爬策略3:通过验证码限制
解决方案:手工打码、验证码接口自动识别或者通过机器学习自动识别反爬策略4:通过数据的异步加载限制
解决方案:抓包分析或者使用PhantomJS反爬策略5:通过Cookie限制
解决方案:进行Cookie处理
反爬策略6:通过JS限制(如请求的数据通过JS随机生成等)
解决方案:分析JS解密或者使用PhantomJS
133. scrapy 和 scrapy-redis 有什么区别?为什么选择 redis 数据库?
Scrapy 是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础的组件(仅有组件)。
134. 分布式爬虫主要解决什么问题
- ip
- 带宽
- cpu
- io
135. 写爬虫是用多进程好?还是多线程好? 为什么?
- IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。
- 在实际的数据采集过程中,既考虑网速和响应的问题,也需要考虑自身机器的硬件情况,来设置多进程或多线程
phantomjs
或者chrome-headless
来抓取的爬虫,应当是多进程的,因为每一个phan/chro 实例就是一个进程了,并发只能是多进程。此外爬虫中还是数据处理业务,如果数据处理业务是一个比较耗时的计算型操作,那么对数据处理部分应当设为多进程,但更多可能会考虑将该部分数据处理操作和爬虫程序解耦,也就是先把数据抓取下来,事后单独运行另外的程序解析数据。
136. 解析网页的解析器使用最多的是哪几个
lxml、re、beautifulsope
137. 需要登录的网页,如何解决同时限制 ip,cookie,session(其中有一些是动态生成的)在不使用动态爬取的情况下?
- 解决限制 IP 可以使用代理 IP 地址池、服务器;
- 不适用动态爬取的情况下可以使用反编译 JS 文件获取相应的文件,或者换用其他平台(比如手机端) 看看是否可以获取相应的 json 文件。
138. 验证码的解决(简单的:对图像做处理后可以得到的,困难的:验证码是点击,拖动等动态进行的?)
图形验证码:干扰、杂色不是特别多的图片可以使用开源库
Tesseract
进行识别,太过复杂的需要借助第三方打码平台。点击和拖动滑块验证码可以借助
selenium
、无图形界面浏览器(chromedirver
或者phantomjs
) 和pillow
包来模拟人的点击和滑动操作,pillow
可以根据色差识别需要滑动的位置。手动打码(有的验证码确实无解)
139. 使用最多的数据库(mysql,mongodb,redis 等),对他的理解?
MySQL 数据库:开源免费的关系型数据库,需要实现创建数据库、数据表和表的字段,表与表之间可以进行关联(一对多、多对多),是持久化存储。
Mongodb 数据库:是非关系型数据库,数据库的三元素是,数据库、集合、文档,可以进行持久化存储,也可作为内存数据库,存储数据不需要事先设定格式,数据以键值对的形式存储。
redis 数据库:非关系型数据库,使用前可以不用设置格式,以键值对的方式保存,文件格式相对自由,主要用与缓存数据库,也可以进行持久化存储。
140. 简要介绍三次握手和四次挥手
- 第一次握手:客户端发送SYN包(SYN=j)到服务器,并进入SYN_SEND状态,等待服务器确认。
- 第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据
由于TCP连接是全双工的,连接的拆除需要发送四个包,因此称为“四次挥手”。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
- 第一次挥手:客户端发送一个FIN,用来关闭客户到服务器的数据传送。
- 第二次挥手:服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
- 第三次挥手:服务器关闭与客户端的连接,发送一个FIN给客户端。
- 第四次挥手:客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
Python网络编程
141. TCP 和 UDP 的区别?
- 基于连接与无连接;
- 对系统资源的要求(TCP较多,UDP少);
- UDP程序结构较简单;
- 流模式与数据报模式 ;
- TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
tcp协议和udp协议的差别
比较内容 | TCP | UDP |
---|---|---|
是否连接 | 面向连接 | 面向非连接 |
传输可靠性 | 可靠 | 不可靠 |
应用场合 | 少量数据 | 传输大量数据 |
速度 | 慢 | 快 |
142. 什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?
粘包的概念
粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
出现粘包的原因
出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
粘包的处理方式:
- 当时短连接的情况下,不用考虑粘包的情况
- 如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
- 如果双方建立长连接,需要在连接后一段时间内发送不同结构数据
接收方创建预处理线程,对接收到的数据包进行预处理,将粘连的包分开;分包是指在出现粘包的时候我们的接收方要进行分包处理,数据包的边界发生错位,导致读出错误的数据分包,进而曲解原始数据含义。
粘包情况有两种:
- 一种是粘在一起的包都是完整的数据包,
- 另一种情况是粘在一起的包有不完整的包。
- 本文作者: Jason
- 本文链接: https://caicaijason.github.io/2019/12/26/Python面试题汇总/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!