Python常见的170道面试题全解析答案
语言特性
1. 谈谈对 Python 和其他语言的区别
答:Python 是一门语法简洁优美,功能强大无比,应用领域非常广泛,具有强大完备的第三方库,他是一门强类型的可移植、可扩展,可嵌入的解释型编程语言,属于动态语言。
拿 C 语言和 Python 比: Python 的第三方类库比较齐全并且使用简洁,很少代码就能实现一些功能,如果用 C 去实现相同的功能可能就比较复杂。但是对于速度来说 Python 的运行速度相较于 C 就比较慢了。所以有利的同时也有弊端,毕竟我们的学习成本降低了。
2. 简述解释型和编译型编程语言
答:解释型语言是在运行程序的时候才翻译,每执行一次,要翻译一次,效率较低。 编译型就是直接编译成机型可以执行的,只翻译一次,所以效率相对来说较高。
3. Python 的解释器种类以及相关特点?
答:
CPython c 语言开发的,使用最广的解释器
IPython 基于 cPython 之上的一个交互式计时器,交互方式增强功能和 cPython 一样
PyPy 目标是执行效率,采用 JIT 技术。对 Python 代码进行动态编译,提高执行效率
JPython 运行在 Java 上的解释器,直接把 Python 代码编译成 Java 字节码执行
IronPython 运行在微软 .NET 平台上的解释器,把 Python 编译成 . NET 的字节码。
4. Python3 和 Python2 的区别?
答: 这里例举 5 条
print 在 Python3 中是函数必须加括号,Python2 中 print 为 class。
Python2 中使用 xrange,Python3 使用 range。
Python2 中默认的字符串类型默认是 ASCII,Python3 中默认的字符串类型是 Unicode。
Python2 中/的结果是整型,Python3 中是浮点类型。
Python2 中声明元类:metaclass = MetaClass,Python3 中声明元类:class newclass(metaclass=MetaClass):pass。
5. Python3 和 Python2 中 int 和 long 区别?
答:Python2 有 int 和 long 类型。int 类型最大值不能超过 sys.maxint,而且这个最大值是平台相关的。可以通过在数字的末尾附上一个L来定义长整型,显然,它比 int 类型表示的数字范围更大。在 Python3 里,只有一种整数类型 int,大多数情况下,和 Python2中的长整型类似。
6. xrange 和 range 的区别?
答:xrange 是在 Python2 中的用法,Python3 中只有 range xrange 用法与 range 完全相同,所不同的是生成的不是一个 list 对象,而是一个生成器。
编码规范
7. 什么是 PEP8?
答:PEP8 通常会听别人提到,但是具体的指什么内容呢,简单介绍下。 《Python Enhancement Proposal #8》(8 号 Python 增强提案)又叫 PEP8,他针对的 Python 代码格式而编订的风格指南。
8. 了解 Python 之禅么?
答:通过 import this 语句可以获取其具体的内容。它告诉大家如何写出高效整洁的代码。
9. 了解 DocStrings 么?
答:DocStrings 文档字符串是一个重要工具,用于解释文档程序,帮助你的程序文档更加简单易懂。主要是解释代码作用的。
10. 了解类型注解么?
答:PEP 484 引入了类型提示,这使得可以对 Python 代码进行静态类型检查。 在使用 Ide 的时候可以获取到参数的类型,更方便传入参数。使用格式如下
def foo(num: int) -> None:
print(f”接收到的数字是:{num}”)
介绍下这个简单例子,我们可以在函数的参数部分使用参数名+:+类型,来指定参数可以接受的类型,这里的话就是 num 参数为 int 类型,然后后面->接的是返回值的类型。这里返回值为 None,然后通过 fstring 格式化字符串输出传入的数字。
11. 例举你知道 Python 对象的命名规范,例如方法或者类等
答:
类:总是使用首字母大写单词串,如 MyClass。内部类可以使用额外的前导下划线。 变量:小写,由下划线连接各个单词。方法名类似 常量:常量名所有字母大写 等
12. Python 中的注释有几种?
答:总体来说分为两种,单行注释和多行注释。
单行注释在行首是 #。
多行注释可以使用三个单引号或三个双引号,包括要注释的内容。
13. 如何优雅的给一个函数加注释?
答:可以使用 docstring 配合类型注解
14. 如何给变量加注释?
答:可以通过变量名:类型的方式如下
a: str = “this is string type”
15. Python 代码缩进中是否支持 Tab 键和空格混用。
答:不允许 tab 键和空格键混用,这种现象在使用 sublime 的时候尤为明显。
一般推荐使用 4 个空格替代 tab 键。
16. 是否可以在一句 import 中导入多个库?
答:可以是可以,但是不推荐。因为一次导入多个模块可读性不是很好,所以一行导入一个模块会比较好。同样的尽量少用 from modulename import *,因为判断某个函数或者属性的来源有些困难,不方便调试,可读性也降低了。
17. 在给 Py 文件命名的时候需要注意什么?
答:给文件命名的时候不要和标准库库的一些模块重复,比如 abc。 另外要名字要有意义,不建议数字开头或者中文命名。
18. 例举几个规范 Python 代码风格的工具
答:pylint 和 flake8
数据类型-字符串
19. 列举 Python 中的基本数据类型?
答: Python3 中有六个标准的数据类型:字符串(String)、数字(Digit)、列表(List)、元组(Tuple)、集合(Sets)、字典(Dictionary)。
20. 如何区别可变数据类型和不可变数据类型
答: 从对象内存地址方向来说
可变数据类型:在内存地址不变的情况下,值可改变(列表和字典是可变类型,但是字典中的 key 值必须是不可变类型)
不可变数据类型:内存改变,值也跟着改变。(数字,字符串,布尔类型,都是不可变类型)可以通过 id() 方法进行内存地址的检测。
21. 将”hello world”转换为首字母大写”Hello World”
答: 这个得看清题目是要求两个单词首字母都要大写,如果只是第一个单词首字母大小的话,只使用 capitalize 即可,但是这里是两个单词,所以用下面的方法。
arr = “hello world”.split(“ “)
new_str = f”{arr[0].capitalize()} {arr[1].capitalize()}”
print(new_str)
后来评论中有朋友提到了下面的方法,这里感谢这位朋友提醒。方案如下
“hello world”.title()
非常简单一句话搞定。
22. 如何检测字符串中只含有数字?
答:可以通过 isdigit 方法,例子如下
s1 = “12223”.isdigit()
print(s1)
s2 = “12223a”.isdigit()
print(s2)
#结果如下:
#True
#False
23. 将字符串”ilovechina”进行反转
答:
s1 = “ilovechina”[::-1]
print(s1)
24. Python 中的字符串格式化方式你知道哪些?
答:%s,format,fstring(Python3.6 开始才支持,现在推荐的写法)
25. 有一个字符串开头和末尾都有空格,比如“ adabdw ”,要求写一个函数把这个字符串的前后空格都去掉。
答:因为题目要是写一个函数所以我们不能直接使用 strip,不过我们可以把它封装到函数啊
def strip_function(s1):
return s1.strip()
s1 = “ adabdw “
print(strip_function(s1))
26. 获取字符串”123456“最后的两个字符。
答:切片使用的考察,最后两个即开始索引是 -2,代码如下
a = “123456”
print(a[-2::])
27. 一个编码为 GBK 的字符串 S,要将其转成 UTF-8 编码的字符串,应如何操作?
答:
a= “S”.encode(“gbk”).decode(“utf-8”,’ignore’)
print(a)
28. (1)s=”info:xiaoZhang 33 shandong”,用正则切分字符串输出[‘info’, ‘xiaoZhang’, ‘33’, ‘shandong’]。(2)a = “你好 中国 “,去除多余空格只留一个空格。
答:
(1)我们需要根据冒号或者空格切分
import re
s = “info:xiaoZhang 33 shandong”
res = re.split(r”:| “, s)
print(res)
(2)
s = “你好 中国 “
print(“ “.join(s.split()))
29. (1) 怎样将字符串转换为小写。 (2) 单引号、双引号、三引号的区别?
答: (1) 使用字符串的 lower() 方法。
(2)单独使用单引号和双引号没什么区别,但是如果引号里面还需要使用引号的时候,就需要这两个配合使用了,然后说三引号,同样的三引号也分为三单引号和三双引号,两个都可以声名长的字符串时候使用,如果使用 docstring 就需要使用三双引号。
数据类型 - 列表
30. 已知 AList = [1,2,3,1,2],对 AList 列表元素去重,写出具体过程。
答:
list(set(AList))
31. 如何实现 “1,2,3” 变成 [“1”,”2”,”3”]
答:
s = “1,2,3”
print(s.split(“,”))
32. 给定两个 list,A 和 B,找出相同元素和不同元素
答:
A、B 中相同元素:print(set(A)&set(B))
A、B 中不同元素:print(set(A)^set(B))
33. [[1,2],[3,4],[5,6]] 一行代码展开该列表,得出 [1,2,3,4,5,6]
答:
l = [[1,2],[3,4],[5,6]]
x=[j for i in l for j in i]
print(x)
34. 合并列表 [1,5,7,9] 和 [2,2,6,8]
答:使用 extend 和 + 都可以。
a = [1,5,7,9]
b = [2,2,6,8]
a.extend(b)
print(a)
35. 如何打乱一个列表的元素?
答:
import random
a = [1, 2, 3, 4, 5]
random.shuffle(a)
print(a)
数据类型 - 字典
36. 字典操作中 del 和 pop 有什么区别
答:del 可以根据索引(元素所在位置)来删除的,没有返回值。 pop 可以根据索引弹出一个值,然后可以接收它的返回值。
37. 按照字典的内的年龄排序
d1 = [
{‘name’:’alice’, ‘age’:38},
{‘name’:’bob’, ‘age’:18},
{‘name’:’Carl’, ‘age’:28},
]
答:
d1 = [
{‘name’: ‘alice’, ‘age’: 38},
{‘name’: ‘bob’, ‘age’: 18},
{‘name’: ‘Carl’, ‘age’: 28},
]
print(sorted(d1, key=lambda x:x[“age”]))
38. 请合并下面两个字典 a = {“A”:1,”B”:2},b = {“C”:3,”D”:4}
答: 合并字典方法很多,可以使用 a.update(b) 或者下面字典解包的方式
a = {“A”:1,”B”:2}
b = {“C”:3,”D”:4}
print({**a,**b})
39. 如何使用生成式的方式生成一个字典,写一段功能代码。
答:
需求 3: 把字典的 key 和 value 值调换;
d = {‘a’:’1’, ‘b’:’2’}
print({v:k for k,v in d.items()})
40. 如何把元组 (“a”,”b”) 和元组 (1,2),变为字典 {“a”:1,”b”:2}
答 zip 的使用,但是最后记得把 zip 对象再转换为字典。
a = (“a”, “b”)
b = (1, 2)
print(dict(zip(a, b)))
数据类型 - 综合
41. 下列字典对象键类型不正确的是?
A:{1:0,2:0,3:0}
B:{“a”:0, “b”:0, “c”:0}
C: {(1,2):0, (2,3):0}
D: {[1,2]:0, [2,3]:0}
答:D 因为只有可 hash 的对象才能做字典的键,列表是可变类型不是可 hash 对象,所以不能用列表做为字典的键。
42. 如何交换字典 {“A”:1,”B”:2}的键和值
答:
s = {“A”:1,”B”:2}
#方法一:
dict_new = {value:key for key,value in s.items()}
方法二:
new_s= dict(zip(s.values(),s.keys()))
43. Python 里面如何实现 tuple 和 list 的转换?
答: Python 中的类型转换,一般通过类型强转即可完成 tuple 转 list 是 list() 方法 list 转 tuple 使用 tuple() 方法
44. 我们知道对于列表可以使用切片操作进行部分元素的选择,那么如何对生成器类型的对象实现相同的功能呢?
答: 这个题目考察了 Python 标准库的 itertools 模快的掌握情况,该模块提供了操作生成器的一些方法。 对于生成器类型我们使用 islice 方法来实现切片的功能。例子如下
from itertools import islice
gen = iter(range(10)) #iter()函数用来生成迭代器
#第一个参数是迭代器,第二个参数起始索引,第三个参数结束索引,不支持负数索引
for i in islice(gen,0,4):
print(i)
45. 请将 [i for i in range(3)] 改成生成器
答:通过把列表生产式的中括号,改为小括号我们就实现了生产器的功能即,
(i for i in range(3))
46. a=”hello” 和 b=”你好” 编码成 bytes 类型
答: 这个题目一共三种方式,第一种是在字符串的前面加一个 b,第二种可以使用 bytes 方法,第三种使用字符串 encode 方法。具体代码如下,abc 代表三种情况
a = b”hello”
b = bytes(“你好”, “utf-8”)
c = “你好”.encode(“utf-8”)
print(a, b, c)
47. 下面的代码输出结果是什么?
a = (1,2,3,[4,5,6,7],8)
a[2] = 2
答: 我们知道元组里的元素是不能改变的所以这个题目的答案是出现异常。
48. 下面的代码输出的结果是什么?
a = (1,2,3,[4,5,6,7],8)
a[3][0] = 2
答:前面我说了元组的里元素是不能改变的,这句话严格来说是不准确的,如果元组里面元素本身就是可变类型,比如列表,那么在操作这个元素里的对象时,其内存地址也是不变的。a[3] 对应的元素是列表,然后对列表第一个元素赋值,所以最后的结果是: (1,2,3,[2,5,6,7],8)
操作类题目
49. Python 交换两个变量的值
答:在 Python 中交换两个对象的值通过下面的方式即可
a , b = b ,a
但是需要强调的是这并不是元组解包,通过 dis 模块可以发现,这是交换操作的字节码是 ROT_TWO,意思是在栈的顶端做两个值的互换操作。
50. 在读文件操作的时候会使用 read、readline 或者 readlines,简述它们各自的作用
答:.read() 每次读取整个文件,它通常用于将文件内容放到一个字符串变量中。如果希望一行一行的输出那么就可以使用 readline(),该方法会把文件的内容加载到内存,所以对于对于大文件的读取操作来说非常的消耗内存资源,此时就可以通过 readlines 方法,将文件的句柄生成一个生产器,然后去读就可以了。
51. json 序列化时,可以处理的数据类型有哪些?如何定制支持 datetime 类型?
答: 可以处理的数据类型是 str、int、list、tuple、dict、bool、None, 因为 datetime 类不支持 json 序列化,所以我们对它进行拓展。
自定义时间序列化
import json
from datetime import datetime, date
JSONEncoder 不知道怎么去把这个数据转换成 json 字符串的时候
,它就会去调 default()函数,所以都是重写这个函数来处理它本身不支持的数据类型,
default()函数默#认是直接抛异常的。
class DateToJson(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime(‘%Y-%m-%d %H:%M:%S’)
elif isinstance(obj, date):
return obj.strftime(‘%Y-%m-%d’)
else:
return json.JSONEncoder.default(self, obj)
d = {‘name’: ‘cxa’, ‘data’: datetime.now()}
print(json.dumps(d, cls=DateToJson))
52. json 序列化时,默认遇到中文会转换成 unicode,如果想要保留中文怎么办?
答:可以通过 json.dumps 的 ensure_ascii 参数解决,代码示例如下:
import json
a=json.dumps({“name”:”张三”},ensure_ascii=False)
print(a)
53. 有两个磁盘文件 A 和 B,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件 C 中。
答:
#文件 A.txt 内容为 ASDCF
#文件 B.txt 内容为 EFGGTG
with open(“A.txt”) as f1:
f1_txt = f1.readline()
with open(“B.txt”) as f2:
f2_txt = f2.readline()
f3_txt = f1_txt + f2_txt
f3_list = sorted(f3_txt)
with open(“C.txt”, “a+”) as f:
f.write(“”.join(f3_list))
输出的文件 C 的内容为 ACDEFFGGGST
54. 如果当前的日期为 20190530,要求写一个函数输出 N 天后的日期,(比如 N 为 2,则输出 20190601)。
答:这个题目考察的是 datetime 里的 timedelta 方法的使用,参数可选、默认值都为 0:datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0) 通过这个参数可以指定不同的日期类型进行加减操作,这里我们需要改的是 days,代码如下
import datetime
def datetime_operate(n: int):
now = datetime.datetime.now() # 获取当前时间
_new_date = now + datetime.timedelta(days=n) # 获取指定天数后的新日期
new_date = _new_date.strftime(“%Y%m%d”) # 转换为指定的输出格式
return new_date
if name == ‘main‘:
print(datetime_operate(4))
55. 写一个函数,接收整数参数 n,返回一个函数,函数的功能是把函数的参数和 n 相乘并把结果返回。
答:这个题目考查了闭包的使用代码示例如下,返回函数之类型是函数对象。
def mul_operate(num):
def g(val):
return num * val
return g
m = mul_operate(8)
print(m(5))
56. 下面代码会存在什么问题,如何改进?
def strappend(num):
str=’first’
for i in range(num):
str+=str(i)
return str
答: 首先不应该使用 Python 的内置类似 str 作为变量名这里我把它改为了 s,另外在Python,str 是个不可变对象,每次迭代都会生成新的存储空间,num 越大,创建的 str 对象就会越多,内存消耗越大。使用 yield 改成生成器即可, 还有一点就是命名规范的位置,函数名改为_分割比较好,完整的代码如下:
def str_append(num):
s = ‘first’
for i in range(num):
s += str(i)
yield s
if name == ‘main‘:
for i in str_append(3):
print(i)
57. 一行代码输出 1-100 之间的所有偶数。
答:可以通过列表生成式,然后使用与操作如果如 1 与之后结果为 0 则表明为偶数,等于 1 则为奇数。
方法1
print([i for i in range(1, 101) if i & 0x1 == 0])
方法2:测试发现方法二效率更高
print(list(range(2, 101, 2)))
58. with 语句的作用,写一段代码?
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
其他的内容看下面我之前写的代码。
#一般访问文件资源时我们会这样处理:
f = open(
‘c:\test.txt’, ‘r’)
data = f.read()
f.close()
这样写没有错,但是容易犯两个毛病:
1. 如果在读写时出现异常而忘了异常处理。
2. 忘了关闭文件句柄
#以下的加强版本的写法:
f = open(‘c:\test.txt’, ‘r’)
try:
data = f.read()
finally:
f.close()
#以上的写法就可以避免因读取文件时异常的发生而没有关闭问题的处理了。代码长了一些。
#但使用 with 有更优雅的写法:
with open(r’c:\test.txt’, ‘r’) as f:
data = f.read()
#with 的实现
class Test:
def enter(self):
print(‘enter() is call!’)
return self
def dosomething(self):
print(‘dosomethong!’)
def exit(self, exc_type, exc_value, traceback):
print(‘exit() is call!’)
print(f’type:{exc_type}’)
print(f’value:{exc_value}’)
print(f’trace:{traceback}’)
print(‘exit() is call!’)
with Test() as sample:
pass
#当对象被实例化时,就会主动调用__enter__()方法,任务执行完成后就会调用__exit__()方法,
#另外,注意到,exit()方法是带有三个参数的(exc_type, exc_value, traceback),
#依据上面的官方说明:如果上下文运行时没有异常发生,那么三个参数都将置为 None,
#这里三个参数由于没有发生异常,的确是置为了 None, 与预期一致.
修改后不出异常了
class Test:
def enter(self):
print(‘enter() is call!’)
return self
def dosomething(self):
x = 1/0
print(‘dosomethong!’)
def exit(self, exc_type, exc_value, traceback):
print(‘exit() is call!’)
print(f’type:{exc_type}’)
print(f’value:{exc_value}’)
print(f’trace:{traceback}’)
print(‘exit() is call!’)
return True
with Test() as sample:
59. Python 字典和 json 字符串相互转化方法
答:
在 Python 中使用 dumps 方法 将 dict 对象转为 Json 对象,使用 loads 方法可以将 Json 对象转为 dict 对象。
dic = {‘a’: 123, ‘b’: “456”, ‘c’: “liming”}
json_str = json.dumps(dic)
dic2 = json.loads(json_str)
print(dic2)
打印:
‘{“a”: 123, “b”: “456”, “c”: “liming”}’
{‘a’: 123, ‘b’: ‘456’, ‘c’: ‘liming’}
我们再来看一个特殊的例子
import json
dic = {‘a’: 123, ‘b’: “456”, ‘c’: “liming”}
dic_str = json.loads(str(dic).replace(“‘“, “"“))
print(dic_str)
下面我解释下上面代码是测试什么:
首先 json.loads(jsonstr) 这里面的参数只能是 jsonstr 格式的字符串.
当我们使用 str 将字典 dic 转化为字符串以后,得到的结果为:”{‘a’: 123, ‘b’: ‘456’, ‘c’: ‘liming’}”。
如果直接使用 json.loads(str(dic)) 你会发现出现错误,原因就是,单引号的字符串不符合Json的标准格式所以再次使用了 replace(“‘“, “"“)。得到字典
其实这个例子主要目的是告诉大家 Json 的标准格式是不支持单引号型字符串的,否则会出现以下错误。
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
60. 请写一个 Python 逻辑,计算一个文件中的大写字母数量
答:
with open(‘A.txt’) as fs:
count = 0
for i in fs.read():
if i.isupper():
count += 1
print(count)
61. 请写一段 Python连接Mongo数据库,然后的查询代码。
答:
-- coding: utf-8 --
@Author : 陈祥安
import pymongo
db_configs = {
‘type’: ‘mongo’,
‘host’: ‘地址’,
‘port’: ‘端口’,
‘user’: ‘spider_data’,
‘passwd’: ‘密码’,
‘db_name’: ‘spider_data’
}
class Mongo():
def init(self, db=db_configs[“db_name”], username=db_configs[“user”],
password=db_configs[“passwd”]):
self.client = pymongo.MongoClient(f’mongodb://{db_configs[“host”]}:db_configs[“port”]’)
self.username = username
self.password = password
if self.username and self.password:
self.db1 = self.client[db].authenticate(self.username, self.password)
self.db1 = self.client[db]
def find_data(self):
# 获取状态为0的数据
data = self.db1.test.find({“status”: 0})
gen = (item for item in data)
return gen
if name == ‘main‘:
m = Mongo()
print(m.find_data())
62.说一说Redis的基本类型
答: Redis 支持五种数据类型: string(字符串) 、 hash(哈希)、list(列表) 、 set(集合) 及 zset(sorted set: 有序集合)。
63. 请写一段 Python连接Redis数据库的代码。
答:
from redis import StrictRedis, ConnectionPool
redis_url=”redis://:xxxx@112.27.10.168:6379/15”
pool = ConnectionPool.from_url(redis_url, decode_responses=True)
r= StrictRedis(connection_pool=pool)
64. 请写一段 Python连接Mysql数据库的代码。
答:
conn = pymysql.connect(host=’localhost’,
port=3306, user=’root’,
passwd=’1234’, db=’user’, charset=’utf8mb4’)#声明mysql连接对象
cursor=conn.cursor(cursor=pymysql.cursors.DictCursor)#查询结果以字典的形式
cursor.execute(sql语句字符串)#执行sql语句
conn.close()#关闭链接
65.了解Redis的事务么
答: 简单理解,可以认为 redis 事务是一些列 redis 命令的集合,并且有如下两个特点: 1.事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 2.事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 一般来说,事务有四个性质称为ACID,分别是原子性,一致性,隔离性和持久性。 一个事务从开始到执行会经历以下三个阶段:
开始事务
命令入队
执行事务 代码示例:
import redis
import sys
def run():
try:
conn=redis.StrictRedis(‘192.168.80.41’)
Python中redis事务是通过pipeline的封装实现的
pipe=conn.pipeline()
pipe.sadd('s001','a')
sys.exit()
#在事务还没有提交前退出,所以事务不会被执行。
pipe.sadd('s001','b')
pipe.execute()
pass
except Exception as err:
print(err)
pass
if name==”main“:
run()
66.了解数据库的三范式么?
答: 经过研究和对使用中问题的总结,对于设计数据库提出了一些规范,这些规范被称为范式 一般需要遵守下面3范式即可: 第一范式(1NF):强调的是列的原子性,即列不能够再分成其他几列。 第二范式(2NF):首先是 1NF,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。 第三范式(3NF):首先是 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
67.了解分布式锁么
答: 分布式锁是控制分布式系统之间的同步访问共享资源的一种方式。 对于分布式锁的目标,我们必须首先明确三点:
任何一个时间点必须只能够有一个客户端拥有锁。
不能够有死锁,也就是最终客户端都能够获得锁,尽管可能会经历失败。
错误容忍性要好,只要有大部分的Redis实例存活,客户端就应该能够获得锁。 分布式锁的条件 互斥性:分布式锁需要保证在不同节点的不同线程的互斥 可重入性:同一个节点上的同一个线程如果获取了锁之后,能够再次获取这个锁。 锁超时:支持超时释放锁,防止死锁 高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。 支持阻塞和非阻塞:可以实现超时获取失败,tryLock(long timeOut) 支持公平锁和非公平锁
分布式锁的实现方案 1、数据库实现(乐观锁) 2、基于zookeeper的实现 3、基于Redis的实现(推荐)
68.用 Python 实现一个 Reids 的分布式锁的功能
答:REDIS分布式锁实现的方式:SETNX + GETSET,NX是Not eXists的缩写,如SETNX命令就应该理解为:SET if Not eXists。 多个进程执行以下Redis命令:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果 SETNX 返回1,说明该进程获得锁,SETNX将键 lock.foo 的值设置为锁的超时时间(当前时间 + 锁的有效时间)。 如果 SETNX 返回0,说明其他进程已经获得了锁,进程不能进入临界区。进程可以在一个循环中不断地尝试 SETNX 操作,以获得锁。
import time
import redis
from conf.config import REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
class RedisLock:
def init(self):
self.conn = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD, db=1)
self._lock = 0
self.lock_key = “”
@staticmethod
def my_float(timestamp):
“””
Args:
timestamp:
Returns:
float或者0
如果取出的是None,说明原本锁并没人用,getset已经写入,返回0,可以继续操作。
“””
if timestamp:
return float(timestamp)
else:
#防止取出的值为None,转换float报错
return 0
@staticmethod
def get_lock(cls, key, timeout=10):
cls.lock_key = f”{key}_dynamic_lock”
while cls._lock != 1:
timestamp = time.time() + timeout + 1
cls._lock = cls.conn.setnx(cls.lock_key, timestamp)
# if 条件中,可能在运行到or之后被释放,也可能在and之后被释放
# 将导致 get到一个None,float失败。
if cls._lock == 1 or (
time.time() > cls.my_float(cls.conn.get(cls.lock_key)) and
time.time() > cls.my_float(cls.conn.getset(cls.lock_key, timestamp))):
break
else:
time.sleep(0.3)
@staticmethod
def release(cls):
if cls.conn.get(cls.lock_key) and time.time() < cls.conn.get(cls.lock_key):
cls.conn.delete(cls.lock_key)
def redis_lock_deco(cls):
def _deco(func):
def __deco(*args, **kwargs):
cls.get_lock(cls, args[1])
try:
return func(*args, **kwargs)
finally:
cls.release(cls)
return __deco
return _deco
@redis_lock_deco(RedisLock())
def my_func():
print(“myfunc() called.”)
time.sleep(20)
if name == “main“:
my_func()
69.写一段 Python 使用 mongo 数据库创建索引的代码:
答:
-- coding: utf-8 --
@Time : 2018/12/28 10:01 AM
@Author : cxa
import pymongo
db_configs = {
‘type’: ‘mongo’,
‘host’: ‘地址’,
‘port’: ‘端口’,
‘user’: ‘spider_data’,
‘passwd’: ‘密码’,
‘db_name’: ‘spider_data’
}
class Mongo():
def init(self, db=db_configs[“db_name”], username=db_configs[“user”],
password=db_configs[“passwd”]):
self.client = pymongo.MongoClient(f’mongodb://{db_configs[“host”]}:{db_configs[“port”]}’)
self.username = username
self.password = password
if self.username and self.password:
self.db1 = self.client[db].authenticate(self.username, self.password)
self.db1 = self.client[db]
def add_index(self):
“””
通过create_index添加索引
“””
self.db1.test.create_index([(‘name’, pymongo.ASCENDING)], unique=True)
def get_index(self,):
“””
查看索引列表
“””
indexlist=self.db1.test.list_indexes()
for index in indexlist:
print(index)
if name == ‘main‘:
m = Mongo()
m.add_index()
print(m.get_index())
高级特性
70. 函数装饰器有什么作用?请列举说明?
答: 装饰器就是一个函数,它可以在不需要做任何代码变动的前提下给一个函数增加额外功能,启动装饰的效果。 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。 下面是一个日志功能的装饰器
from functools import wraps
def log(label):
def decorate(func):
@wraps(func)
def _wrap(*args,**kwargs):
try:
func(*args,**kwargs)
print(“name”,func.name)
except Exception as e:
print(e.args)
return _wrap
return decorate
@log(“info”)
def foo(a,b,c):
print(a+b+c)
print(“in foo”)
#decorate=decorate(foo)
if name == ‘main‘:
foo(1,2,3)
#decorate()
71. Python 垃圾回收机制?
答:Python 不像 C++,Java 等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对 Python 语言来讲,对象的类型和内存都是在运行时确定的。这也是为什么我们称 Python 语言为动态类型的原因。
主要体现在下面三个方法:
1.引用计数机制 2.标记-清除 3.分代回收
72. 魔法函数 _call_怎么使用?
答: call 可以把类实例当做函数调用。 使用示例如下
class Bar:
def call(self, *args, **kwargs):
print(‘in call’)
if name == ‘main‘:
b = Bar()
b()
73. 如何判断一个对象是函数还是方法?
答:看代码已经结果就懂了
from types import MethodType, FunctionType
class Bar:
def foo(self):
pass
def foo2():
pass
def run():
print(“foo 是函数”, isinstance(Bar().foo, FunctionType))
print(“foo 是方法”, isinstance(Bar().foo, MethodType))
print(“foo2 是函数”, isinstance(foo2, FunctionType))
print(“foo2 是方法”, isinstance(foo2, MethodType))
if name == ‘main‘:
run()
输出
foo 是函数 False
foo 是方法 True
foo2 是函数 True
foo2 是方法 False
74. @classmethod 和 @staticmethod 用法和区别
答: 相同之处:@staticmethod 和@classmethod 都可以直接类名.方法名()来调用,不用在示例化一个类。 @classmethod 我们要写一个只在类中运行而不在实例中运行的方法。如果我们想让方法不在实例中运行,可以这么做:
def iget_no_of_instance(ins_obj):
return ins_obj.class.no_inst
class Kls(object):
no_inst = 0
def init(self):
Kls.no_inst = Kls.no_inst + 1
ik1 = Kls()
ik2 = Kls()
print(iget_no_of_instance(ik1))
@staticmethod 经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法
IND = ‘ON’
class Kls(object):
def init(self, data):
self.data = data
@staticmethod
def check_ind():
return (IND == ‘ON’)
def do_reset(self):
if self.check_ind():
print(‘Reset done for:’, self.data)
def set_db(self):
if self.check_ind():
self.db = ‘New db connection’
print(‘DB connection made for: ‘, self.data)
ik1 = Kls(12)
ik1.do_reset()
ik1.set_db()
75. Python 中的接口如何实现?
答: 接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数。但是在 Python 中根本就没有一个叫做 interface 的关键字,如果非要去模仿接口的概念,可以使用抽象类来实现。抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。使用 abc 模块来实现抽象类。
76. Python 中的反射了解么?
答:Python 的反射机制设定较为简单,一共有四个关键函数分别是 getattr、hasattr、setattr、delattr。
77. metaclass 作用?以及应用场景?
答: metaclass 即元类,metaclass 是类似创建类的模板,所有的类都是通过他来 create 的(调用new),这使得你可以自由的控制创建类的那个过程,实现你所需要的功能。 我们可以使用元类创建单例模式和实现 ORM 模式。
78. hasattr()、getattr()、setattr() 的用法
答:这三个方法属于 Python 的反射机制里面的,hasattr 可以判断一个对象是否含有某个属性,getattr 可以充当 get 获取对象属性的作用。而 setattr 可以充当 person.name = “liming”的赋值操作。代码示例如下:
class Person():
def init(self):
self.name = “liming”
self.age = 12
def show(self):
print(self.name)
print(self.age)
def set_name(self):
setattr(Person, “sex”, “男”)
def get_name(self):
print(getattr(self, “name”))
print(getattr(self, “age”))
print(getattr(self, “sex”))
def run():
if hasattr(Person, “show”):
print(“判断 Person 类是否含有 show 方法”)
Person().set_name()
Person().get_name()
if name == ‘main‘:
run()
79. 请列举你知道的 Python 的魔法方法及用途。
答:
1 __init__:
类的初始化方法。它获取任何传给构造器的参数(比如我们调用 x = SomeClass(10, ‘foo’) , __init__就会接到参数 10 和 ‘foo’ 。 __init__在 Python 的类定义中用的最多。
2 __new__:
__new__是对象实例化时第一个调用的方法,它只取下 cls 参数,并把其他参数传给 init 。 __new__很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候.
3 __del__:
__new__和 __init__是对象的构造器, __del__是对象的销毁器。它并非实现了语句 del x (因此该语句不等同于 x.del())。而是定义了当对象被垃圾回收时的行为。 当对象需要在销毁时做一些处理的时候这个方法很有用,比如 socket 对象、文件对象。但是需要注意的是,当 Python 解释器退出但对象仍然存活的时候,__del__并不会 执行。 所以养成一个手工清理的好习惯是很重要的,比如及时关闭连接。
80. 如何知道一个 Python 对象的类型?
答:可以通过 type 方法
81. Python 的传参是传值还是传址?
答:Python 中的传参即不是传值也不是传地址,传的是对象的引用。
82. Python 中的元类 (metaclass) 使用举例
答:可以使用元类实现一个单例模式,代码如下
class Singleton(type):
def init(self, *args, **kwargs):
print(“in init“)
self.__instance = None
super(Singleton, self).init(*args, **kwargs)
def call(self, *args, **kwargs):
print(“in call“)
if self.__instance is None:
self.__instance = super(Singleton, self).call(*args, **kwargs)
return self.__instance
class Foo(metaclass=Singleton):
pass # 在代码执行到这里的时候,元类中的__new__方法和__init__方法其实已经被执行了,而不是在 Foo 实例化的时候执行。且仅会执行一次。
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)
83. 简述 any() 和 all() 方法
答: any(x):判断 x 对象是否为空对象,如果都为空、0、false,则返回 false,如果不都为空、0、false,则返回 true。 all(x):如果 all(x) 参数 x 对象的所有元素不为 0、’’、False 或者 x 为空对象,则返回 True,否则返回 False。
84. filter 方法求出列表所有奇数并构造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
答
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda x: x % 2 == 1, a)))
其实现在不推荐使用 filter,map 等方法了,一般列表生成式就可以搞定了。
85. 什么是猴子补丁?
答: 猴子补丁(monkey patching):在运行时动态修改模块、类或函数,通常是添加功能或修正缺陷。猴子补丁在代码运行时内存中)发挥作用,不会修改源码,因此只对当前运行的程序实例有效。因为猴子补丁破坏了封装,而且容易导致程序与补丁代码的实现细节紧密耦合,所以被视为临时的变通方案,不是集成代码的推荐方式。大概是下面这样的一个效果
def post():
print(“this is post”)
print(“想不到吧”)
class Http():
@classmethod
def get(self):
print(“this is get”)
def main():
Http.get=post #动态的修改了 get 原因的功能,
if name == ‘main‘:
main()
Http.get()
86. 在 Python 中是如何管理内存的?
答: 垃圾回收:Python 不像 C++,Java 等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对 Python 语言来讲,对象的类型和内存都是在运行时确定的。这也是为什么我们称 Python 语言为动态类型的原因(这里我们把动态类型可以简单的归结为对变量内存地址的分配是在运行时自动判断变量类型并对变量进行赋值)。
引用计数:Python 采用了类似 Windows 内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。当变量被绑定在一个对象上的时候,该变量的引用计数就是 1,(还有另外一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为 0 的时候,该对就会被回收。
内存池机制 Python 的内存机制以金字塔行,1、2 层主要有操作系统进行操作
第 0 层是 C 中的 malloc,free 等内存分配和释放函数进行操作
第 1 层和第 2 层是内存池,有 Python 的接口函数 PyMem_Malloc 函数实现,当对象小于 256K 时有该层直接分配内存
第 3 层是最上层,也就是我们对 Python 对象的直接操作
在 C 中如果频繁的调用 malloc 与 free 时,是会产生性能问题的.再加上频繁的分配与释放小块的内存会产生内存碎片。Python 在这里主要干的工作有:
如果请求分配的内存在 1~256 字节之间就使用自己的内存管理系统,否则直接使用 malloc。
这里还是会调用 malloc 分配内存,但每次会分配一块大小为 256k 的大块内存。
经由内存池登记的内存到最后还是会回收到内存池,并不会调用 C 的 free 释放掉以便下次使用。对于简单的 Python 对象,例如数值、字符串,元组(tuple 不允许被更改)采用的是复制的方式(深拷贝?),也就是说当将另一个变量 B 赋值给变量 A 时,虽然 A 和 B 的内存空间仍然相同,但当 A 的值发生变化时,会重新给 A 分配空间,A 和 B 的地址变得不再相同。
87. 当退出 Python 时是否释放所有内存分配?
答:不是的,循环引用其他对象或引用自全局命名空间的对象的模块,在 Python 退出时并非完全释放。
另外,也不会释放 c 库保留的内存部分
正则表达式
88. (1)使用正则表达式匹配出<h1>www.baidu.com中的地址(2)a=”张明 98 分”,用 re.sub,将 98 替换为 100
答: 第一问答案
import re
source = “
www.baidu.com
“pat = re.compile(“
(.*?)
“)print(pat.findall(source)[0])
第二问答案
import re
s = “张明 98 分”
print(re.sub(r”\d+”,”100”,s))
89. 正则表达式匹配中(.)和(.?)匹配区别?
答:(.) 为贪婪模式极可能多的匹配内容 ,(.?) 为非贪婪模式又叫懒惰模式,一般匹配到结果就好,匹配字符的少为主,示例代码如下
import re
s = “
pat1 = re.compile(r”<div>(.*?)</div>”)
print(pat1.findall(s))
pat2 = re.compile(r”<div>(.*)</div>”)
print(pat2.findall(s))
输出
[‘文本 1’, ‘文本 2’]
[‘文本 1
90. 写一段匹配邮箱的正则表达式
答:关于邮箱的匹配这个还真的是一个永恒的话题。
电子邮件地址有统一的标准格式:用户名@服务器域名。用户名表示邮件信箱、注册名或信件接收者的用户标识,@符号后是你使用的邮件服务器的域名。@可以读成“at”,也就是“在”的意思。整个电子邮件地址可理解为网络中某台服务器上的某个用户的地址。
用户名,可以自己选择。由字母 a~z(不区分大小写)、数字 0~9、点、减号或下划线组成;只能以数字或字母开头和结尾。
与你使用的网站有关,代表邮箱服务商。例如网易的有@163.com 新浪有@vip.sina.com 等。
网上看到了各种各样的版本,都不确定用哪个,于是自己简单的总结了一个。大家有更好的欢迎留言。
r”^[a-zA-Z0-9]+[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$”
下面解释上面的表达式
首先强调一点关于\w 的含义,\w 匹配英文字母和俄语字母或数字或下划线或汉字。
注意^[]和[^]的区别,[]表示字符集合,^[]表示已[]内的任意字符集开始,[^]表示。
^[a-zA-Z0-9]+:这里注意^[]和[^]的,第一个^表示已什么开头,第二个[]的^表示不等于[]内。所以这段表示以英文字母和数字开头,后面紧跟的+,限定其个数>=1 个。
[a-zA-Z0-9.+-]+:表示匹配英文字母和数字开头以及.+-, 的任意一个字符,并限定其个数>=1 个。为了考虑@前面可能出现.+-(但是不在开头出现)。
@就是邮箱必备符号了
@[a-zA-Z0-9-]+.:前面的不用说了,后面的.表示.转义了,也是必备符号。
[ a-zA-Z0-9-.]+:$符表示以什么结束,这里表示以英文字和数字或 -. 1 个或多个结尾。
来个例子验证一波:
import re
plt=re.compile(r”^[a-zA-Z0-9]+[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$”)
b=plt.findall(‘adas+fefe.we@qq.com.cn’)
print(b)
网上找了个验证邮件地址的通用正则表达式(符合 RFC 5322 标准)
(?:[a-z0-9!#$%&’+/=?^_{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_
{|}~-]+)|”(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])“)@(?:(?:a-z0-9?.)+a-z0-9?|[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-][a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])
其他内容
91. 解释一下 Python 中 pass 语句的作用?
答:pass 实际上就是一个占位符,在写一个函数但是不确定里面写啥的时候,这个时候可以使用 pass。示例如下
def foo():
pass
92. 简述你对 input()函数的理解
答:在 Python3 中 input 函数可以接收用户输入的字符串。
然后根据程序的需要转换成所需格式即可。
93. Python 中的 is 和==
答:先说==它的作用是判断两个对象的值是否相同,然后说 is。is 表示的谁是谁,这也就意味着对象完全相等。我们知道一个对象有各自的内存地址和对应的值,当内存地址和值都相同的时候使用 is 可以得到结果 True。另外需要注意的下面两点特殊的情况。
这些变量很可能在许多程序中使用。 通过池化这些对象,Python 可以防止对一致使用的对象进行内存分配调用。
1.介于数字-5 和 256 之间的整数
2.字符串仅包含字母、数字或下划线
94. Python 中的作用域
答:
Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定
当 Python 遇到一个变量的话它会按照这的顺序进行搜索
本地作用域(Local)—>当前作用域被嵌入的本地作用域(Enclosing locals)—>全局/模块作用域(Global)—>内置作用域(Built-in)
95. 三元运算写法和应用场景?
答:Python 中的三元运算又称三目运算,是对简单的条件语句的简写。 是一种比较 Pythonic 的学法,形式为:val = 1 if 条件成立 else 2 代码示例如下:
a = 2
b = 5
普通写法
if a > b:
val = True
else:
val = False
改为三元运算符后
val = a if a > b else b
print(val) # 5
96. 了解 enumerate 么?
答:enumerate 可以在迭代一个对象的时候,同时获取当前对象的索引和值。 代码示例如下
from string import ascii_lowercase
s = ascii_lowercase
for index, value in enumerate(s):
print(index, value)
97. 列举 5 个 Python 中的标准模块
答: pathlib:路径操作模块,比 os 模块拼接方便。 urllib:网络请求模块,包括对 url 的结构解析。 asyncio: Python 的异步库,基于事件循环的协程模块。 re:正则表达式模块。 itertools:提供了操作生成器的一些模块。
98. 如何在函数中设置一个全局变量
答:
通过使用 global 对全局变量进行修改。
n = 0
def foo():
global n
n = 100
foo()
print(n)
x = 0
之前我在视频教程中对这块做了个讲解,具体点击下方链接 https://www.bilibili.com/video/av50865713
99. pathlib 的用法举例
答:pathlib 可以对文件以及文件的其他属性进行操作。比较喜欢的一点是路径拼接符”/“的使用,之前在公众号中写过 pathlib 一些其他的用法这里就不一一例举了。
100. Python 中的异常处理,写一个简单的应用场景
答: 比如在计算除法中出现为 0 的情况出现异常
try:
1 / 0
except ZeroDivisionError as e:
print(e.args)
101. Python 中递归的最大次数,那如何突破呢?
答:Python 有递归次数限制,默认最大次数为 1000。通过下面的代码可以突破这个限制
import sys
sys.setrecursionlimit(1500) # set the maximum depth as 1500
另外需要注意的是 sys.setrecursionlimit() 只是修改解释器在解释时允许的最大递归次数,此外,限制最大递归次数的还和操作系统有关。
102. 什么是面向对象的 mro
答:Python 是支持面向对象编程的,同时也是支持多重继承的。一般我们通过调用类对象的 mro()方法获取其继承关系。
103. isinstance 作用以及应用场景?
答:isinstance 是判断一个对象是否为另一个对象的子类的,例如我们知道在 Python3 中 bool 类型其实是 int 的子类,所以我们可以对其检测。
print(isinstance(True,int))
104. 什么是断言?应用场景?
答:在 Python 中是断言语句 assert 实现此功能,一般在表达式为 True 的情况下,程序才能通过。
#author:陈祥安
#公众号:Python 学习开发
#assert()方法,断言成功,则程序继续执行,断言失败,则程序报错
断言能够帮助别人或未来的你理解代码,
找出程序中逻辑不对的地方。一方面,
断言会提醒你某个对象应该处于何种状态,
另一方面,如果某个时候断言为假,
会抛出 AssertionError 异常,很有可能终止程序。
def foo(a):
assert a==2,Exception(“不等于 2”)
print(“ok”,a)
if name == ‘main‘:
foo(1)
105. lambda 表达式格式以及应用场景?
答:lambda 表达式其实就是一个匿名函数,在函数编程中经常作为参数使用。 例子如下
a = [(‘a’,1),(‘b’,2),(‘c’,3),(‘d’,4)]
a_1 = list(map(lambda x:x[0],a))
106. 新式类和旧式类的区别
答:Python 2.x 中默认都是经典类,只有显式继承了 object 才是新式类,Python 3.x 中默认都是新式类,经典类被移除,不必显式的继承 object。 新式类都从 object 继承,经典类不需要。 新式类的 MRO(method resolution order 基类搜索顺序)算法采用 C3 算法广度优先搜索,而旧式类的 MRO 算法是采用深度优先搜索。 新式类相同父类只执行一次构造函数,经典类重复执行多次。
107. dir()是干什么用的?
答:当在使用某一个对象不知道有哪些属性或者方法可以使用时,此时可以通过 dir() 方法进行查看。
108. 一个包里有三个模块,demo1.py、demo2.py、demo3.py,但使用 from tools import *导入模块时,如何保证只有 demo1、demo3 被导入了。
答: 增加_init_.py 文件,并在文件中增加:
all = [‘demo1’,’demo3’]
109. 列举 5 个 Python 中的异常类型以及其含义
答:
AttributeError 对象没有这个属性
NotImplementedError 尚未实现的方法
StopIteration 迭代器没有更多的值
TypeError 对类型无效的操作
IndentationError 缩进错误
110. copy 和 deepcopy 的区别是什么?
答: copy.copy()浅拷贝,只拷贝父对象,不会拷贝对象的内部的子对象。 copy.deepcopy()深拷贝,拷贝对象及其子对象。
111. 代码中经常遇到的*args, **kwargs 含义及用法。
答: 在函数定义中使用 *args 和**kwargs 传递可变长参数。 *args 用来将参数打包成 tuple 给函数体调用。 **kwargs 打包关键字参数成 dict 给函数体调用。
112. Python 中会有函数或成员变量包含单下划线前缀和结尾,和双下划线前缀结尾,区别是什么?
答: “单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量; “双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
以单下划线开头(_foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用“from xxx import *”而导入;以双下划线开头的(__foo)代表类的私有成员;
以双下划线开头和结尾的(_foo)代表 Python 里特殊方法专用的标识,如 _init()代表类的构造函数。
代码示例
class Person:
“””docstring for ClassName”””
def init(self):
self.__age = 12
self._sex = 12
def _sex(self):
return “男”
def set_age(self,age):
self.__age = age
def get_age(self):
return self.__age
if name == ‘main‘:
p=Person()
print(p._sex)
#print(p.__age)
#Python 自动将__age 解释成 _Person__age,于是我们用 _Person__age 访问,这次成功。
print(p._Person__age)
113. w、a+、wb 文件写入模式的区别
答: w 表示写模式支持写入字符串,如果文件存在则覆盖。 a+ 和 w 的功能类型不过如果文件存在的话内容不会覆盖而是追加。 wb 是写入二进制字节类型的数据。
114. 举例 sort 和 sorted 的区别
答: 相同之处 sort 和 sorted 都可以对列表元素排序,sort() 与 sorted() 的不同在于,sort 是在原位重新排列列表,而 sorted() 是产生一个新的列表。 sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
115. 什么是负索引?
答:负索引一般表示的是从后面取元素。
116. pprint 模块是干什么的?
答:pprint 是 print 函数的美化版,可以通过 import pprint 导入。示例如下
import pprint
pprint.pprint(“this is pprint”)
117. 解释一下 Python 中的赋值运算符
答:通过下面的代码列举出所有的赋值运算符
a=7
a+=1
print(a)
a-=1
print(a)
a*=2
print(a)
a/=2
print(a)
a**=2
print(a)
a//=3
print(a)
a%=4
print(a)
118. 解释一下 Python 中的逻辑运算符
答:Python 中有三个逻辑运算符:and、or、not
print(False and True) #False
print(7<7 or True) #True
print(not 2==2) #False
119. 讲讲 Python 中的位运算符
答:按位运算符是把数字看作二进制来进行计算的。Python 中的按位运算法则如下:
下表中变量 a 为 60,b 为 13,二进制格式如下:
a = 0011 1100
b = 0000 1101
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a = 1100 0011
enter image description here
120. 在 Python 中如何使用多进制数字?
答: 我们在 Python 中,除十进制外还可以使用二进制、八进制和十六进制
二进制数字由 0 和 1 组成,我们使用 0b 或 0B 前缀表示二进制数
print(int(0b1010))#10
使用 bin()函数将一个数字转换为它的二进制形式
print(bin(0xf))#0b1111
八进制数由数字 0-7 组成,用前缀 0o 或 0O 表示 8 进制数
print(oct(8))#0o10
十六进数由数字 0-15 组成,用前缀 0x 或者 0X 表示 16 进制数
print(hex(16))#0x10
print(hex(15))#0xf
121. 怎样声明多个变量并赋值?
答:Python 是支持多个变量赋值的,代码示例如下
#对变量 a,b,c 声明并赋值
a,b,c = 1,2,3
算法和数据结构
122. 已知:
AList = [1,2,3]
BSet = {1,2,3}
(1) 从 AList 和 BSet 中 查找 4,最坏时间复杂度哪个大? (2) 从 AList 和 BSet 中 插入 4,最坏时间复杂度哪个大?
答: (1) 对于查找,列表和集合的最坏时间复杂度都是 O(n),所以一样的。 (2) 列表操作插入的最坏时间复杂度为 o(n),集合为 o(1),所以 Alist 大。 set 是哈希表所以操作的复杂度基本上都是 o(1)。
123. 用 Python 实现一个二分查找的函数
答:
def binary_search(arr, target):
n = len(arr)
left = 0
right = n-1
while left <= right :
mid = (left + right)//2
if arr[mid] < target:
left = mid + 1
elif arr[mid] > target:
right = mid - 1
else:
print(f”index:{mid},value:{arr[mid]}”)
return True
return False
if name == ‘main‘:
l = [1,3,4,5,6,7,8]
binary_search(l,8)
124. Python 单例模式的实现方法
答:实现单例模式的方法有多种,之前再说元类的时候用 call 方法实现了一个单例模式,另外 Python 的模块就是一个天然的单例模式,这里我们使用 new 关键字来实现一个单例模式。
“””
通过 new 函数实现简单的单例模式。
“””
class Book:
def new(cls, title):
if not hasattr(cls, “_ins”):
cls._ins = super().new(cls)
print(‘in new‘)
return cls._ins
def init(self, title):
print(‘in init‘)
super().init()
self.title = title
if name == ‘main‘:
b = Book(‘The Spider Book’)
b2 = Book(‘The Flask Book’)
print(id(b))
print(id(b2))
print(b.title)
print(b2.title)
125. 使用 Python 实现一个斐波那契数列
答: 斐波那契数列:数列从第 3 项开始,每一项都等于前两项之和。
def fibonacci(num):
“””
获取指定位数的列表
:param num:
:return:
“””
a, b = 0, 1
l = []
for i in range(num):
a, b = b, a + b
l.append(b)
return l
if name == ‘main‘:
print(fibonacci(10))
126. 找出列表中的重复数字
答:
“””
从头扫到尾,只要当前元素值与下标不同,就做一次判断,numbers[i]与 numbers[numbers[i]],
相等就认为找到了重复元素,返回 true,否则就交换两者,继续循环。直到最后还没找到认为没找到重复元素。
“””
-- coding:utf-8 --
class Solution:
def duplicate(self, numbers):
“””
:param numbers:
:return:
"""
if numbers is None or len(numbers) <= 1:
return False
use_set = set()
duplication = {}
for index, value in enumerate(numbers):
if value not in use_set:
use_set.add(value)
else:
duplication[index] = value
return duplication
if name == ‘main‘:
s = Solution()
d = s.duplicate([1, 2, -3, 4, 4, 95, 95, 5, 2, 2, -3, 7, 7, 5])
print(d)
127. 找出列表中的单个数字
答:
def find_single(l :list):
result = 0
for v in l:
result ^= v
if result == 0:
print(“没有落单元素”)
else:
print(“落单元素” ,result)
if name == ‘main‘:
l = [1,2,3,4,5,6,2,3,4,5,6]
find_single(l)
128. 写一个冒泡排序
答:
“””
冒泡排序
“””
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
if name == ‘main‘:
l = [1, 2, 3, 4, 5, 55, 6, 3, 4, 5, 6]
bubble_sort(l)
print(l)
129. 写一个快速排序
答:
“””
快速排序
“””
def quick_sort(arr, first, last):
if first >= last:
return
mid_value = arr[first]
low = first
high = last
while low < high:
while low < high and arr[high] >= mid_value:
high -= 1 # 游标左移
arr[low] = arr[high]
while low < high and arr[low] < mid_value:
low += 1
arr[high] = arr[low]
arr[low] = mid_value
quick_sort(arr, first, low - 1)
quick_sort(arr, low + 1, last)
if name == ‘main‘:
l = [1, 2, 3, 4, 5, 55, 6, 3, 4, 5, 6]
quick_sort(l, 0, len(l) - 1)
print(l)
130. 写一个拓扑排序
答:
“””
拓扑排序
对应于该图的拓扑排序。每一个有向无环图都至少存在一种拓扑排序。
“””
import pysnooper
from typing import Mapping
@pysnooper.snoop()
def topological_sort(graph: Mapping):
in_degrees = {‘a’: 0, ‘b’: 0, ‘c’: 0, ‘d’: 0, ‘e’: 0, ‘f’: 0}
in_degrees = dict((u, 0) for u in graph)
for u in graph:
for v in graph[u]: # 根据键找出值也就是下级节点
in_degrees[v] += 1 # 对获取到的下级节点的入度加 1
循环结束之后的结果: {‘a’: 0, ‘b’: 1, ‘c’: 1, ‘d’: 2, ‘e’: 1, ‘f’: 4}
Q = [u for u in graph if in_degrees[u] == 0] # 入度为 0 的节点
in_degrees_zero = []
while Q:
u = Q.pop() # 默认从最后一个移除
in_degrees_zero.append(u) # 存储入度为 0 的节点
for v in graph[u]:
in_degrees[v] -= 1 # 删除入度为 0 的节点,以及移除其指向
if in_degrees[v] == 0:
Q.append(v)
return in_degrees_zero
if name == ‘main‘:
用字典的键值表示图的节点之间的关系,键当前节点。值是后续节点。
graph_dict = {
‘a’: ‘bf’, # 表示 a 指向 b 和 f
‘b’: ‘cdf’,
‘c’: ‘d’,
‘d’: ‘ef’,
‘e’: ‘f’,
‘f’: ‘’
}
t = topological_sort(graph_dict)
print(t)
131. Python 实现一个二进制计算
答:
“””
二进制加法
“””
def binary_add(a: str, b: str):
return bin(int(a, 2) + int(b, 2))[2:]
if name == ‘main‘:
num1 = input(“输入第一个数,二进制格式:\n”)
num2 = input(“输入第二个数,二进制格式:\n”)
print(binary_add(num1, num2))
132. 有一组“+”和“-”符号,要求将“+”排到左边,“-”排到右边,写出具体的实现方法。
答:
“””
有一组“+”和“-”符号,要求将“+”排到左边,“-”排到右边,写出具体的实现方法。
如果让+等于 0,-等于 1 不就是排序了么。
“””
from collections import deque
from timeit import Timer
s = “++++++—-+++—-“
方法一
def func1():
new_s = s.replace(“+”, “0”).replace(“-“, “1”)
result = “”.join(sorted(new_s)).replace(“0”, “+”).replace(“1”, “-“)
return result
方法二
def func2():
q = deque()
left = q.appendleft
right = q.append
for i in s:
if i == “+”:
left(“+”)
elif i == “-“:
right(“-“)
def func3():
data = list(s)
start_index = 0
end_index = 0
count = len(s)
while start_index + end_index < count:
if data[start_index] == ‘-‘:
data[start_index], data[count - end_index - 1] = data[count - end_index - 1], data[start_index]
end_index += 1
else:
start_index += 1
return “”.join(data)
if name == ‘main‘:
timer1 = Timer(“func1()”, “from main import func1”)
print(“func1”, timer1.timeit(1000000))
timer2 = Timer(“func2()”, “from main import func2”)
print(“func2”, timer2.timeit(1000000))
timer3 = Timer(“func3()”, “from main import func3”)
print(“func3”, timer3.timeit(1000000))
1000000 测试结果
func1 1.39003764
func2 1.593012875
func3 3.3487415590000005
func1 的方式最优,其次是 func2
133. 单链表反转
答:
“””
单链表反转
“””
class Node:
def init(self, val=None):
self.val = val
self.next = None
class SingleLinkList:
def init(self, head=None):
“””链表的头部”””
self._head = head
def add(self, val: int):
“””
给链表添加元素
:param val: 传过来的数字
:return:
“””
# 创建一个节点
node = Node(val)
if self._head is None:
self._head = node
else:
cur = self._head
while cur.next is not None:
cur = cur.next # 移动游标
cur.next = node # 如果 next 后面没了证明以及到最后一个节点了
def traversal(self):
if self._head is None:
return
else:
cur = self._head
while cur is not None:
print(cur.val)
cur = cur.next
def size(self):
“””
获取链表的大小
:return:
“””
count = 0
if self._head is None:
return count
else:
cur = self._head
while cur is not None:
count += 1
cur = cur.next
return count
def reverse(self):
“””
单链表反转
思路:
让 cur.next 先断开即指向 none,指向设定 pre 游标指向断开的元素,然后
cur.next 指向断开的元素,再把开始 self._head 再最后一个元素的时候.
:return:
“””
if self._head is None or self.size() == 1:
return
else:
pre = None
cur = self._head
while cur is not None:
post = cur.next
cur.next = pre
pre = cur
cur = post
self._head = pre # 逆向后的头节点
if name == ‘main‘:
single_link = SingleLinkList()
single_link.add(3)
single_link.add(5)
single_link.add(6)
single_link.add(7)
single_link.add(8)
print(“对链表进行遍历”)
single_link.traversal()
print(f”size:{single_link.size()}”)
print(“对链表进行逆向操作之后”)
single_link.reverse()
single_link.traversal()
134. 交叉链表求交点
答:
Definition for singly-linked list.
class ListNode:
def init(self, x):
self.val = x
self.next = None
class Solution:
def getIntersectionNode(self, headA, headB):
“””
:tye head1, head1: ListNode
:rtye: ListNode
“””
if headA is not None and headB is not None:
cur1, cur2 = headA, headB
while cur1 != cur2:
cur1 = cur1.next if cur1 is not None else headA
cur2 = cur2.next if cur2 is not None else headB
return cur1
cur1、cur2,2 个指针的初始位置是链表 headA、headB 头结点,cur1、cur2 两个指针一直往后遍历。 直到 cur1 指针走到链表的末尾,然后 cur1 指向 headB; 直到 cur2 指针走到链表的末尾,然后 cur2 指向 headA; 然后再继续遍历; 每次 cur1、cur2 指向 None,则将 cur1、cur2 分别指向 headB、headA。 循环的次数越多,cur1、cur2 的距离越接近,直到 cur1 等于 cur2。则是两个链表的相交点。
135. 用队列实现栈
答: 下面代码分别使用 1 个队列和 2 个队列实现了栈。
from queue import Queue
#使用 2 个队列实现
class MyStack:
def init(self):
“””
Initialize your data structure here.
“””
# q1 作为进栈出栈,q2 作为中转站
self.q1 = Queue()
self.q2 = Queue()
def push(self, x):
“””
Push element x onto stack.
:type x: int
:rtype: void
“””
self.q1.put(x)
def pop(self):
“””
Removes the element on top of the stack and returns that element.
:rtype: int
“””
while self.q1.qsize() > 1:
self.q2.put(self.q1.get()) # 将 q1 中除尾元素外的所有元素转到 q2 中
if self.q1.qsize() == 1:
res = self.q1.get() # 弹出 q1 的最后一个元素
self.q1, self.q2 = self.q2, self.q1 # 交换 q1,q2
return res
def top(self):
“””
Get the top element.
:rtype: int
“””
while self.q1.qsize() > 1:
self.q2.put(self.q1.get()) # 将 q1 中除尾元素外的所有元素转到 q2 中
if self.q1.qsize() == 1:
res = self.q1.get() # 弹出 q1 的最后一个元素
self.q2.put(res) # 与 pop 唯一不同的是需要将 q1 最后一个元素保存到 q2 中
self.q1, self.q2 = self.q2, self.q1 # 交换 q1,q2
return res
def empty(self):
“””
Returns whether the stack is empty.
:rtype: bool
“””
return not bool(self.q1.qsize() + self.q2.qsize()) # 为空返回 True,不为空返回 False
#使用 1 个队列实现
class MyStack2(object):
def init(self):
“””
Initialize your data structure here.
“””
self.sq1 = Queue()
def push(self, x):
“””
Push element x onto stack.
:type x: int
:rtype: void
“””
self.sq1.put(x)
def pop(self):
“””
Removes the element on top of the stack and returns that element.
:rtype: int
“””
count = self.sq1.qsize()
if count == 0:
return False
while count > 1:
x = self.sq1.get()
self.sq1.put(x)
count -= 1
return self.sq1.get()
def top(self):
“””
Get the top element.
:rtype: int
“””
count = self.sq1.qsize()
if count == 0:
return False
while count:
x = self.sq1.get()
self.sq1.put(x)
count -= 1
return x
def empty(self):
“””
Returns whether the stack is empty.
:rtype: bool
“””
return self.sq1.empty()
if name == ‘main‘:
obj = MyStack2()
obj.push(1)
obj.push(3)
obj.push(4)
print(obj.pop())
print(obj.pop())
print(obj.pop())
print(obj.empty())
136. 找出数据流的中位数
答:对于一个升序排序的数组,中位数为左半部分的最大值,右半部分的最小值,而左右两部分可以是无需的,只要保证左半部分的数均小于右半部分即可。因此,左右两半部分分别可用最大堆、最小堆实现。
如果有奇数个数,则中位数放在左半部分;如果有偶数个数,则取左半部分的最大值、右边部分的最小值之平均值。
分两种情况讨论: 当目前有偶数个数字时,数字先插入最小堆,然后选择最小堆的最小值插入最大堆(第一个数字插入左半部分的最小堆)。
当目前有奇数个数字时,数字先插入最大堆,然后选择最大堆的最大值插入最小堆。 最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。 最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
-- coding:utf-8 --
from heapq import *
class Solution:
def init(self):
self.maxheap = []
self.minheap = []
def Insert(self, num):
if (len(self.maxheap) + len(self.minheap)) & 0x1: # 总数为奇数插入最大堆
if len(self.minheap) > 0:
if num > self.minheap[0]: # 大于最小堆里的元素
heappush(self.minheap, num) # 新数据插入最小堆
heappush(self.maxheap, -self.minheap[0]) # 最小堆中的最小插入最大堆
heappop(self.minheap)
else:
heappush(self.maxheap, -num)
else:
heappush(self.maxheap, -num)
else: # 总数为偶数 插入最小堆
if len(self.maxheap) > 0: # 小于最大堆里的元素
if num < -self.maxheap[0]:
heappush(self.maxheap, -num) # 新数据插入最大堆
heappush(self.minheap, -self.maxheap[0]) # 最大堆中的最大元素插入最小堆
heappop(self.maxheap)
else:
heappush(self.minheap, num)
else:
heappush(self.minheap, num)
def GetMedian(self, n=None):
if (len(self.maxheap) + len(self.minheap)) & 0x1:
mid = self.minheap[0]
else:
mid = (self.minheap[0] - self.maxheap[0]) / 2.0
return mid
if name == ‘main‘:
s = Solution()
s.Insert(1)
s.Insert(2)
s.Insert(3)
s.Insert(4)
print(s.GetMedian())
137. 二叉搜索树中第 K 小的元素
答: ??二叉搜索树(Binary Search Tree),又名二叉排序树(Binary Sort Tree)。 ? 二叉搜索树是具有有以下性质的二叉树:?
若左子树不为空,则左子树上所有节点的值均小于或等于它的根节点的值。
若右子树不为空,则右子树上所有节点的值均大于或等于它的根节点的值。
左、右子树也分别为二叉搜索树。
二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。所以对其遍历一个节点就进行计数,计数达到 k 的时候就结束。
class TreeNode:
def init(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
count = 0
nodeVal = 0
def kthSmallest(self, root, k):
“””
:type root: TreeNode
:type k: int
:rtype: int
“””
self.dfs(root, k)
return self.nodeVal
def dfs(self, node, k):
if node != None:
self.dfs(node.left, k)
self.count = self.count + 1
if self.count == k:
self.nodeVal = node.val
# 将该节点的左右子树置为 None,来结束递归,减少时间复杂度
node.left = None
node.right = None
self.dfs(node.right, k)
爬虫相关
138. 在 requests 模块中,requests.content 和 requests.text 什么区别
答: requests.content 获取的是字节,requests.text 获取的是文本内容。
139. 简要写一下 lxml 模块的使用方法框架
答:
from lxml import html
source=’’’