Python 面试准备

转载知乎

基础知识

1. 列出 5 个常用 Python 标准库?

time os sys glob timeit datetime calendar random math operator

re urllib threading multiprocessing queue equests,virtualenv,selenium,scrapy,xadmin,celery,hashlib,md5。 20个必不可少的Python库也是基本的第三方库 Numpy,Scipy,Pandas queue

os 模块

os 模块
# coding:utf-8 # os 模块 处理文件和目录 import os # 1. os.getcwd os.getcwd() # 返回当前的工作目录 # 2. os.listdir os.listdir('d:') # 目录下的所有文件(夹) # 3. os.path.exists os.path.exists(filepath) # 判断文件是否存在 # 4. os.remove os.remove(filepath) # 删除文件

sys 模块

sys 模块
# coding:utf-8 # sys 模块 提供对解释器使用或维护的一些变量的访问,以及与解释器强烈交互的函数 import sys # 1. sys.argv print(sys.argv[1]) # 脚本执行参数列表,0为文件名 # 2. sys.exit(n) try: sys.exit(1) # 异常退出程序 except SystemExit,value: exitfunc(value) # 3. sys.modules print(sys.modules.keys()) # sys.modules 全局字典 自动记录模块 print(sys.modules.values()) print(sys.modules["os"]) # 4. sys.stdin\stdout\stderr # 与解释器的标准输入,输出和错误流相对应的文件对象 支持read()、write()和flush()等操作

glob 模块

glob 模块
# coding:utf-8 # glob 模块 提供了一个函数用于从目录通配符搜索中生成文件列表 import glob # 1. glob.glob glob.glob('*.py')

random 模块

random 模块
# coding:utf-8 # random 模块 随机数 import random print( random.randint(1,10) ) # 返回 1 到 10 的一个整数型随机数 print( random.random() ) # 返回 0 到 1 之间的随机浮点数 print( random.uniform(1, 10) ) # 返回 1 到 10 之间的随机浮点数 print( random.uniform(1.1,5.4) ) # 返回 1.1 到 5.4 之间的随机浮点数,区间可以不是整数 print( random.choice('abcdefg') ) # 从序列中随机选取一个元素 print( random.sample('zyxwvutsrqponmlkjihgfedcba',5)) # 多个字符中生成指定数量的随机字符 print( random.randrange(1,100,2) ) # 返回 从1到100的间隔为2的随机整数 a=[1,3,5,6,7] # 将序列a中的元素顺序打乱 random.shuffle(a) print(a)

math 模块

math 模块
# coding:utf-8 # math 模块 import math math.ceil(x) 上入整数 math.exp(x) e的x次幂 math.fabs(x) 绝对值 浮点数 math.floor(x) 下舍整数 math.log(x[,y]) 以e[y]为基数的x的对数 math.log2(x)2为基数的x的对数 math.log10(x)10为基数的x的对数 math.modf(x) 整数部分与小数部分 math.sqrt(x) x的平方根 math.atan2(y,x) 返回给定的 X 及 Y 坐标值的反正切值。 math.hypot(x,y) 返回给定的 X 及 Y 坐标值的反正切值。 math.degress(x) 将弧度转换为角度,如degrees(math.pi/2) , 返回90.0 math.radians(x) 将角度转换为弧度 math.copysign(x,y) 把y的正负号加到x前面 math.factorial(x) x的阶乘 math.fmod(x,y) 得到x/y的余数,其值是一个浮点数 math.frexp(x) 返回一个元组(m,n),x = m * (2 ** n),m的绝对值属于(0.5,1) math.ldexp(x,i) 返回 x * (2 ** i) 的值 math.fsum(iterable) 对迭代器里的每个元素进行求和操作 math.gcd(x,y) 返回x和y的最大公约数 math.hypot(x,y) 用勾股定理求斜边的长 math.isfinite(x) 判定x不是无穷大 bool math.isinf(x) 判定x是无穷大 bool math.isnan(x) 判定x不是数字 bool math.modf(x) 返回由x的小数部分和整数部分组成的元组 math.trunc(x) 返回x的整数部分

operator 模块

operator 模块
# coding:utf-8 # operator 模块 # operator模块是用c实现的,所以执行速度比python代码快 import operator operator.lt(a, b) # a < b operator.le(a, b) # a <= b operator.eq(a, b) # a = b operator.ne(a, b) # a != b operator.ge(a, b) # a >= b operator.gt(a, b) # a > b operator.not_(obj) operator.truth(obj) 如果 obj 为真值则返回 True,否则返回 False。 这等价于使用 bool 构造器。 operator.is_(a, b) 返回 a is b. 测试对象标识 operator.is_not(a, b) 返回 a is not b. 测试对象标识。 operator.add(a, b) # 返回 a + b operator.sub(a, b) # 返回 a - b. operator.mul(a, b) # 返回 a * b。 operator.truediv(a, b) # 返回 a / b operator.floordiv(a, b) # 返回 a // b. 取整 operator.mod(a, b) # 返回 a % b. 字符串格式化 operator.matmul(a, b) # 返回 a @ b 矩阵相乘 operator.pow(a, b) # 对于数字 a 和 b,返回 a ** b。 operator.abs(obj) 返回 obj 的绝对值。 operator.neg(obj) 返回 obj 的负值 (-obj) operator.pos(obj) 返回 obj 取正的结果 (+obj) operator.index(a) 返回 a 转换为整数的结果。 等价于 a.__index__()。 operator.and_(a, b) 返回 x 和 y 按位与 operator.or_(a, b) 返回 a 和 b 按位或的结果。 operator.inv(obj) 返回数字 obj 按位取反的结果。 这等价于 ~obj。 operator.invert(obj) 返回数字 obj 按位取反的结果。 这等价于 ~obj。 operator.xor(a, b) 返回 a 和 b 按位异或的结果。 operator.lshift(a, b) 返回 a 左移 b 位的结果。 operator.rshift(a, b) 返回 a 右移 b 位的结果。 适用于序列的操作(其中一些也适用于映射)包括: operator.concat(a, b) 对于序列 a 和 b,返回 a + b。 operator.contains(a, b) 返回 b in a 检测的结果。 请注意操作数是反序的。 operator.countOf(a, b) 返回 b 在 a 中的出现次数。 operator.setitem(obj, k, v) 将索引号 b 上的值 a 设为 c。 operator.getitem(obj, k) 返回索引号 b 上的值 a。 operator.delitem(obj, k) 移除索引号 b 上的值 a。 operator.indexOf(obj, k) 返回 b 在 a 中首次出现所在的索引号。 operator.length_hint(obj, default=0) 返回对象 o 的估计长度。 首先尝试返回其实际长度,再使用 object.__length_hint__() 得出估计值,最后返回默认值。 operator.attrgetter(attr) operator.attrgetter(*attrs) # 返回一个可从操作数中获取 attr 的可调用对象。 如果请求了一个以上的属性,则返回一个属性元组。 属性名称还可包含点号。 # 在 f = attrgetter('name') 之后,调用 f(b) 将返回 b.name。 # 在 f = attrgetter('name.first', 'name.last') 之后,调用 f(b) 将返回 (b.name.first, b.name.last) operator.itemgetter(item) operator.itemgetter(*items) # 返回一个使用操作数的 __getitem__() 方法从操作数中获取 item 的可调用对象。 如果指定了多个条目,则返回一个查找值的元组。 # 在 f = itemgetter(2) 之后,调用 f(r) 将返回 r[2]。 # 在 g = itemgetter(2, 5, 3) 之后,调用 g(r) 将返回 (r[2], r[5], r[3])。 operator.methodcaller(name[, args...]) # 返回一个在操作数上调用 name 方法的可调用对象。 如果给出额外的参数和/或关键字参数,它们也将被传给该方法。 例如: # 在 f = methodcaller('name') 之后,调用 f(b) 将返回 b.name()。 # 在 f = methodcaller('name', 'foo', bar=1) 之后,调用 f(b) 将返回 b.name('foo', bar=1)。 ############# ## 原地操作 ## ############# # 不可变的目标例如字符串、数字和元组,需要再次赋值 # 可变的目标例如列表和字典,原地方法将执行更新,因此不需要后续赋值操作 operator.iadd(a, b) # a = iadd(a, b) 等价于 a += b。 operator.iand(a, b) # a = iand(a, b) 等价于 a &= b。 operator.iconcat(a, b) # a = iconcat(a, b) 等价于 a += b 其中 a 和 b 为序列。 operator.ifloordiv(a, b) # a = ifloordiv(a, b) 等价于 a //= b. operator.ilshift(a, b) # a = ilshift(a, b) 等价于 a <<= b。 operator.imod(a, b) # a = imod(a, b) 等价于 a %= b。 operator.imul(a, b) # a = imul(a, b) 等价于 a *= b。 operator.imatmul(a, b) # a = imatmul(a, b) 等价于 a @= b operator.ior(a, b) # a = ior(a, b) 等价于 a |= b。 operator.ipow(a, b) # a = ipow(a, b) 等价于 a **= b。 operator.irshift(a, b) # a = irshift(a, b) 等价于 a >>= b。 operator.isub(a, b) # a = isub(a, b) 等价于 a -= b。 operator.itruediv(a, b) # a = itruediv(a, b) 等价于 a /= b。 operator.ixor(a, b) # a = ixor(a, b) 等价于 a ^= b。
运算 语法 函数
加法 a + b add(a, b)
字符串拼接 seq1 + seq2 concat(seq1, seq2)
包含测试 obj in seq contains(seq, obj)
除法 a / b truediv(a, b)
除法 a // b floordiv(a, b)
按位与 a & b and_(a, b)
按位异或 a ^ b xor(a, b)
按位取反 ~ a invert(a)
按位或 a | b or_(a, b)
取幂 a ** b pow(a, b)
一致 a is b is_(a, b)
一致 a is not b is_not(a, b)
索引赋值 obj[k] = v setitem(obj, k, v)
索引删除 del obj[k] delitem(obj, k)
索引取值 obj[k] getitem(obj, k)
左移 a << b lshift(a, b)
取模 a % b mod(a, b)
乘法 a * b mul(a, b)
矩阵乘法 a @ b matmul(a, b)
否定(算术) - a neg(a)
否定(逻辑) not a not_(a)
正数 + a pos(a)
右移 a >> b rshift(a, b)
切片赋值 seq[i:j] = values setitem(seq, slice(i, j), values)
切片删除 del seq[i:j] delitem(seq, slice(i, j))
切片取值 seq[i:j] getitem(seq, slice(i, j))
字符串格式化 s % obj mod(s, obj)
减法 a - b sub(a, b)
真值测试 obj truth(obj)
比较 a < b lt(a, b)
比较 a <= b le(a, b)
相等 a == b eq(a, b)
不等 a != b ne(a, b)
比较 a >= b ge(a, b)
比较 a > b gt(a, b)

内置函数 部分

内置函数 部分
# coding:utf-8 # 1. input([prompt]) 返回为 string 类型 # 2. print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False) 打印输出;flush = True,Loading 的效果 # 3. format 格式化字符串 # 4. help([object]) 返回对象帮助信息。 # 5. dir 返回当前范围内的变量、方法和定义的类型列表。带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。 # 6. open(file, mode='r') 打开一个文件,并返回文件对象;如果该文件无法被打开,会抛出 OSError;关闭文件对象,即调用 close() # 7. type(name, bases, dict) 只有第一个参数则返回对象的类型,三个参数返回新的类型对象。 # 8. all(iterable) 判定容器内的元素全部都有意义 # 9. any(iterable) 判定容器内的元素存在一个有意义 # 10. bool([x]) 返回一个布尔值 # 12. str 返回一个对象的string格式。 # 13. repr(object) 返回一个对象的 string 格式 # 14. tuple( iterable ) 返回元组 将集合转换为元组时,只保留键! # 15. list() 返回列表 # 16. dict 返回一个字典 # 17. set([iterable]) 创建一个无序不重复元素集-集合, & 交集、 | 并集、 - 差集 # 18. frozenset([iterable]) 返回一个冻结的集合-不可变集合 # 19. map(function, iterable, ...) 返回迭代器,根据提供的函数对指定序列做映射 # 20. hash(object) 返回对象的哈希值。 # 21. zip([iterable, ...]) 返回一个对象。 # 22. id([object]) 返回对象的唯一标识符,CPython 中获取对象的内存地址 # 23. enumerate(sequence, [start=0]) 返回 enumerate(枚举) 对象,list中元素加入下标成元组类型。 # 24. iter(object[, sentinel]) 生成迭代器 # 25. reversed(seq) 返回一个反转的迭代器,使逆序 # 26. next() 返回迭代器的下一个项目 # 27. filter(function, iterable) 返回一个迭代器对象,使用 list() 来转换为列表 list(enumerate( ['Spring', 'Summer', 'Fall', 'Winter'] , start=1)) # 28. range(start, stop[, step]) 返回的结果是一个整数序列的对象 # 29. len() 返回对象长度。 # 30. slice(start, stop[, step]) 实现切片对象 list(range(100)[slice(2,25,3)]) # [slice(2,25,3)]等同[2:25:3] # 31. sorted(iterable, key=None, reverse=False) 返回重新排序的列表,对所有可迭代的对象进行排序操作;list.sort()会修改原始的 list d1 = [{'name':'alice', 'score':38}, {'name':'bob', 'score':18}, {'name':'darl', 'score':28}, {'name':'christ', 'score':28}] sorted(d1, key=lambda x:(-x['score'], x['name'])) # 先按照成绩降序排序,相同成绩的按照名字升序排序 # 32. globals() 返回全局变量的字典 # 33. locals() 返回字典类型的局部变量。 # 34. vars([object]) 返回对象object的属性和属性值的字典对象,如果没有参数,就打印当前调用位置的属性和属性值 类似 locals() # 35. memoryview(obj) 返回给定参数的内存查看对象 # 36. abs(x) 绝对值 # 37. max(x1, x2, ...) 最大值 # 38. min(x1, x2, ...) 最小值 # 39. pow(x,y) x**y # 40. sum(iterable[, start]) 返回计算结果 # 41. round( x [, n] ) 返回浮点数 x 的五舍六入的值 # 42. divmod 返回一个包含商和余数的元组 # 43. complex 返回一个复数 # 44. ascii(object) 返回一个对象可打印的字符串 # 45. bytearray 返回一个新字节数组 # 46. chr 返回值对应的 ASCII 字符. chr(0x30) == chr(48) == 0 # 47. ord(c) 返回值是对应的十进制整数,与 chr() 函数相对 # 48. bin(x) 返回二进制字符串 # 49. oct(x) 返回一个8 进制字符串,以 0o 开头 # 50. int(x[, base=10]) 返回整型数据 # 51. hex([x]) 返回一个16 进制字符串,以 0x 开头。 # 52. float([x]) 返回浮点数 # 53. eval(expression[, globals[, locals]]) 执行一个字符串表达式,并返回表达式的值 # 54. exec(object[, globals[, locals]]) 返回表达式的值,支持动态执行。 # 55. __import__(name[, globals[, locals[, fromlist[, level]]]]) 用于动态加载类和函数 # 56. isinstance(object, classinfo) 判断obj是否和某类有关系 # 57. issubclass(class, classinfo) 判断class 是否是 classinfo 的子类。 # 58. super(type[, object-or-type]) 用于调用父类(超类)的一个方法,解决多重继承问题——查找顺序(MRO)、重复调用(钻石继承)等 # 59. staticmethod 修饰符,声明一个静态方法,不强制要求传递参数, # 60. classmethod 修饰符, 对象不需要实例化,函数不需要 self 参数。但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等 # 61. compile(source, filename, mode[, flags[, dont_inherit]]) 返回表达式执行结果。 exec(compile("for i in range(0,10): print(i)",'','exec')) eval(compile("3 * 4 + 5",'','eval')) # 62. setattr 用于[创建]设置属性值 # 63. getattr(object, name[, default]) 返回一个对象属性值 # 64. hasattr(object, name) 判断对象是否包含对应的属性 # 65. delattr(object, name) 删除对象属性 dict(a='a', b='b', t='t') # 传入关键字 dict(zip(['one', 'two', 'three'], [1, 2, 3])) # 映射函数方式来构造字典 dict([('one', 1), ('two', 2), ('three', 3)]) # 可迭代对象方式来构造字典 # 66. property([fget[, fset[, fdel[, doc]]]]) class C(object): def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") # 如果 c 是 C 的实例化, c.x 将触发 getter,c.x = value 将触发 setter , del c.x 触发 deleter。如果给定 doc 参数,其将成为这个属性值的 docstring,否则 property 函数就会复制 fget 函数的 docstring(如果有的话)。 # 将 property 函数用作装饰器可以很方便的创建只读属性 # 下面的代码将 voltage() 方法转化成同名只读属性的 getter 方法。 class Parrot(object): def __init__(self): self._voltage = 100000 @property def voltage(self): """Get the current voltage.""" return self._voltage # property 的 getter,setter 和 deleter 方法同样可以用作装饰器,这个代码和第一个例子完全相同: class C(object): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x

2. Python 内建数据类型有哪些?

六个标准的数据类型: - Number(数字类别): int、float、bool、complex - String(字符串) - List(列表) - Tuple(元组) - Set(集合) - Dictionary(字典)

不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组); 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。

字典中的键是不可变类型,可变类型listdict不能作为字典键 一个对象能不能作为字典的key,就取决于其有没有__hash__方法

另:映射 map、类 class、实例 、异常 BaseException

3. 简述 with 方法打开处理文件帮我我们做了什么?

with语句是“上下文管理器”,用于资源访问的场合,作用是资源释放和异常处理。 如果按照常规的 f.open 写法,我们需要 try,except,finally,做异常判断,并且文件最终不管遇到什么情况,都要在finally里执行f.close()关闭文件,而with方法帮我们实现了finallyf.close

with语句支持通过上下文管理器所定义的运行时上下文 - object.__enter__(自我) 输入与此对象相关的运行时上下文。该with语句会将此方法的返回值绑定到as该语句的子句中指定的目标( 如果有)。 - object.__exit__(self,exc_type,exc_value,traceback ) 退出关联到此对象的运行时上下文。 各个参数描述了导致上下文退出的异常。 如果上下文是无异常地退出的,三个参数都将为 None。

如果提供了异常,并且希望方法屏蔽此异常(即避免其被传播),则应当返回真值。 否则的话,异常将在退出此方法时按正常流程处理。 请注意 exit() 方法不应该重新引发被传入的异常,这是调用者的责任。

4. 列出 Python 中可变数据类型和不可变数据类型,为什么?

变量更改后地址(id())是否发生改变 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组); 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。 () ### 5. Python 获取当前日期?

import datetime
datetime.now()
# >: 2021-06-20 02:12:37

import time
time.ctime()
# >: Sun Jun 20 02:12:37 2021

time.time() # 返回当前时间戳
# >: 1624126357

time.localtime() #  返回当前时间结构元组
# >: time.struct_time(tm_year=2021, tm_mon=6, tm_mday=20, tm_hour=2, tm_min=12, tm_sec=37, tm_wday=6, tm_yday=171, tm_isdst=0)

time.strftime("%Y-%m-%d %H:%M:%S",) # 格式化输出当前时间

6. 统计字符串每个单词出现的次数

s = 'I can because i think i can'

from collections import Counter
result = Counter(s.split())
print(result)

result = {word: s.split().count(word) for word in set(s.split())}
print(result)

7. 用 python 删除文件和用 linux 命令删除文件方法

import os
os.remove(filepath) # 删除文件
rm -rf 文件名
rm -rf *文件名关键字*

8. 写一段自定义异常代码

class MyException(Exception):
    def __init__(self,*args):
        self.args = args
class NumErorr(MyException):
    def __init__(self,numA,numB):
        self.numA=numA
        self.numB=numB
    def __str__(self):
        return f"本计算器只接收整数!"

try:
    raise MyException('stop')
    raise NumErorr(1,2)
except NumErorr as e:
    print(e)
except MyException as e:
    print(e)

9. 举例说明异常模块中 try except else finally 的相关意义

  • try..except..else没有捕获到异常,执行else语句
  • try..except..finally不管是否捕获到异常,都执行finally语句

10. 遇到 bug 如何处理

  • 细节上的错误,通过print()打印,能执行到print()说明一般上面的代码没有问题,分段检测程序是否有问题,如果是js的话可以alert或console.log
  • assert / try-except / IDE单步调式
  • 如果涉及一些第三方框架,会去查官方文档或者一些技术博客。
  • issue中查询是否有相似bug

语言特性

1. 谈谈对 Python 和其他语言的区别

语言特点:简洁、优雅,省略了各种大括号和分号,还有一些关键字,类型说明; 语言类型:解释型语言,运行的时候是一行一行的解释,并运行,所以调试代码很方便,开发效率很高; 第三方库:python是开源的,并且python的定位是任由其发展。Python具有非常完备的第三方库;标准库与第三方库都非常强大,而且应用领域也非常广,比如Web,运维,自动化测试,爬虫,数据分析,人工智能。

和Java相比:在很多方面,Python比Java要简单,比如java中所有变量必须声明才能使用,而Python不需要声明,用少量的代码构建出很多功能;(高效的高级数据结构)

和C相比:类库齐全并且使用简洁,很少代码实现的功能用C可能要很复杂。运行速度相较于C,绝对是很慢了,Python和CPython解释器都是C语言编写的

2. 简述解释型和编译型编程语言

解释型:就是边解释边执行; 编译性:编译后再执行

3. Python 的解释器种类以及相关特点?

CPython:c语言开发的 使用最广的解释器 IPython:基于cpython之上的一个交互式计时器 交互方式增强 功能和cpython一样 PyPy:目标是执行效率 采用JIT技术 对python代码进行动态编译,提高执行效率 Jython/JPython:运行在Java上的解释器 直接把python代码编译成Java字节码执行 IronPython:运行在微软 .NET 平台上的解释器,把python编译成. NET 的字节码

4. 说说你知道的Python3 和 Python2 之间的区别?

python3 和 python2 是不兼容的,而且差异比较大。 | Python2 | Python3 | | :---- | :---- | Python2 打印内容可以不用带小括号|Python3 中必须带; Python2 range(1,10)返回列表|Python3 返回迭代器,节约内存; Python2 中使用ascii编码|Python3 中使用utf-8编码; python2中unicode表示字符串序列,str表示字节序列|python3中 str表示字符串序列,byte表示字节序列; python2中为正常显示中文,引入coding声明|python3中 不需要 python2中在使用super()必须在参数中写上基类|python3中 不需要,直接无参数调用即可 python2中True 和 False 是两个全局变量|python3中 True或False不可变 python2中range,字典对象的 dict.keys()dict.values() 方法, mapfilterzip等返回列表,迭代器必须实现next方法|python3中 将返回列表的方法改为了返回迭代器对象,内置了__next__,不用特意去实现next,rang()相当于python2中 xrang() python2中 没有办法在嵌套函数中将变量声明为一个非局部变量,只能在函数中声明全局变量|python3中 nonlocal方法实现声明全局变量 python2中函数定义不可以用中文定义|python3中可以用中文

5. Python3 和 Python2 中 int 和 long 区别?

python3 中 int 整合了 python2 重中的 intlong python3 中 int 类型的范围是动态长度的,正整数或者负整数,用sys.getsizeof()可以看int占了几位. Python2中long类型的范围是无限大小

6. xrange 和 range 的区别?

python2 中xrang()返回到的是迭代器对象,rang()返回的是list列表类型 python3 中弃用了xrang()rang()返回到的是迭代器对象

7. 强类型语言、动态语言和脚本语言之间的区别?

强类型:不允许不同类型相加。例如:整形+字符串会报类型错误。 动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。 脚本语言:一般是解释性语言,运行代码只需要一个解释器,不需要编辑。

编码规范

1. 什么是 PEP8?

PEP 8(Python Enhancement Proposal 8),是 Python 第8号增强提案,针对Python语言编订的代码风格指南、编码约定。 - 缩进。4个空格的缩进,不使用Tap,更不能混合使用Tap和空格 - 每行最大长度79,换行可以使用反斜杠,最好使用圆括号。换行点要在操作符的后边敲回车。 - 类和top-level函数定义之间空两行;类中的方法定义之间空一行;函数内逻辑无关段落之间空一行;其他地方尽量不要再空行。 - 模块导入的顺序:按标准、三方和自己编写顺序依次导入,之间空一行。 - 不要在一句中import多个库 - 避免不必要的空格 - 注释必须要有 - 函数命名要遵循规范 - 尽可能使用'is''is not'取代'==',比如if x is not None 要优于if x - 异常中try的代码尽可能少

2. 了解 Python 之禅么?

import this

"""
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
#(优美胜于丑陋)
# Python 以编写优美的代码为目标
Explicit is better than implicit.
#(明了胜于晦涩)
# 优美的代码应当是明了的,命名规范,风格相似
Simple is better than complex.
#(简洁胜于复杂)
# 优美的代码应当是简洁的,不要有复杂的内部实现
Complex is better than complicated.
#(复杂胜于凌乱)
# 如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁
Flat is better than nested.
#(扁平胜于嵌套)
# 优美的代码应当是扁平的,不能有太多的嵌套
Sparse is better than dense.
#(间隔胜于紧凑)
# 优美的代码有适当的间隔,不要奢望一行代码解决问题
Readability counts.
#(可读性很重要)
# 优美的代码是可读的
Special cases aren't special enough to break the rules.
#(即便假借特例的实用性之名,也不可违背这些规则)
Although practicality beats purity.
# 这些规则至高无上
Errors should never pass silently.
#(不要包容所有错误,除非你确定需要这样做)
Unless explicitly silenced.
# 精准地捕获异常,不写 except:pass 风格的代码
In the face of ambiguity, refuse the temptation to guess.
#(当存在多种可能,不要尝试去猜测)
There should be one-- and preferably only one --obvious way to do it.
#(而是尽量找一种,最好是唯一一种明显的解决方案)
# 如果不确定,就用穷举法
Although that way may not be obvious at first unless you're Dutch.
#(虽然这并不容易,因为你不是 Dutch )
#  Dutch 是指 Guido -- Python 之父
Now is better than never.
#(做也许好过不做)
Although never is often better than *right* now.
#(但不假思索就动手还不如不做)
# 动手之前要细思量
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
#(如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然)
# 方案测评标准
Namespaces are one honking great idea -- let's do more of those!
#(命名空间是一种绝妙的理念,我们应当多加利用)
# 倡导与号召
"""

3. 了解 docstring 么?

Python 文档字符串(DocStrings)是一个用于解释文档程序,生成帮助文档的重要工具。通过Docstring建立的文档不仅对人来说有更好的可读性,也能够让IDE等工具自动识别使用函数、类、变量等的一些限制,从而帮助我们更好地理解程序。 PEP0257里的约定使用三个双引号(""")来定义文档字符串,也可以但不建议使用三个单引号 ''',docstring可以写在三个地方:模块或包,对象,函数。 使用惯例:首行简述函数功能,第二行空行,第三行为函数的具体描述。

通过__doc__(注意双下划线)调用函数中的文档字符串属性。 通过help函数直接输出一份有格式的文档,主要是在交互模式下使用

Python Docstring有三种主要风格,分别是reST风格、Google风格、Numpy风格和Epytext风格等: #### reST风格 - reST的全称是reStructredText。通过以冒号开头的几个关键字来说明类、函数中的参数、返回值、异常等。

:param <类属性名称>: <描述>
:type <类属性名称>: <类型>
:return: <对返回值的描述>
:rtype: <返回值类型>
:raises: <可能抛出的异常列表>
#### Google风格 - Google 风格将所有的参数写在Args标签下,而所有的返回值写在Returns标签下。我个人认为比起reST风格,Google 风格的可读性要更好一些。在Args标签下,可以在参数名称后面加 (类型)来确定参数的类型,同样可以起到对参数类型的限制作用。
Args:
  <参数> : <描述>
  <参数> (<类型>): <描述>
Returns:
  <类型>: <描述>
#### Numpy风格 - Numpy是矩阵分析、科学计算、机器学习中都会用到的常见Python程序库。Numpy也有自己独特的Python Docstring风格。Numpy风格的docstring似乎不能用sphinx来生成html形式的文档。
Parameters
----------
参数 : [类型]
    参数的描述
    
Attributes
----------
属性 : [类型]
    属性的描述
#### Epytext风格 - Epytext类似于javadoc的风格
@param a:
@param b:
@return:

4. 了解类型注解么?

类型注解是从 Python 3.5 开始引入,提高代码的可读性,可以通过第三方工具测试代码里的类型注解正确性。 变量类型注解需要 Python 3.6 以上版本。

# 注解前
def add(x, y):
    return x + y

# 加注解
def add(x: int, y: int) -> int:
    return x + y

# 查看参数和返回值类型的注解
print(add.__annotations__)

# inspect 模块提供了一些有用的函数帮助获取对象的信息,例如模块、类、方法、函数、回溯、帧对象以及代码对象。例如它可以帮助你检查类的内容,获取某个方法的源代码,取得并格式化某个函数的参数列表,或者获取你需要显示的回溯的详细信息。
# 该模块提供了4种主要的功能:类型检查、获取源代码、检查类与函数、检查解释器的调用堆栈。
# 文档: https://docs.python.org/zh-cn/3.7/library/inspect.html

import inspect

print(sig = inspect.signature(add))
print(sig)
print(sig.parameters)
print(sig.return_annotation)
print(sig.parameters['x'])

使用第三方包mypy测试注解的正确性

# 安装
pip install mypy

# 测试
mypy filename.py

5. 例举你知道 Python 对象的命名规范,例如方法或者类等

- 变量:

  1. 单下划线开头(_xx): protected
  2. 双下划线开头(__xx): private
  3. 双下划线开头结尾(xx): 系统内置变量

- 函数

  1. 私有方法(_func): 小写和一个前导下划线
  2. 特殊方法(魔术方法)(func): 小写和两个前导下划线,两个后置下划线
  3. 一般方法(func): 方法 备注: 函数参数: 小写和下划线,缺省值等号两边无空格 def func(self, user=None):

- 类

  1. 驼峰格式命名(CamelClass): 所有单词首字母大写其余字母小写
  2. 基类而言,可以使用一个Base或者Abstract前缀(CamelBase)
  3. 不要滥用*args和**kwargs,可能会破坏函数的健壮性

- 常量

大写,单词间用下划线

6. Python 中的注释有几种?

单行注释:# 多行注释:一对'''或者一对"""

7. 如何优雅的给一个函数加注释?

Docstring 上文有提到。 另,注释的种类: 1. 复述代码——这种把代码复述一遍的注释最无聊 2. 解释代码——解释代码的思路,这种代码可以有,但是大部分时候是因为代码写得不好 3. 标记——可能用的到标注,如 TODO、FIXME 4. 概述代码——一句话告诉别人代码做了什么,很好的注释代码 5. 意图说明——指出代码要解决的问题 6. 传达代码无法表述的信息——非常重要

8. 如何给变量加注释?

Docstring 上文有提到

9. Python 代码缩进中是否支持 Tab 键和空格混用。

支持但不建议,如果混用的话,tab按8个空格算

10. 是否可以在一句 import 中导入多个库?

可以但不建议

11. 在给 Py 文件命名的时候需要注意什么?

全小写,可使用下划线,不可与第三方库、内建模块等重名。

12. 例举几个规范 Python 代码风格的工具

Docstring 上文有提到

数据类型

字符串

1. 列举 Python 中的基本数据类型?

基本数据类型上文有提到

2. 如何区别可变数据类型和不可变数据类型

可变数据类型&不可变数据类型上文有提到

3. 将"hello world"转换为首字母大写"Hello World"

str = "hello world"

# 转换成标题
print(str.title())

# capitalize() 将字符串的第一个字母变成大写,其他字母变小写
# upper() 将字符串的所有字母变成大写
words = str.split(" ")
print([word.capitalize() for word in words])
print([word[0].upper()+word[1:] for word in words])

4. 如何检测字符串中只含有数字?

str = '123'
str.isdigit()
str.isnumeric() # 只是针对Unicode对象

5. 将字符串"ilovechina"进行反转

str = "ilovechina"

# 一行
newStr = str[::-1]
newStr = ''.join(i for i in str[::-1])
newStr = ''.join(list(reversed(str)))
newStr = ''.join(i for i in reversed(str))

# 两行
str_list = list(str)
newStr = ''.join( ( str_list, str_list.reverse() )[0] )

# 列表倒序(按 “ASCII 字符顺序” 进行排序)
str_list = list(str)
str_list.sort(reverse=True)
newStr = ''.join(str_list)

# 累积相加法
from functools import reduce
def add(x, y) :
    return y + x
newStr = reduce(add, [1,2,3,4,5]))

# 累积相加法 进阶
from functools import reduce
newStr = reduce(lambda x,y:y+x, str)

# 累积相加法 高阶
from functools import reduce
newStr = reduce(lambda x,y:y+x, str)

# 倒序切片法
newStr = str[::-1]
print(newStr)

# 列表弹出
newStr= ""
str_list = list(str)
while len(str_list) > 0:
    newStr = newStr + str_list.pop()
print(newStr)

# 循环反向迭代法
newStr= ""
for i in str:
    newStr = i + newStr
print(newStr)

# 列表反转
str_list = list(str)
str_list.reverse()
newStr = ''.join(str_list)
print(newStr)

# 善用递归
def func(s):
    if len(s)<=1:
        return s
    return s[-1] + func(s[:-1])
newStr = func(str)
print(newStr)
        

6. Python 中的字符串格式化方式你知道哪些?

由双引号包围的是格式化字符串,可以理解为一个字符串模板。特别地,由三引号包围的字符串更为直观,之间的所有字符,包括换行、TAB,都属于字符串的内容。Python的字符串格式化有两种方式:%格式符方式,format方式、f-string方式。 1. %格式符方式: %[(name)][flags][width].[precision]typecode | | 解释 | | :----- | :----- | | (name) | 指定变量 | | flags| 默认右对齐,正数前无符号,负数前加负号,用空格填充空白处
转为浮点数同时默认以0补充至小数点后6位,第7位开始用空格补充
可选值:
+:正数前加正号
-:左对齐
0:用0填充空白处 | | width | 最小占有宽度 | | .precision | 小数点后保留的位数| | typecode | 转换符号
%d、%i 转换为带符号的十进制整数
%o 转换为带符号的八进制整数
%x、%X 转换为带符号的十六进制整数
%e 转化为科学计数法表示的浮点数(e 小写)
%E 转化为科学计数法表示的浮点数(E 大写)
%f、%F 转化为十进制浮点数
%g 智能选择使用 %f 或 %e 格式
%G 智能选择使用 %F 或 %E 格式
%c 格式化字符及其 ASCII 码
%r 使用 repr() 函数将表达式转换为字符串
%s 使用 str() 函数将表达式转换为字符串|

几点说明: - 对于整数,指定左对齐时,在右边补 0 是没有效果的,因为这样会改变整数的值。 - 对于小数,以上三个标志可以同时存在。如果没有要求小数位,则默认以0补充至小数点后6位,第7位开始用空格补充。 - 对于字符串,只能使用-标志,因为符号对于字符串没有意义,而补 0 会改变字符串的值。 - 当字符串中存在格式化标志时,需要用 %%表示一个百分号

  1. format 数字格式的定义以 ':' 号开始。碰到了': '字符就知道要定义一个数字的显示格式了。格式的定义顺序为 [[fill]align][sign][#][0][width][,][.precision][type] ||解释| |:--|:--| | fill | 【可选】空白处填充的字符 | | align| 【可选】对齐方式(需配合width使用)
    <,内容左对齐
    >,内容右对齐(默认)
    =,内容右对齐,将符号放置在填充字符的左侧,且只对数字类型有效。 即使:符号+填充物+数字
    ^,内容居中 | | sign | 【可选】有无符号数字
    +,正号加正,负号加负;
    -,正号不变,负号加负;
    空格 ,正号空格,负号加负; | | # | 【可选】对于二进制、八进制、十六进制,如果加上#,会显示 0b/0o/0x,否则不显示| | , |【可选】为数字添加分隔符,如:1,000,000 | | width | 【可选】格式化位所占宽度 | | .precision |【可选】小数位保留精度 | | type | 【可选】格式化类型
    s,格式化字符串类型数据
    空白,未指定类型,则默认是None,同s
    b,将10进制整数自动转换成2进制表示然后格式化
    c,将10进制整数自动转换为其对应的unicode字符
    d,十进制整数
    o,将10进制整数自动转换成8进制表示然后格式化;
    x,将10进制整数自动转换成16进制表示然后格式化(小写x)
    X,将10进制整数自动转换成16进制表示然后格式化(大写X)
    e, 转换为科学计数法(小写e)表示,然后格式化;
    E, 转换为科学计数法(大写E)表示,然后格式化;
    f , 转换为浮点型(默认小数点后保留6位)表示,然后格式化;
    F, 转换为浮点型(默认小数点后保留6位)表示,然后格式化;
    g, 自动在e和f中切换
    G, 自动在E和F中切换
    %,显示百分比(默认显示小数点后6位) |
  2. f-string f-string就是在format格式化的基础之上做了一些变动,核心使用思想和format一样。在这个方式中,大括号{ }里放被替换字段,在:前可以填入表达式或调用函数或匿名函数。

7. 把一个字符串开头和末尾都有空格

比如" adabdw ",要求写一个函数把这个字符串的前后空格都去掉。 1. strip():把头和尾的空格去掉 2. lstrip():把左边的空格去掉 3. rstrip():把右边的空格去掉 4. replace('c1','c2'):把字符串里的c1替换成c2。故可以用replace(' ','')来去掉字符串里的所有空格 5. split():通过指定分隔符对字符串进行切片,如果参数num 有指定值,则仅分隔 num 个子字符串 6. 用正则匹配:

# re.sub 表示替换; \s 表示空白字符

re.sub(' ','',str) # 把str中的空格全部清除
def trim(s):
    ''' 如果两端存在空格,则清除两端的空格,并返回结果 '''
    import re
    if s.startswith(' ') or s.endswith(' '): # 如果两端存在空格
      return re.sub(r"^(\s+)|(\s+)$", "", s) # 仅清除两端的空格
    return s
7. 使用递归:
def trim(s):
    if s[0] == " ":
      return trim(s[1:])   # 如果开首有多个空格的话,递归去除多个空格
    elif s[-1] == " ":
      return trim(s[:-1])  # 如果末尾有多个空格的话,递归去除多个空格
    else:
      return s

8. 获取字符串"123456"最后的两个字符。

print("123456"[-2:])

9. 一个编码为 GBK 的字符串 S,要将其转成 UTF-8 编码的字符串,应如何操作?

str1 = "人生苦短,我用Python"
S = str1.encode('GBK') # 编码 GBK
result = S.decode('GBK').encode('UTF-8') # 解码 GBK 后,编码 UTF-8

10. 正则切分字符串

s="info:xiaoZhang 33 shandong",用正则切分字符串输出['info', 'xiaoZhang', '33', 'shandong']

import re
s="info:xiaoZhang 33 shandong"
# compile 预编译正则表达式
# \W 匹配任何不是单词字符的字符
pattern = re.compile(r'\W')
print(pattern.split(s))
# 同下
re.split(r'\W', s)

11. 去除多余空格只留一个空格

a = "你好 中国 ",去除多余空格只留一个空格。

a = "你好 中国 "
print(a.rstrip()) # rstrip 去除右边空格

12. 怎样将字符串转换为小写

demo_str = "HELLO WORLD"
demo_str.lower() # 转小写使用lower() 转大写使用upper()

13. 单引号、双引号、三引号的区别?

单引号和双引号都可以用来表示一个字符串,区别不大,遇到需要转义字符的情况需要注意。 三个单引号和三个双引号一般用于多行注释,也可以用来表示字符串:输出多行文本

列表

1. 已知 AList = [1,2,3,1,2],对 AList 列表元素去重,写出具体过程。

AList = [1, 2, 3, 1, 2]

# 第一种方法,使用set集合,先转为集合再转回列表
result_list = list(set(AList))
print(result_list)

# 第二种,使用dict.fromkeys,该函数有两个参数,第一个是字典的键,第二个是对应值(默认为空str),用于创建一个字典类型
result_list = list(dict.fromkeys(AList))
print(result_list)

# 第三种,遍历列表进行判断
result_list = []
for i in AList:
    if i not in result_list:
        result_list.append(i)
print(result_list)

# 第四种,使用pandas.unique()方法,
import pandas
result_list = pandas.unique(AList).tolist()
print(result_list)

# 还有一种不简单的简单行码
[(this,this.append(i)) for this in ([],) for i in AList if i not in this ][0][0]

2. 如何实现 "1,2,3" 变成 ["1","2","3"]

demo_str = "1,2,3"
result_list = demo_str.split(",")
print(result_list)

3. 给定两个 list,A 和 B,找出相同元素和不同元素

# 找相同
same_list = [i for i in list_A if i in list_B]
# 找不同 在A不在B + 在B不在A
different_list = [i for i in list_A if i not in list_B] + [i for i in list_B if i not in list_A]

# 通过集合
# # 找相同
set(list_A) & set(list_B)
# 找不同 对称差分是集合的XOR 又称"异或" 等价的方法:symmetric_difference()
set(list_A) ^ set(list_B)

4. [[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]

q_list = [[1,2],[3,4],[5,6]]
a_list = [ i for item in q_list for i in item]

5. 合并列表[1,5,7,9]和[2,2,6,8]

a_list = [1,5,7,9]
b_list = [2,2,6,8]

# 第一种方法:使用运算符“+”
a_list = a_list + b_list
print(a_list)

# 第二种方法:使用运算符extend()方法
a_list.extend(b_list)
print(a_list)

# 第三种方法:使用append
for i in b_list:
    a_list.append(i)
print(a_list)
[ a_list.append(i) for i in b_list]

6. 如何打乱一个列表的元素?

import random
q_list = [i for i in range(10)]

# 使用random.shuffle打乱一个list数组
random.shuffle(q_list)
print(q_list)

字典

1. 字典操作中 del 和 pop 有什么区别

q_dic = {"a": 1, "b": 2, "c": 3}
# pop方法删除指定的键值对,并返回删除的值
pop_str = q_dic.pop("a")
print(q_dic,'\n',pop_str)

# del不会返回相应的值,只是将其删除
del q_dic["b"]
print(q_dic)

2. 按照字典的内的年龄排序

d1=[ {'name':'alice','age':38}, {'name':'bob','age':16}, {'name':'carl','age':18},]

d1=[
    {'name':'alice','age':38},
    {'name':'bob','age':16},
    {'name':'carl','age':18},
    
]
# 强大的sort方法,满足大多数排序算法,列表排序优先考虑sort!
"""
list.sort( key=None, reverse=False)
key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse --排序规则,reverse = True降序,reverse = False升序(默认)。
"""
d1.sort(key=lambda x: x['age'])
print(d1)

3. 请合并下面两个字典 a = {"A":1,"B":2},b = {"C":3,"D":4}

a = {"A":1,"B":2}
b = {"C":3,"D":4}

# 字典拆分 pyhton 3.5+
d = {**a, **b}

# 使用update方法
a.update(b)
print(a)

# 字典推导式
d = {k:v for d in [a, b] for k,v in d.items()} 

# 列表拼接
d = dict(list(a.items()) + list(b.items())) 

# 元素并集
# items() 返回的是一个类似集合的对象,并不准确,集合是无序的,键重复时不能保证谁覆盖谁。
# 只适合字典中的值可以保证是唯一的可哈希的
d = dict(a.items() | b.items())

# 关键字参数
# 只适合字典的键是字符串时才有效
d = dict(a , **b) 

# chain items
from itertools import chain
d = dict(chain(a.items(), b.items()))

# ChainMap
from collections import ChainMap
d = dict(ChainMap(a, b))

4. 如何使用生成式的方式生成一个字典,写一段功能代码。

{ k:v for k,v in enumerate(range(10))}

5. 如何把元组("a","b")和元组(1,2),变为字典{"a":1,"b":2}

tuple_key = ("a", "b")
tuple_val = (1, 2)
result_dic = dict(zip(tuple_key,tuple_val))
print(result_dic)

综合

1. Python 常用的数据结构的类型及其特性?

数据类型 上文有提到

2. 如何交换字典 {"A":1,"B":2}的键和值?

demo_dic = {"A": 1, "B": 2}
# 使用字典推导式交换位置
result_dic = {v: k for k, v in demo_dic.items()}
print(result_dic)

3. Python 里面如何实现 tuple 和 list 的转换?

强制类型转换

tuple(demo_list)
list(demo_tup)

4. 我们知道对于列表可以使用切片操作进行部分元素的选择,那么如何对生成器类型的对象实现相同的功能呢?

from itertools import islice
gener = (i for i in range(10))
gener_clip = islice(gener, 3, 7)
for i in gener_clip:
    print(i)

5. 请将[i for i in range(3)]改成生成器

(i for i in range(3))  # 方括号改为圆括号即可

6. a="hello"和 b="你好"编码成 bytes 类型

a.encode()
b.encode()

7. 下面的代码输出结果是什么?

a = (1, 2, 3, [4, 5, 6, 7], 8)
a[2] = 2

# 输出: TypeError: 'tuple' object does not support item assignment
# 原因: 元祖是不可变类型,因此不能修改元祖内的值

8. 下面的代码输出的结果是什么?

a = (1, 2, 3, [4, 5, 6, 7], 8)
a[3][0] = 2
# 输出: (1, 2, 3, [2, 5, 6, 7], 8)
# 

9. Python 交换两个变量的值

a, b = b, a

10. 在读文件操作的时候会使用 read、readline 或者 readlines,简述它们各自的作用

  • read:读取整个文件。
  • readline:读取下一行,使用生成器方法。
  • readlines:读取整个文件到一个迭代器以供我们遍历

11. json 序列化时,可以处理的数据类型有哪些?如何定制支持 datetime 类型?

  • json序列化时,可以处理列表、字典、字符、数值、布尔和None
  • 定制datetime类型↓
    from datetime import datetime
    import json
    from json import JSONEncoder
    class DatetimeEncoder(JSONEncoder):
        """扩展JSONEncoder类中的default方法
        判断传入的类型是否是datetime类型,如果是则转为str字符,否则不是返回父类的值
        """
        def default(self, o):
            if isinstance(o, datetime):
                return o.strftime('%Y-%m-%d %H:%M:%S')
            else:
                return super(DatetimeEncoder, self).default(o)
    
    if __name__ == '__main__':
        dict_demo = {'name': 'alex', 'data': datetime.now()}
        print(json.dumps(dict_demo, cls=DatetimeEncoder))

12. json 序列化时,默认遇到中文会转换成 unicode,如果想要保留中文怎么办?

import json
dict_demo = {"name": "中文"}
# 使用dumps的默认参数ensure_ascii
print(json.dumps(dict_demo, ensure_ascii=False).encoding='utf-8')

13. 有两个磁盘文件 A 和 B,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件 C 中。

data_A = data_B =''
# 读文件A B,将AB内容合并
with open(A, "r") as F:
    data_A = F.read()
with open(B, "r") as F:
    data_B = F.read()
data = data_A + data_B
# 按字母顺序排列
new_data = "".join(sorted(data))
# 输出到一个新文件 C 
with open("C", "w") as F:
    F.write(new_data) 

14. 如果当前的日期为 20210630,要求写一个函数输出 N 天后的日期,(比如 N 为 2,则输出 20210702)。

from datetime import datetime, timedelta

s = '20210630'
N = 2
dt = datetime(int(s[0:4]),int(s[4:6]),int(s[6:8]))
dt = datetime.strptime(s, '%Y%m%d')
dt = dt + timedelta(days=N)
dt.strftime('%Y%m%d')

15. 写一个函数,接收整数参数 n,返回一个函数,函数的功能是把函数的参数和 n 相乘并把结果返回。

  • 闭包是一种特殊的函数,这种函数由多个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包;
  • 闭包函数必须返回一个函数对象;
  • 闭包函数返回的那个函数必须引用外部变量;
  • 闭包可以保存运行环境,即在闭包内的变量是不能被轻易修改的;
  • 闭包的好处:提高代码的可复用性。
    def out_func(n):
        def in_func(num):
            return n*num
        return in_func
    
    demo = out_func(3)
    res = demo(4)
    print(res)

16. 下面代码会存在什么问题,如何改进?

def strappend(num):        # 函数作用、参数意义不明,需要加注释
    str='frist'            # 不能使用关键字"str"作为变量名
    for i in range(num):   # 遍历得到的元素"i"意义不明,无注释
        str+=str(i)        # 变量名和关键字在这个时候重名,必定报错,没有了str()方法
    return str

# 修改后
def str_append(append_cound: int) -> str:
    """字符串修改
    
    遍历append_cound,将遍历的值转为str类型并添加到字符串中
    :param append_cound: 遍历次数
    :return: 最终修改得到的新字符串
    """
    append_str = "frist"
    # 遍历获取到"times"次数int类型
    for times in range(append_cound):
        append_str += str(times)
    return append_str

print(str_append(4))

17. 一行代码输出 1-100 之间的所有偶数。

print([ num for num in range(0, 101) if num % 2 == 0])
print([ num for num in range(2,101,2)])

18. with 语句的作用,写一段代码?

with 语句上文有提到

import threading

# 来一个用于线程锁的with使用
num = 0  # 全局变量多个线程可以读写,传递数据
thread_lock = threading.Lock()  # 创建一个锁

class Mythread(threading.Thread):
    def run(self):
        global num
        with thread_lock:               # with Lock的作用相当于自动获取和释放锁(资源)
            for i in range(1000000):    # 锁定期间,其他线程不可以运行
                num += 1
        print(num)

19. python 字典和 json 字符串相互转化方法

json.dumps 用于将 Python 对象编码成 JSON 字符串。

json.dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, encoding="utf-8", default=None, sort_keys=False, **kw)

json.loads 用于解码 JSON 数据。该函数返回 Python 字段的数据类型。

json.loads(s[, encoding[, cls[, object_hook[, parse_float[, parse_int[, parse_constant[, object_pairs_hook[, **kw]]]]]]]])
import json

dict_demo = {"a": 1, "b": 2}

# 序列化:使用json.dumps()将python类型转为json字符串
json_demo = json.dumps(dict_demo)
print(json_demo)
# 输出:'{"a": 1, "b": 2}'
# 使用其他参数
json_demo = json.dumps(dict_demo,sort_keys=True, indent=4, separators=(',', ': '))
print(json_demo)
"""输出:
{
    "a": 1,
    "b": 2
}
"""

# 反序列化:json.loads 用于解码 JSON 数据
dict_demo = json.loads(json_demo)

20. 请写一个 Python 逻辑,计算一个文件中的大写字母数量

import re
def captial_count(file_name):
    """计算文件中的大写字母数量
    读取文件并计算文件数据的大写字母数量,返回大写字母数量
    :param file_name: 文件名
    :return: 文件中的大写字母数量
    """
    with open(file_name, "r") as f:
        file_data = f.read()
    # 删除掉除大写字母之外的所有字符
    file_data = re.sub("[^A-Z]", "", file_data)
    return len(file_data)

if __name__ == '__main__':
    print(capital_count("test.txt"))
    

21. 请写一段 Python连接 Mongo 数据库,然后的查询代码。

from pymongo import MongoClient

# 连接本地数据库
db_client = MongoClient("mongodb://localhost:27017/")

# 切换到 testdb 测试数据库
test_db = db_client["testdb"]

# 切换到 sites 文档
sites_obj = test_db["sites"]

# find_one() 方法来查询集合中的一条数据
first_data = sites_obj.find_one()

print(first_data)

22. 说一说 Redis 的基本类型。

string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

23. 请写一段 Python连接 Redis 数据库的代码。

import redis

# 创建连接对象
connec_obj = redis.Redis(host='localhost', port=6379, db=0)

# 设置一个键值
connec_obj.set('test', '1')

# 读取一个键值
connec_obj.get('test')   # ->> '1'

24. 请写一段 Python 连接 MySQL 数据库的代码。

import pymysql
# 打开数据库连接
db = pymysql.connect("localhost", "testuser", "test123", "TESTDB", charset='utf8' )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# 使用execute方法执行SQL语句
cursor.execute("SELECT VERSION()")

# 使用 fetchone() 方法获取一条数据
data = cursor.fetchone()

# 关闭数据库连接
db.close()

25. 了解 Redis 的事务么?

  • Redis 事务可以一次执行多个命令,即将多个命令打包,一次性提交并按顺序执行
  • 批量操作在发送 EXEC 命令前被放入队列缓存
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
  • Redis 事务的执行并不是原子性的:在事务执行过程,其他命令不会插入到事务执行命令序列中,中间某条指令的失败不会导致中断也不会导致回滚
  • 事务提供了一种"将多个命令打包,一次性提交并按顺序执行"的机制,提交后在事务执行中不会中断。只有在执行完所有命令后才会继续执行来自其他客户的消息。

Redis通过multi,exec,discard,watch实现事务功能。 1. multi:开始事务 2. exec:提交事务并执行 3. discard:取消事务 4. watch:事务开始之前监视任意数量的键 5. unwatch:取消WATCH命令对多有key的监控,所有监控锁将会被取消。 关于ACID: 1. 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断 2. 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行 3. 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制。

26. 了解数据库的三范式么?

  • 1NF:字段不可分割,每个字段是原子级别
  • 2NF:有主键,非主键字段依赖主键
  • 3NF:非主键字段不能相互依赖,即消除传递依赖
  • BCNF:非主键字段不能对主键的子集依赖,即消除了对主码子集的依赖
  • 4NF: > 多值依赖的概念: 多值依赖即属性之间的一对多关系,记为K→→A。 函数依赖事实上是单值依赖,所以不能表达属性值之间的一对多关系。 平凡的多值依赖:全集U=K+A,一个K可以对应于多个A,即K→→A。此时整个表就是一组一对多关系。 非平凡的多值依赖:全集U=K+A+B,一个K可以对应于多个A,也可以对应于多个B,A与B互相独立,即K→→A,K→→B。整个表有多组一对多关系,且有:“一”部分是相同的属性集合,“多”部分是互相独立的属性集合。 第四范式即在满足巴斯-科德范式(BCNF)的基础上,消除非平凡且非函数依赖的多值依赖(即把同一表内的多对多关系删除)。
  • 5NF: > 即在满足第四范式(4NF)的基础上,消除不是由候选码所蕴含的连接依赖。如果关系模式R中的每一个连接依赖均由R的候选码所隐含,则称此关系模式符合第五范式。 函数依赖是多值依赖的一种特殊的情况,而多值依赖实际上是连接依赖的一种特殊情况。但连接依赖不像函数依赖和多值依赖可以由语义直接导出,而是在关系连接运算时才反映出来。存在连接依赖的关系模式仍可能遇到数据冗余及插入、修改、删除异常等问题。

27. 了解分布式锁么?

多线程和多进程在抢占同一资源时可能会导致数据不一致,为了保证线程或者进程安全,引入线程锁和进程锁,保证了数据的一致性和完整性。同样的,在分布式系统中对共享资源进行操作时,使用分布式锁来解决这一问题。 分布式锁的实现有很多种,常见的有redis、zookeeper和数据库mysql等。 详解

28. 用 Python 实现一个 Reids 的分布式锁的功能。

引自:junli_chen

#!/usr/bin/python3
# coding=utf-8

import time
import redis

class RedisLock(object):
    def __init__(self, key):
        # 连接数据库,创建连接对象
        self.rdcon = redis.Redis(host='', port=6379, password="", db=1)
        # 设置锁的值
        self._lock = 0
        # 分布式锁的键
        self.lock_key = "%s_dynamic_test" % key

    @staticmethod
    def get_lock(cls, timeout=10):
        """获取redis分布式锁

        设置分布式锁,判断锁是否超时
        :param cls: 锁的类对象
        :param timeout: 锁超时时间
        :return:
        """
        while cls._lock != 1:
            # 设置锁的过期时间
            timestamp = time.time() + timeout + 1
            # 设置redis分布式锁键值
            cls._lock = cls.rdcon.setnx(cls.lock_key, timestamp)
            # 判断锁的值是否为1,或者当前时间大于锁预期释放的时间,如果成立则退出循环,释放锁
            if cls._lock == 1 or (
                    time.time() > cls.rdcon.get(cls.lock_key) and
                    time.time() > cls.rdcon.getset(cls.lock_key, timestamp)):
                print("get lock")
                break
            else:
                time.sleep(0.3)

    @staticmethod
    def release(cls):
        """释放锁

        :param cls: 锁的类对象
        :return:
        """
        # 判断当前时间是否大于锁最大释放时间
        if time.time() < cls.rdcon.get(cls.lock_key):
            print("release lock")
            cls.rdcon.delete(cls.lock_key)


def deco(cls):
    """分布式锁装饰器

    :param cls: 分布式锁类对象
    :return: 外层函数
    """
    def _deco(func):
        def __deco(*args, **kwargs):
            print("before %s called [%s]." % (func.__name__, cls))
            cls.get_lock(cls)
            try:
                return func(*args, **kwargs)
            finally:
                cls.release(cls)

        return __deco

    return _deco


@deco(RedisLock("demoLock"))
def myfunc():
    print("myfunc() called.")
    # 设置20s模拟超过锁释放时间就自动释放锁的操作
    time.sleep(20)


if __name__ == "__main__":
    myfunc()

29. 写一段 Python 使用 Mongo 数据库创建索引的代码。

#!/usr/bin/python3
# coding=utf-8
import pymongo
from pymongo import ASCENDING, DESCENDING
# 连接数据库,创建连接对象
myclient = pymongo.MongoClient(mongodbUrl)
# 切换数据库
mydb = myclient[dbName]
# 创建索引,create_index()创建索引,可以有多个约束条件,值为1则升序,-1是降序
mydb.create_index([("date", DESCENDING), ("author", ASCENDING)])

高级特性

1. 函数装饰器有什么作用?请列举说明?

装饰器主要是在不修改代码前提下进行功能的扩展,满足面向对象的“开闭原则”。 > 应用场景: 1. 引入日志 2. 函数执行时间统计 3. 执行函数前预备处理 4. 执行函数后清理功能 5. 权限校验等场景 6. 缓存 7. 事务处理

2. Python 垃圾回收机制?

引用计数为主,标记清除和分代回收为辅: #### 整数 - 小整数:Python 对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象。单个字母同样也是如此。 - 大整数:每一个大整数的创建均在内存中会分配一个内存空间,所以大整数的内存空间是需要被回收的。 #### 引用计数: python里每一个东西都是对象,它们的核心就是一个结构体:PyObject PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少当引用计数为0时,该对象生命就结束了。 |优点|缺点| |---|---| |简单|维护引用计数消耗资源| |实时性|循环引用| - 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

3. 魔法函数 __call__ 怎么使用?

__call__允许一个类的实例像函数一样被调用

#!/usr/bin/python3
# coding=utf-8

class Entity(object):
    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        # 改变实例属性
        self.x, self.y = x, y

if __name__ == '__main__':
    # 创建实例
    demo_obj = Entity(1, 2, 3)
    # 实例可以像函数那样执行,并传入x y值,修改对象的x y
    demo_obj(4, 5)

4. 如何判断一个对象是函数还是方法?

  • 使用isinstance()判断
  • 声明def
    • 类外声明def为函数
    • 类中声明def:
      • 使用类调用的为函数
      • 使用实例化对象调用的为方法
  • 摘自马玉刚的博客
    1. 从分类的角度
    • 函数的分类:
      • 内置函数
      • 匿名函数
      • 递归函数
      • 自定义函数
    • 方法的分类
      • 普通方法:直接用self调用的方法。
      • 私有方法:__函数名,只能在类中被调用的方法。
      • 属性方法:@property,将方法伪装成为属性,让代码看起来更合理。
      • 特殊方法(双下划线方法):以__init__为例,是用来封装实例化对象的属性,只要是实例化对象就一定会执行__init__方法,如果对象子类中没有则会寻找父类(超类),如果父类(超类)也没有,则直接继承object(python 3.x)类,执行类中的__init__方法。
      • 类方法:通过类名的调用去操作公共模板中的属性和方法。
      • 静态方法:不用传入类空间、对象的方法, 作用是保证代码的一致性,规范性,可以完全独立类外的一个方法,但是为了代码的一致性统一的放到某个模块(py文件)中。
    1. 从作用域的角度
    • 函数作用域:从函数调用开始至函数执行完成,返回给调用者后,在执行过程中开辟的空间会自动释放,也就是说函数执行完成后,函数体内部通过赋值等方式修改变量的值不会保留,会随着返回给调用者后,开辟的空间会自动释放。
    • 方法作用域:通过实例化的对象进行方法的调用,调用后开辟的空间不会释放,也就是说调用方法中对变量的修改值会一直保留。
    1. 从调用的方式
    • 函数:通过类.函数名()的方式进行调用
    • 方法:通过实例化对象.方法名()的方式进行调用。

5. @classmethod 和@staticmethod 用法和区别

  • @classmethod 是类方法:访问和修改类属性,进行类相关的操作,通过类或示例对象调用,需要传递cls类对象为参数;
  • @staticmethod 是静态方法:不访问类属性和实例属性,通过类或实例调用,相当于一个普通函数。

6. Python 中的接口如何实现?

摘自终结大笨狗的博客#python中接口实现# 1. 用抽象类和抽象函数实现接口

#抽象类加抽象方法就等于面向对象编程中的接口
from abc import ABCMeta,abstractmethod

class interface(object):
    __metaclass__ = ABCMeta #指定这是一个抽象类
    @abstractmethod  #抽象方法
    def Lee(self):
        pass

    def Marlon(self):
        pass

class RelalizeInterfaceLee(interface):#必须实现interface中的所有函数,否则会编译错误
    def __init__(self):    
        print '这是接口interface的实现'
    def Lee(self):
        print '实现Lee功能'        
    def Marlon(self):
        pass   

class RelalizeInterfaceMarlon(interface): #必须实现interface中的所有函数,否则会编译错误
    def __init__(self):    
        print '这是接口interface的实现'
    def Lee(self):
        pass      
    def Marlon(self):
        print "实现Marlon功能"
2. 用普通类定义接口
class interface(object): #假设这就是一个接口,接口名可以随意定义,所有的子类不需要实现在这个类中的函数
    def Lee(self):pass

    def Marlon(self):
        pass

class Realaize_interface(interface):
    def __init__(self):
        pass
    def Lee(self):
        print "实现接口中的Lee函数"

class Realaize_interface2(interface):
    def __init__(self):
        pass
    def Marlon(self):
        print "实现接口中的Marlon函数"

obj=Realaize_interface()
obj.Lee()

obj=Realaize_interface2()
obj.Marlon()

7. Python 中的反射了解么?

计算机中的反射,是在运行的时候来自我检查,并对内部成员进行操作。就是说这个变量的类型可以动态的改变,在运行的时候确定它的作用。

反射是指程序可以访问。检测和修改它本身状态或行为的一种能力(自省)。 在Python中,能够通过一个对象,找出其typeclassattributemethod的能力,称为反射或自省。 具有反射能力的函数有type()isinstance()callable()dir()getattr()hasattr()setattr()delattr()__import__()等。

反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块寻找指定函数,并执行。利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!

8. metaclass 作用?以及应用场景?[重难点]

metaclass 元类是Python中非常具有魔术性的对象,可以动态的定制或修改继承它的子类。由于太过于灵活,改日另起一篇。 三个重点:__new__,__init__,__call__.

扩展链接: - 廖雪峰-使用元类

class Mymeta(type):
    def __init__(self, name, bases, dic):
        super().__init__(name, bases, dic)
        print('===>Mymeta.__init__')
        print(self.__name__)
        print(dic)
        print(self.yaml_tag)
    def __new__(cls, *args, **kwargs):
        print('===>Mymeta.__new__')
        print(cls.__name__)
        return type.__new__(cls, *args, **kwargs)
    def __call__(cls, *args, **kwargs):
        print('===>Mymeta.__call__')
        obj = cls.__new__(cls)
        cls.__init__(cls, *args, **kwargs)
        return obj

class Foo(metaclass=Mymeta):
    yaml_tag = '!Foo'
    def __init__(self, name):
        print('Foo.__init__')
        self.name = name
    def __new__(cls, *args, **kwargs):
        print('Foo.__new__')
        return object.__new__(cls)

'''输出
===>Mymeta.__new__
Mymeta
===>Mymeta.__init__
Foo
{'__module__': '__main__', '__qualname__': 'Foo', 'yaml_tag': '!Foo', '__init__': <function Foo.__init__ at 0x0000000007EF3828>, '__new__': <function Foo.__new__ at 0x0000000007EF3558>}
!Foo
'''

foo = Foo('foo')
'''输出
===>Mymeta.__call__
Foo.__new__
Foo.__init__
'''

正常情况下我们在父类中是不能对子类的属性进行操作,但是元类可以。换种方式理解:元类、装饰器、类装饰器都可以归为元编程。

9. hasattr() getattr() setattr()的用法

这三种方法用于为对象属性的存在判断、获取和添加修改。

class A():
    name = 'python'
    def func(self):
        return 'A()类的方法func()'

if __name__ == '__main__':
    print(hasattr(A, 'name'))  # True
    print(hasattr(A, 'func'))  # True
    print(hasattr(A, 'age'))   # False
    
    print(getattr(A, 'name'))  # 'python'
    print(getattr(A, 'func'))  # <function A.func at 0x000XXX>
    print(getattr(A(),'func')) # 'A()类的方法func()'
    print(getattr(A,'age'))    # AttributeError
    
    setattr(A, 'name', 'java')
    print(getattr(A, 'name'))  # 'java'
    setattr(A, 'age', 20)
    print(getattr(A, "age"))    # 20

10. 请列举你知道的 Python 的魔法方法及用途。

魔法方法,即dunder method,是以"__"包起来的方法。 - __new__:是用来创建类并返回这个类的实例, - __init__:将传入的参数来初始化该实例,以及初始化示例属性,与__new__共同构成了“构造函数” - __del__:将实例化后的对象销毁,即为析构函数 - __call__:允许一个类像函数一样被调用  - __getattr__:访问对象不存在的属性时,调用该方法,用于定义访问行为 - __setattr__:设置对象属性时调用 - __delattr__:删除对象属性时调用 - __enter____exit__: 上下文管理器 - __iter__:返回一个容器迭代器,很多情况下会返回迭代器,尤其是当内置的iter()方法被调用的时候,以及当使用for x in container:方式循环的时候。迭代器是它们本身的对象,它们必须定义返回self的__iter__方法。 - __next__:返回迭代器的下一个元素

11. 如何知道一个 Python 对象的类型?

type()判断对象类型

12. Python 的传参是传值还是传址?

结论先行:Python 对可变对象(字典或列表)传址,对不可变对象(数字、字符或元祖)传值。

13. Python 中的元类(metaclass)使用举例

metaclass上文有提到

14. 简述 any()和 all()方法

any()判断 一个可迭代对象 存在不为空的元素,返回True;空列表和空元祖为False; all()判断 一个可迭代对象 全部的元素不为空,返回True;空列表和空元祖为True。

15. filter 方法求出列表所有奇数并构造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] list(filter(lambda x: x % 2 == 1, a))

16. 什么是猴子补丁?

猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。 举个栗子: > 之前做的一个游戏服务器,很多地方用的import json,后来发现ujson比自带json快了N倍,于是问题来了,难道几十个文件要一个个把import json改成import ujson as json吗? 其实只需要在进程startup的地方monkey patch就行了.是影响整个进程空间的. 同一进程空间中一个module只会被运行一次.

import json
import ujson
def monkey_patch_json():
    json.__name__ = 'ujson'
    json.dumps = ujson.dumps
    json.loads = ujson.loads

monkey_patch_json()

17. 在 Python 中是如何管理内存的?

Python内存池:内存池的概念就是预先在内存中申请一定数量的,大小相等 的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。 python中的内存管理机制——Pymalloc:python中的内存管理机制都有两套实现,一套是针对小对象,就是大小小于256bits时,pymalloc会在内存池中申请内存空间;当大于256bits,则会直接执行new/malloc的行为来申请内存空间。

内存释放: 参考垃圾回收

18. 当退出 Python 时是否释放所有内存分配?

那些具有对象循环引用或者全局命名空间引用的变量,在 Python 退出是往往不会被释放。另外不会释放 C 库保留的部分内容。

正则表达式

正则表达式可视化 对正则表达式进行解释

1. 使用正则表达式匹配出中的地址 a="张明 98 分",用 re.sub,将 98 替换为 100

import re
html_str = '<html><h1><div>a="张明 98 分"</div></html>'
result_str = re.sub(r'\d{2}', "100", html_str)
print(result_str)

2. 正则表达式匹配中(.*)(.*?)匹配区别?

  • 贪婪匹配在匹配字符串时总是尝试匹配尽可能多的字符
  • 非贪婪匹配在匹配字符串时总是尝试匹配尽可能少的字符。
  • Python里数量词默认是贪婪模式的,在"*","?","+","{m,n}"后面加上?,可使贪婪模式变成非贪婪模式。

3. 写一段匹配邮箱的正则表达式

import re
str1 = 'fdg.123@163.cn  hdfh.abc@qq.com  hidfsd@qq.com gfgfa@qq.net  bdfdg@163.com'
reg_str1 = r'([\w]+(\.[\w]+)*@[\w]+(\.[\w]+)+)'
mod = re.compile(reg_str1)

items = mod.findall(str1)
for item in items:
    print(item)

其他内容

1. 解释一下 python 中 pass 语句的作用?

空语句,是为了保持程序结构的完整性; pass不做任何事情,一般用做占位语句; 一般在搭建程序框架的时候或在判断语句中使用。

2. 简述你对 input()函数的理解

  • Python3.x: input()函数接受一个标准输入数据,返回为字符串类型
  • Python2.x: input() 需要输入 python 表达式。比如程序中有语句a = input("input:"),当输入 runoob会报错,此时 runoob 解释成一个变量,而"runoob"不会报错。
  • Python2.x: raw_input() 将所有输入作为字符串看待

3. python 中的 is 和==

is 是身份运算符,判断两个对象的内存id是否相等 == 是比较运算符,判断两个对象的值是否相等 进行值比较的时候使用==,判断是否是同一对象的时候使用is

4. Python 中的作用域

L (Local) 局部作用域 E (Enclosing) 闭包函数外的函数中 G (Global) 全局作用域 B (Built-in) 内建作用域 以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

5. 三元运算写法和应用场景?

三元运算符就是在赋值变量的时候,可以直接加判断,然后赋值格式 条件为真时的结果 if 判段的条件 else 条件为假时的结果

#先定义变量:
a,b = 1,2

#第一种写法:
erroStr = "More" if a > b else "Less"
print(erroStr) # 运行结果为:Less

#第二种写法:
print({True: "More", False: "Less"}[a > b]) # 运行结果为:Less

#第三种写法:
print(("FalseValue", "TrueValue")[a > b]) # 运行结果为:FalseValue
其中我们比较常见的是第一种。 第二三种是挺简洁的,但是写在项目里怕是接手的同事要抓狂了。

6. 了解 enumerate 么?

enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

语法:

enumerate(sequence, [start=0])

7. 列举 5 个 Python 中的标准模块

参见:常用 Python 标准库

8. 如何在函数中设置一个全局变量

使用global

9. pathlib 的用法举例

pathlib 模块提供了一组面向对象的类,这些类可代表各种操作系统上的路径,程序可通过这些类操作路径。

from pathlib import Path

# 1.查看路径
# 使用cmd()方法输出当前的工作目录
# 使用home()输出用户的主目录
now_path = Path.cwd()
home_path = Path.home()
print("当前工作目录", now_path, type(now_path))
print("home目录", home_path, type(home_path))

# 2. 路径拼接
# 将字符串转为Pathlib.Path类型
# 使用 "/" 直接拼接路径
dir_path = Path(r"D:\code\web") / "flaskweb"
print(dir_path, type(dir_path))

# 3.读写文件
# 使用基础的open()函数
demo_file = Path.cwd() / 'test.md'
with open(demo_file, mode='r') as fid:
    file_data = fid.read()
print(file_data)

"""
使用pathlib的open()方法
这样写的好处就是open里面我们不需要再去传入路径了,
直接指定文件读写模式即可。实际上这里的open方法,
底层也是调用了os.open的方法。使用哪种方式看个人的喜好。
也可以不使用with open的形式即可以进行读写
.read_text(): 找到对应的路径然后打开文件,读成str格式。等同open操作文件的"r"格式。
.read_bytes(): 读取字节流的方式。等同open操作文件的"rb"格式。
.write_text(): 文件的写的操作,等同open操作文件的"w"格式。
.write_bytes(): 文件的写的操作,等同open操作文件的"wb"格式
"""
demo_file = Path.cwd() / 'test.md'
with demo_file.open("r") as fid:
    file_data = fid.read()
print(file_data)

# 4.使用resolve可以通过传入文件名,来返回文件的完整路径
# 需要注意的是"demo.py"文件要和我当前的程序文件在同一级目录。
py_path =Path("demo.py")
print(py_path.resolve())

CSDN-Pathlib模块 Python文档-shutil 高阶文件操作 ### 10. Python 中的异常处理,写一个简单的应用场景 参见:写一段自定义异常代码 参见:异常模块中 try except else finally 的相关意义

11. Python 中递归的最大次数,那如何突破呢?

最大次数为1000次 TAIL CALL OPTIMIZATION DECORATOR 一脸懵逼

12. 什么是面向对象的 mro

MRO:Method Resolution Order(方法解析顺序) MRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。 MRO 是在Python多继承和钻石继承问题上的核心内容,它规定了如何,什么时候,怎么样去 调用父类的方法

# 输出类的解析继承关系顺序:类名.__mro__
DemoClass.__mro__

13. isinstance 作用以及应用场景?

isinstance:判断对象是否是一个已知的类型

isinstance(object, classinfo) - object -- 实例对象。 - classinfo -- 可以是直接或间接类名、基本类型或者由它们组成的元组。

使用场景举例: - 判断对象的数据类型,如参数和返回值判断,根据不同的数据类型 - 判断类的继承关系,isinstance可以用作判断是否继承了某个父类

拓展:type和isinstance - type只输出当前类名,不管继承关系 - isinstance在使用当前类的父类做判断时,输出为True(多重继承适用)

class A:
    pass
 
class B(A):
    pass
 
isinstance(A(), A)    # returns True
type(A()) == A        # returns True
isinstance(B(), A)    # returns True
type(B()) == A        # returns False

14. 什么是断言?应用场景?

断言语句是将调试断言插入程序的便捷方式 assert condition:在condition为True时不触发,False触发AssertionError错误

>>> assert 1==1
>>> assert 1==0
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    assert 1==0
AssertionError
如果没有特别的目的,断言应该用于如下情况: - 防御性的编程 - 运行时对程序逻辑的检测 - 合约性检查(比如前置条件,后置条件) - 程序中的常量 - 检查文档

15. lambda 表达式格式以及应用场景?

lambda表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。

lambda表达式:lambda 参数1,参数2...: 参数表达式

适用场景: - 简单功能的函数实现 - 不需要关注函数命名 - 复用性不高或只用一次的函数

# 输出1到100内的奇数 filder和lambda的组合
print(list(filter(lambda x: x % 2 == 1, range(1, 101))))

# 列表的排序:按照绝对值大小排序
# sorted和lambda也是很好的组合,这里的abs是绝对值函数
list_demo = [3, 5, -4, -1, 0, -2, -6]
print(sorted(list_demo, key=lambda x: abs(x)))

# 闭包lambda
def get_y(a, b):
    return lambda x: a*x + b
y1 = get_y(3, 1)
print(y1(1))  # 结果为4

16. 新式类和旧式类的区别

在Python 3.x中取消了经典类,默认都是新式类,并且不必显式的继承object,也就是说: - class Person(object):pass - class Person():pass - class Person:pass 三种写法并无区别,推荐第一种

  • class Person(object):pass 新式类写法
  • class Person():pass 经典类写法
  • class Person:pass 经典类写法

新式类和经典类的最大的区别:继承搜索顺寻的变化 - 新式类多继承搜索顺序(广度优先):先在水平方向查找,然后再向上查找 - 经典类多继承搜索顺序(深度优先):先深入继承树左侧查找,然后再返回,开始查找右侧

graph TD
A[ 基类 A  ] --> C[ C ]
A --> B[ B ]
B --> D[ D ]
C --> D[ D ]

17. dir()是干什么用的?

dir()函数不带参数时,返回当前范围内的变量、方法和定义的类型列表; 带参数时,返回参数的属性、方法列表。 如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。

18. 一个包里有三个模块,demo1.py, demo2.py, demo3.py,但使用 from tools import *导入模块时,如何保证只有 demo1、demo3 被导入了。

包(package),本质来说就是一个文件夹,但是其中有一个 init .py文件。 包是从逻辑上来组织模块的,也就是说它是用来存放模块的,如果想导入其他目录下的模块,那么这个目录必须是一个包才可以导入。 在本题中 tools 包下有 demo1.py, demo2.py, demo3.py__init__.py 四个文件,我们只需要在 init 文件中写:

from . import demo1
from . import demo3

from .demo2 import *

19. 列举 5 个 Python 中的异常类型以及其含义

BaseException  # 所有异常的基类
 +-- SystemExit  # 解释器请求退出
 +-- KeyboardInterrupt  # 用户中断执行(通常是输入^C)
 +-- GeneratorExit  # 生成器(generator)发生异常来通知退出
 +-- Exception  # 常规异常的基类
      +-- StopIteration  # 迭代器没有更多的值
      +-- StopAsyncIteration  # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
      +-- ArithmeticError  # 各种算术错误引发的内置异常的基类
      |    +-- FloatingPointError  # 浮点计算错误
      |    +-- OverflowError  # 数值运算结果太大无法表示
      |    +-- ZeroDivisionError  # 除(或取模)零 (所有数据类型)
      +-- AssertionError  # 当assert语句失败时引发
      +-- AttributeError  # 属性引用或赋值失败
      +-- BufferError  # 无法执行与缓冲区相关的操作时引发
      +-- EOFError  # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
      +-- ImportError  # 导入模块/对象失败
      |    +-- ModuleNotFoundError  # 无法找到模块或在在sys.modules中找到None
      +-- LookupError  # 映射或序列上使用的键或索引无效时引发的异常的基类
      |    +-- IndexError  # 序列中没有此索引(index)
      |    +-- KeyError  # 映射中没有这个键
      +-- MemoryError  # 内存溢出错误(对于Python 解释器不是致命的)
      +-- NameError  # 未声明/初始化对象 (没有属性)
      |    +-- UnboundLocalError  # 访问未初始化的本地变量
      +-- OSError  # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
      |    +-- BlockingIOError  # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
      |    +-- ChildProcessError  # 在子进程上的操作失败
      |    +-- ConnectionError  # 与连接相关的异常的基类
      |    |    +-- BrokenPipeError  # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
      |    |    +-- ConnectionAbortedError  # 连接尝试被对等方中止
      |    |    +-- ConnectionRefusedError  # 连接尝试被对等方拒绝
      |    |    +-- ConnectionResetError    # 连接由对等方重置
      |    +-- FileExistsError  # 创建已存在的文件或目录
      |    +-- FileNotFoundError  # 请求不存在的文件或目录
      |    +-- InterruptedError  # 系统调用被输入信号中断
      |    +-- IsADirectoryError  # 在目录上请求文件操作(例如 os.remove())
      |    +-- NotADirectoryError  # 在不是目录的事物上请求目录操作(例如 os.listdir())
      |    +-- PermissionError  # 尝试在没有足够访问权限的情况下运行操作
      |    +-- ProcessLookupError  # 给定进程不存在
      |    +-- TimeoutError  # 系统函数在系统级别超时
      +-- ReferenceError  # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
      +-- RuntimeError  # 在检测到不属于任何其他类别的错误时触发
      |    +-- NotImplementedError  # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
      |    +-- RecursionError  # 解释器检测到超出最大递归深度
      +-- SyntaxError  # Python 语法错误
      |    +-- IndentationError  # 缩进错误
      |         +-- TabError  # Tab和空格混用
      +-- SystemError  # 解释器发现内部错误
      +-- TypeError  # 操作或函数应用于不适当类型的对象
      +-- ValueError  # 操作或函数接收到具有正确类型但值不合适的参数
      |    +-- UnicodeError  # 发生与Unicode相关的编码或解码错误
      |         +-- UnicodeDecodeError  # Unicode解码错误
      |         +-- UnicodeEncodeError  # Unicode编码错误
      |         +-- UnicodeTranslateError  # Unicode转码错误
      +-- Warning  # 警告的基类
           +-- DeprecationWarning  # 有关已弃用功能的警告的基类
           +-- PendingDeprecationWarning  # 有关不推荐使用功能的警告的基类
           +-- RuntimeWarning  # 有关可疑的运行时行为的警告的基类
           +-- SyntaxWarning  # 关于可疑语法警告的基类
           +-- UserWarning  # 用户代码生成警告的基类
           +-- FutureWarning  # 有关已弃用功能的警告的基类
           +-- ImportWarning  # 关于模块导入时可能出错的警告的基类
           +-- UnicodeWarning  # 与Unicode相关的警告的基类
           +-- BytesWarning  # 与bytes和bytearray相关的警告的基类
           +-- ResourceWarning  # 与资源使用相关的警告的基类。被默认警告过滤器忽略。

20. copy 和 deepcopy 的区别是什么?

a = [1,[2,3]]
b = a # 引用 a变b变
c = copy.copy(a) # 浅复制 复制子对象的引用 
d = copy.deepopy(a) # 深复制 复制子对象的副本
# copy模块的信息
help(copy)

python 文档 > 两者区别仅与复合对象 (即包含其他对象的对象,如列表或类的实例) 相关: > - 一个 浅层复制 会构造一个新的复合对象,然后(在可能的范围内)将原对象中找到的 引用 插入其中。 > - 一个 深层复制 会构造一个新的复合对象,然后递归地将原始对象中所找到的对象的 副本 插入。 > > 深度复制操作通常存在两个问题, 而浅层复制操作并不存在这些问题: > - 递归对象 (直接或间接包含对自身引用的复合对象) 可能会导致递归循环。 > - 由于深层复制会复制所有内容,因此可能会过多复制(例如本应该在副本之间共享的数据)。 > > Python的深度复制操作通过以下方式避免了上面俩问题: > a)保留一个字典,这个字典是在当前复制过程中已复制的对象的 "备忘录" (memo) 字典 > b)允许用户定义的类重载复制操作或复制的组件集合 > > 该模块不复制模块、类、函数、方法、栈追踪(stack trace)、栈帧(stack frame)、文件、套接字、窗口、数组或任何类似类型的类型。 > 制作字典的浅层复制可以使用 dict.copy() 方法,而制作列表的浅层复制可以通过赋值整个列表的切片完成,例如, > copied_list = original_list[:]

21. 代码中经常遇到的*args, **kwargs 含义及用法。

args 是 arguments 的缩写,表示位置参数; kwargs 是 keyword arguments 的缩写,表示关键字参数

def demo_func(*args, **kwargs):
    # arg是一个元祖类型
    print(args[1])
    # kwargs是一个字典类型
    print(kwargs.keys())


if __name__ == '__main__':
    # 直接传参,但关键字类型必须为str
    demo_func(1, 2, 3, a=1, b=2)
    # 使用*和**进行解包
    demo_func(*(1, 2, 3), **{"a": 1, "b": 2})

22. Python 中会有函数或成员变量包含单下划线前缀和结尾,和双下划线前缀结尾,区别是什么?

单下划线 单下划线开头的命名方式被常用于模块中,在一个模块中以单下划线开头的变量和方法会被默认划入模块内部范围。 当使用 from my_module import * 导入时,单下划线开头的变量和方法是不会被导入的。 但使用 import my_module 导入的话,仍然可以用 my_module._var 这样的形式访问属性或方法。单下划线结尾的命名方式也存在,但是不常用,其实也不推荐用。这种命名方式的作用就是为了和 python 的一些内置关键词区分开来,假设我们想给一个变量命名为 class,但是这会跟 python 的关键词 class 冲突,所以我们只好退一步使用单下划线结尾命名,也就是 class_。双下划线双下划线开头和结尾的是一些 python 的“魔术”对象,如类成员的 __init____del____add____getitem__ 等,以及全局的__file____name__ 等。 python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数,而是按照文档说明来使用。双下划线开头的命名方式有实际的作用,采用这种命名的变量或方法无法直接通过 “对象名.变量名(方法名)” 这样的方式访问。

23. w、a+、wb 文件写入模式的区别

r : 读取文件,若文件不存在则会报错 w: 写入文件,若文件不存在则会先创建再写入,会覆盖原文件 a : 写入文件,若文件不存在则会先创建再写入,但不会覆盖原文件,而是追加在文件末尾 rb,wb:分别于r,w类似,用于读写二进制文件 r+ : 可读、可写,文件不存在也会报错,写操作时会覆盖 w+ : 可读,可写,文件不存在先创建,会覆盖 a+ :可读、可写,文件不存在先创建,不会覆盖,追加在末尾

24. 举例 sort 和 sorted 的区别

sorted是一个函数,返回一个新的list sort是实例方法,直接作用在list本身,没有返回新的list

demo_list = [1, 3, 4, 2, 7, 5]
result_list = sorted(demo_list)
print(result_list)
demo_list.sort()
print(demo_list)
### 25. 什么是负索引? 负索引是指使用负数做为索引,-1代表数组的最后一位

26. pprint 模块是干什么的?

pprint用于输出一个整齐美观Python数据的结构

import pprint

demo_list = [str(i)*20 for i in range(10)]
pp_object = pprint.PrettyPrinter(indent=4) # indent 是指句首缩进
pp_object.pprint(demo_list)  # 整齐输出
print(demo_list)             # 只输出一行
### 27. 解释一下 Python 中的赋值运算符 赋值运算符包括:=+=-=*=/= 取模%=**= 取整除//=

28. 解释一下 Python 中的逻辑运算符

逻辑运算符包括:与andornor

29. 讲讲 Python 中的位运算符

位运算符包括:按位与& 按位或| 按位异或^ 按位取反~ 左移动<< 右移动>>

30. 在 Python 中如何使用多进制数字?

二进制前缀 0b0B 八进制前缀 0o0O 十六进制前缀 0x0X

int(0b1010) # 10
int(0o10) # 8
int(0xf) # 15

bin(15) # '0b1111'
oct(15) # '0o17'
hex(15) # '0xf'

31. 怎样声明多个变量并赋值?

a, b = 1, 2

算法和数据结构

1. 已知:

(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. 插入操作两个相同;

2. 用 Python 实现一个二分查找的函数

def binary_Search(search_list: list, search_num: int):
    """二分查找

    利用二分法找到list数组中的值
    :param search_list: 目标list
    :param search_num: 待查询值
    :return:
    """
    # 最小的下标
    min_index = 0
    # 最大的下标
    max_index = len(search_list) - 1
    # 查找次数
    count = 0
    while True:
        # 中间的下标每次向下取整
        mid_index = (min_index + max_index) // 2
        if search_num > search_list[mid_index]:
            # 小于需要的猜的数,则将最小下标变为中间的,因为中间的已经猜过,所以要加1
            min_index = mid_index + 1
        elif search_num < search_list[mid_index]:
            # 大于需要的猜的数,则将最大下标变为中间的,因为中间的已经猜过,所以要减1
            max_index = mid_index - 1
        else:
            print("找到数据", "索引是{}".format(mid_index))
            print("一共查找{}次".format(count))
            break
        # 索引值加一
        print(f"{search_num}{search_list[mid_index]} 相比较")
        count += 1

if __name__ == "__main__":
    list1 = [i for i in range(0, 1000)]
    num = 0
    binary_Search(list1, num)

3. python 单例模式的实现方法

应用单例模式的类只会生成一个实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。 下面是使用四种方式实现单例模式:原文链接 - 使用函数装饰器实现单例

def singleton(cls):
    _instance = {}

    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner
    
@singleton
class Cls(object):
    def __init__(self):
        pass

cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))
- 使用类装饰器实现单例
class Singleton(object):
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}
    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]

@Singleton
class Cls2(object):
    def __init__(self):
        pass

cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))

# 同样也可以用下面的方式
class Cls3():
    pass

Cls3 = Singleton(Cls3)
cls3 = Cls3()
cls4 = Cls3()
print(id(cls3) == id(cls4))
- 使用 new 关键字实现单例
class Single(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kw)
        return cls._instance
    def __init__(self):
        pass

single1 = Single()
single2 = Single()
print(id(single1) == id(single2))
- 使用 metaclass 实现单例
class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Cls4(metaclass=Singleton):
    pass

cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))

4. 使用 Python 实现一个斐波那契数列

def fbnq(num):
    """斐波那契生成器

    :param num: 生产数量
    :return: 斐波那契迭代器
    """
    a, b = 1, 1
    for _ in range(num):
        a, b = b, a+b
        yield a

if __name__ == '__main__':
    gener = fbnq(20)
    print(list(gener))
    for i in gener:
        print(i)

5. 找出列表中的重复数字

# 统计每个数字个数
from collections import Counter
result = Counter([1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4])
print(result)
# 

6. 找出列表中的单个数字

同上

7. 写一个冒泡排序

def bubble_sort(parm_list):
    for n in range(len(parm_list)):
        for m in range(len(parm_list) -n -1):
            if parm_list[m] > parm_list[m +1]:
                parm_list[m], parm_list[m +1] = parm_list[m +1], parm_list[m]
    return parm_list

8. 写一个快速排序

快排: 每次选取第一个值为基准值,再把列表中比基准值大的组成新列表,小的组成另一个新列表,再次对两个新列表进行操作,直到新列表为空

def quick_sort(parm_list):
    if not parm_list:
        return []
    else:
        pivot = parm_list[0]
        # 利用递归每次找出大于和小于基准值得两个新列表
        lesser = quick_sort([x for x in parm_list[1:] if x < pivot])
        greater = quick_sort([x for x in parm_list[1:] if x >= pivot])
        # 最后将排列好的值相加
        return lesser + [pivot] + greater

9. 写一个拓扑排序

def topology_sort(relation_parm):
    """简单拓扑排序

    简单实现拓扑排序算法,不考虑时间复杂度
    :param relation_parm: 拓扑关系字典参数
    :return: 排序后结果
    """
    in_degrees = dict((u, 0) for u in relation_parm)            # 初始化所有顶点入度为0,转为字典类型
    vertex_num = len(in_degrees)                                # 字典键值个数
    for key in relation_parm:                                   # 遍历关系参数字典,求出每个顶点的入度值
        for value in relation_parm[key]:                        # 遍历每个键的值,将关系其他顶点的键值+1到in_degrees字典中
            in_degrees[value] += 1                              # 计算每个顶点的入度
    zero_list = [u for u in in_degrees if in_degrees[u] == 0]   # 筛选入度为0的顶点
    result_list = []                                            # 创建结果空列表
    while zero_list:                                            # 循环判断列表为空
        last_zero = zero_list.pop()                             # 删除最后一个入度为0的对象
        result_list.append(last_zero)                           # 结构列表添加刚删除的对象
        for zero_value in relation_parm[last_zero]:             # 遍历以入度为0作为键的字典值
            in_degrees[zero_value] -= 1                         # 和last_zero有关系的键值-1
            if in_degrees[zero_value] == 0:                     # 判断-1过后的键值是否为0
                zero_list.append(zero_value)                    # 再次筛选入度为0的顶点
    if len(result_list) != vertex_num:                          # 如果循环结束后结果列表对象个数不等于原始键个数,说明存在环
        print("there's a circle.")
    else:
        return result_list


relation_dict = {
    'a': 'bcd',
    'b': '',
    'c': 'be',
    'd': 'e',
    'e': '',
    'f': 'de'
}
print(topology_sort(relation_dict)) # ['f', 'a', 'd', 'c', 'e', 'b']

11. python 实现一个二进制计算

12. 有一组“+”和“-”符号,要求将“+”排到左边,“-”排到右边,写出具体的实现方法。

13. 单链表反转

14. 交叉链表求交点

15. 用队列实现栈

16. 找出数据流的中位数

17. 二叉搜索树中第 K 小的元素

爬虫相关

1. 在 requests 模块中,requests.content 和 requests.text 什么区别

.content中间存的是字节码 .text存的是.content编码后的字符串

2. 简要写一下 lxml 模块的使用方法框架

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
htmlEmt = etree.HTML(text)

result = etree.tostring(htmlEmt)

3. 说一说 scrapy 的工作流程

  1. 首先Spiders(爬虫)将需要发送请求的url(requests)经ScrapyEngine(引擎)交给Scheduler(调度器)。
  2. Scheduler(排序,入队)处理后,经ScrapyEngine,DownloaderMiddlewares(可选,主要有User_Agent, Proxy代理)交给Downloader。
  3. Downloader向互联网发送请求,并接收下载响应(response)。将响应(response)经ScrapyEngine,SpiderMiddlewares(可选)交给Spiders。     
  4. Spiders处理response,提取数据并将数据经ScrapyEngine交给ItemPipeline保存(可以是本地,可以是数据库)。
  5. 提取url重新经ScrapyEngine交给Scheduler进行下一个循环。直到无Url请求程序停止结束。

4. scrapy 的去重原理

  1. Scrapy本身自带有一个中间件;
  2. scrapy源码中可以找到一个dupefilters.py去重器;
  3. 需要将dont_filter设置为False开启去重,默认是false去重,改为True,就是没有开启去重;
  4. 对于每一个url的请求,调度器都会根据请求得相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中的指纹信息进 行 比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中;
  5. 如果set()集合中没有存在这个加密后的数据,就将这个Request对象放入队列中,等待被调度。

5. scrapy 中间件有几种类,你用过哪些中间件

spider中间件(主职过滤)对Request、Response的主要作用在过滤,可以对特定路径的URL请求丢弃、对特定页面响应过滤、同时对一些不含有指定信息的item过滤,当然pipeline也能实现item的过滤。 下载中间件(主职加工)主要作用是加工,如给Request添加代理、添加UA、添加cookie,对Response返回数据编码解码、压缩解压缩、格式化等预处理。

user-agend中间件、代理ip中间件、selenium中间件、cookie中间件

6. 你写爬虫的时候都遇到过什么?反爬虫措施,你是怎么解决的?

反爬策略1:通过UA限制或者其他头信息限制 解决方案:构建用户代理池或其他头信息 反爬策略2:通过访问者IP限制 解决方案:构建IP代理池 反爬策略3:通过验证码限制 解决方案:手工打码、验证码接口自动识别或者通过机器学习自动识别 反爬策略4:通过数据的异步加载限制 解决方案:抓包分析或者使用PhantomJS 反爬策略5:通过Cookie限制 解决方案:进行Cookie处理 反爬策略6:通过JS限制(如请求的数据通过JS随机生成等)解决方案:分析JS解密或者使用PhantomJS

7. 为什么会用到代理?

网站采取了反爬虫措施——限制IP的访问,这时就用到了代理IP,将本机的IP伪装起来,通过代理服务器发送请求。

8. 代理失效了怎么处理?

换一个?!用代理池?

9. 列出你知道 header 的内容以及信息

主要的请求方式GETPOST Host: 请求的Web服务器地址 User-Agent: 客户端浏览器信息 Accept: 客户端能够接收的内容类型

10. 说一说打开浏览器访问 百度一下,你就知道 获取到结果,整个流程。

  1. 浏览器向 DNS 服务器发送 baidu.com 域名解析请求。
  2. DNS 服务器返回解析后的 ip 给客户端浏览器,浏览器想该 ip 发送⻚⾯请求。
  3. DNS 服务器接收到请求 后,查询该⻚⾯,并将⻚⾯发送给客户端浏览器。
  4. 客户端浏览器接收到⻚⾯后,解析⻚⾯中的引⽤,并再 次向服务器发送引⽤资源请求。
  5. 服务器接收到资源请求后,查找并返回资源给客户端。
  6. 客户端浏览器接收 到资源后,渲染,输出⻚⾯展现给⽤户。

11. 爬取速度过快出现了验证码怎么处理

最好方法就是尽量避免遇到它,采集速度不要太快,短时间内不要过度频繁的访问一个网站,而是要表现得更像一个人,模拟人浏览网页的操作行为。 将许多验证码解算器集成到他的爬虫系统中。如:验证码识别服务供应商 Death by CAPTCHABypass CAPTCHA 都允许用户通过调用API服务来进行自动打码,从而在抓取数据过程中自动解决验证码。这些验证码解决工具可以处理普通的文本验证码,甚至是更高级的验证码

12. scrapy 和 scrapy-redis 有什么区别?为什么选择 redis 数据库?

Scrapy 是一个通用的爬虫框架,但是不支持分布式. Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础的组件(仅有组件)。

13. 分布式爬虫主要解决什么问题

  1. ip
  2. 带宽
  3. cpu
  4. io

14. 写爬虫是用多进程好?还是多线程好? 为什么?

IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。 在实际的数据采集过程中,既考虑网速和响应的问题,也需要考虑自身机器的硬件情况,来设置多进程或多线程. phantomjs 或者chrome-headless 来抓取的爬虫,应当是多进程的,因为每一个phan/chro 实例就是一个进程了,并发只能是多进程。此外爬虫中还是数据处理业务,如果数据处理业务是一个比较耗时的计算型操作,那么对数据处理部分应当设为多进程,但更多可能会考虑将该部分数据处理操作和爬虫程序解耦,也就是先把数据抓取下来,事后单独运行另外的程序解析数据。

15. 解析网页的解析器使用最多的是哪几个

lxml, re, beautifulsope.

16. 需要登录的网页,如何解决同时限制 ip,cookie,session(其中有一些是动态生成的)在不使用动态爬取的情况下?

解决限制 IP 可以使用代理 IP 地址池、服务器; 不适用动态爬取的情况下可以使用反编译 JS 文件获取相应的文件,或者换用其他平台(比如手机端) 看看是否可以获取相应的 json 文件。

17. 验证码的解决(简单的:对图像做处理后可以得到的,困难的:验证码是点击,拖动等动态进行的?)

图形验证码:干扰、杂色不是特别多的图片可以使用开源库 Tesseract 进行识别,太过复杂的需要借助第三方打码平台。 点击和拖动滑块验证码:可以借助 selenium、无图形界面浏览器(chromedirver 或者 phantomjs) 和 pillow 包来模拟人的点击和滑动操作,pillow 可以根据色差识别需要滑动的位置。 手动打码(有的验证码确实无解)

18. 使用最多的数据库(mysql,mongodb,redis 等),对他的理解?

MySQL 数据库:开源免费的关系型数据库,需要实现创建数据库、数据表和表的字段,表与表之间可以进行关联(一对多、多对多),是持久化存储。 Mongodb 数据库:是非关系型数据库,数据库的三元素是,数据库、集合、文档,可以进行持久化存储,也可作为内存数据库,存储数据不需要事先设定格式,数据以键值对的形式存储。 redis 数据库:非关系型数据库,使用前可以不用设置格式,以键值对的方式保存,文件格式相对自由,主要用与缓存数据库,也可以进行持久化存储。

网络编程

1. TCP 和 UDP 的区别?

  1. 基于连接与无连接;
  2. 对系统资源的要求(TCP较多,UDP少);
  3. UDP程序结构较简单;
  4. 流模式与数据报模式 ;
  5. TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
TCP UDP
是否连接 面向连接 面向非连接
是否可靠 可靠 不可靠
数据量 少量 大量
速度

2. 简要介绍三次握手和四次挥手

  1. 第一次握手:客户端发送SYN包(SYN=j)到服务器,并进入SYN_SEND状态,等待服务器确认。
  2. 第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。完成三次握手,客户端与服务器开始传送数据

由于TCP连接是全双工的,连接的拆除需要发送四个包,因此称为“四次挥手”。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。 1. 第一次挥手:客户端发送一个FIN,用来关闭客户到服务器的数据传送。 2. 第二次挥手:服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。 3. 第三次挥手:服务器关闭与客户端的连接,发送一个FIN给客户端。 4. 第四次挥手:客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

3. 什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?

  1. 粘包的概念 粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

  2. 出现粘包的原因出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。 发送方引起的粘包是由TCP协议本身造成的:,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。 接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。