最新python面试题180题完整版带答案(转载加整理)


1.列出 5 个常用 Python 标准库?
os:             提供了不少与操作系统相关联的函数  示例:os.system('ls')
sys:                通常用于命令行参数                示例:sys.path
re:              正则匹配                          示例:re.match('www','www.zhihu.com')
math:            数学运算                          示例:math.cos(math.pi/3)
datetime:        处理日期时间                      示例:time.sleep(3)
random:          随机数模块                        示例:random.random()
threading:       线程模块                          示例:threading.Thread(target=lambda a, b:a.append(b),args=([1,2],3))
multiprocessing: 进程模块                          示例:multiprocessing.Process(target=method,args=([1,2],3)) #pickle模块不能序列化lambda,需要自定义函数

 

2.Python 内建数据类型有哪些?
数值型--int、float、complex
布尔型--bool、True、False
字符串--str
列表--list
元组--tuple
字典--dict
集合--set

3.简述 with 方法打开处理文件帮我我们做了什么?
with语句适用于对于资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的"清理"操作,释放资源,比如文件使用后自动关闭,线程中锁的自动获取和释放等.
with语句即"上下文管理器",在程序中用来表示代码执行过程中所处的前后环境,上下文管理器:含有__enter__和__exit__方法的对象就是上下文管理器.
__enter__():在执行语句之前,首先执行该方法,通常返回一个实例对象,如果with语句有as目标,则将对象赋值给as目标.
__exit__():执行语句结束后,自动调用__exit__()方法,用户释放资源,若此方法返回布尔值True,程序会忽略异常.
使用环境:文件读写,线程锁的自动释放等.

f = open("./1.txt","wb")
try:
    f.write("hello world")
except:
    pass
finally:
    f.close()
---------------------------
with open("./1.txt", "wb") as f:
    f.write("hello world")


打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open写法,我们需要try,except,finally,做异常判断,并且文件最终不管遇到什么情况,
都要执行finally f.close()关闭文件,with方法帮我们实现了finally中f.close

4.列出 Python 中可变数据类型和不可变数据类型,为什么?
不可变数据类型:当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。
可变数据类型:当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。
总结:不可变数据类型更改后地址发生改变,可变数据类型更改地址不发生改变
整型     不可变
字符串     不可变
元组     不可变
列表     可变
集合     可变
字典     可变

5.Python 获取当前日期?
 

import datetime
print(datetime.datetime.now())

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

def wordAmount(sentence):
    dict_result = {}
    word_list = sentence.split()
    for word_name in word_list:
        if word_name not in dict_result.keys():
            dict_result[word_name] = 1
        else:
            dict_result[word_name] += 1
    return dict_result
if __name__ == '__main__':
    sentence = "I can because i think i can"
    dict_result = wordAmount(sentence)
    print(dict_result)
    
"""
sentence = "I can because i think i can"
result = {word:sentence.split().count(word) for word in set(sentence.split())}
print(result)
"""
"""
from collections import Counter
sentence = "I can because i think i can"
counts = Counter(sentence.split())
print(counts)
"""

7.用 python 删除文件和用 linux 命令删除文件方法
#python 方法
import os
os.remove("demo.txt")
#linux方法
rm demo.txt

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

def judge_value(num_value):
    """自定义异常函数
    
    自定义异常函数,用于抛出大于一定值的异常
    :param num_value:用于判断的值
    :return:异常信息
    """
    if num_value > 10:
        #raise用于抛出自定义异常,格式为:raise 异常类型(异常注明)
        #一旦触发则不再执行raise之后的代码
        raise ValueError("数量不能大于10")
    else:
        return "200"
if __name__ == '__main__':
    judge_value(10)


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

def read_filedata(file_name):
    """读取文件数据
    
    读取指定文件中的所有数据返回数据或者异常信息
    :param file_name:文件路径
    :return:文件数据或异常信息
    """
    file_obj = ""
    try:
        #需要检测的异常代码片段
        file_obj = open(file_name,"r")
        result_date = file_obj.read()
    except IOError, e:
        #发生“IOError”异常进行处理的代码片段
        file_obj = "文件不存在:"+str(e)
    else:
        #没有引发“IOError”异常执行的代码片段”
        #返回读取到的数据
        return result_date
    finally:
        #不管有没有引发错误都会执行的代码片段
        #isinstance()用于判断一个数据类型
        if isinstance(file_obj,str):
            return file_obj
        elif isinstance(file_obj,file):
            file_obj.close()
        else:
            return "未知错误,请检查你的代码"
if __name__ == '__main__':
    result = read_filedata("abc.txt")
    print(result)


    
10.遇到 bug 如何处理
.首先检查报错信息,根据报错信息查找到相应的代码,通常一般的数据结构或算法错误只要找到报错代码就可以顺利解决.
.如果遇到暂时不能解决的错误,可以用编译器的Debug模式或者自己在代码中加注断点进行代码排查.
.如果依然不能解决bug,我们可以拷贝报错的信息,在搜索引擎中进行搜索.
.和同事讨论,比较费时
.另辟蹊径,进行快速开发时,我们应该优先实现功能而不是拘泥于运行效率,所以遇到一些暂时不能解决的bug可以考虑另外的实现方法

语言特性
1.谈谈对 Python 和其他语言的区别
Python是一门语法简洁优美,功能强大无比,应用领域非常广泛,具有强大完备的第三方库的一门强类型的动态,可移植,可扩展,可嵌入的解释型编程语言.(补充:如果语言经常隐式地转换变量类型,那这个语言就是弱类型语言,如果很少会这样做,那就是强类型语言.Python很少会隐式地转换变量的类型,所以是强类型语言)

解释性:解释型语言使用解释器将源码逐行解释成机器码并立即执行 ,不会进行整体性的编译和链接处理,相当于把编译语言中的编译和解释混合到一起同时完成.

优点:跨平台容易,只需要提供特定平台的解释器;
缺点:运行效率低,因为每次执行相当于都要进行一次编译.

简洁优雅:省略了各种大括号和分号,还有一些关键字,类型说明;

面向对象:Python和C++、Java一样都是面向对象的编程语言;

跨平台:简单搭建Python解释器可以在大部分的平台运行.

Python和Java相比:
Python是动态类型语言,Java是静态类型语言.
动态类型语言不需要事先声明变量的类型,而且变量的数据类型可以被修改
静态类型语言需要事先声明,并且不能修改;
Python和C相比:
对于使用:
Python的类库齐全并且使用简洁,很少代码实现的功能用C可能需要很复杂
对于速度:
Python的运行速度相较于C,绝对是很慢了.Python的CPython解释器是C语言编写的.

2.简述解释型和编译型编程语言
编译型语言:源程序--集中转换成机器指令--驱动机器运行--机器
解释型语言:源程序--逐条读取,逐条转换,逐条执行--机器

3.Python 的解释器种类以及相关特点?
CPython
当我们从Python的官方网站下载并安装好Python 3.x之后,我们就直接获得了一个官方版本的解释器:CPython.这个解释器是用C语言开发的,所以叫CPython.在命令行下运行python就是启动 Cpython的解释器.CPython是使用最广的Python解释器.

IPython
IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的.好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE.CPython用>>>作为提示符,而IPython用In[序号]:作为提示符

PyPy
PyPy是另一个Python解释器,他的目标是执行速度.PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度.绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果.如果你的代码放到PyPy下执行,需要了解PyPy和CPython的不同点.

Jython
Jython是运行在Java平台的Python解释器,可以直接把Python代码编译成Java字节码执行.

IronPython
和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码.

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

Python2中字符的类型:
str:已经编码后的字节序列
unicode:编码前的文本字符

Python3中的字符类型:
str:编码过的unicode文本字符
bytes:编码前的字节序列

我们可以认为字符串有两种状态,即文本状态和字节(二进制)状态.Python2和Python3中的两种字符类型都分别对应这两种状态,然后相互之间进行编解码转化.编码就是将字符串转换成字节码,涉及到字符串的内部表示;解码就是将字节码转换为字符串,将比特位显示成字符.在Python2中,str和Unicode都有encode和decode方法.但是不建议对str使用encode,对unicode使用decode,这是Python2设计上的缺陷.Python3则进行了优化,str只有一个encode方法将字符串转化为一个字节码,而且bytes也只有一个decode方法将字节码转化为一个文本字符串.

python2中需要在文件头打上注释#coding:utf-8制定该程序使用的编码格式为UTF-8

print
python2中的print是class;python3中的print是函数
python2的print声明已经被Print()函数取代了,这意味着我们必须包装我们想打印在小括号中的对象.

input
python2:input解析输入为int型,raw_input解析输入为str类型
python3:input解析输入为str字符型.

算术符
python2中带上小数点/表示真除,%表示取余,//结果取整
python3中/表示真除,%表示取余,//结果取整

xrange
python2中使用xrange()来创建一个迭代器对象,使用range()创建一个list数组
python3中使用range()创建一个迭代器对象,移除了xrange()方法

5.Python3 和 Python2 中 int 和 long 区别?
Python3里,只有一种整数类型int,大多数情况下,他很像python2里的长整型
python2有为非浮点数准备int和long类型.int类型的最大值不能超过sys.maxint,而且这个最大值是平台相关的

6.xrange 和 range 的区别?
python2中使用xrange()来创建一个迭代器对象,使用range()创建一个list数组
python3中使用range()创建一个迭代器对象,移除了xrange()方法

编码规范
7.什么是 PEP8?
PEP是Python Enhancement Proposal的缩写,翻译过来就是Python增强建议书,PEP8,简单说就是一种编码规范,是为了让代码更好看,更容易被阅读.

8.了解 Python 之禅么?
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
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.
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.
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!

Python之禅 by Tim Peters
 
优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的)
即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)
不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
当存在多种可能,不要尝试去猜测
而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)
虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )
做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)

9.了解 docstring 么?
Docstring文档字符串是一个重要工具,用于解释文档程序,帮助你的程序文档更加简单易懂.我们可以在函数体的第一行使用一对三个单引号或者一对三个双引号来定义文档字符串.
你可以使用__doc__(注意双下划线)调用函数中的文档字符串属性.
docstring文档字符串使用惯例:它的首行简述函数功能,第二行空行,第三行为函数的具体描述.

def create_iterator(list_param):
    """创建迭代器
    
    使用生成器推导式创建一个迭代器,并返回迭代器
    :param list_param:迭代对象
    :return:迭代器
    """
    
    #将列表推导式的"[]"改为"()"即为生成器推导式,众所周知,生成器返回一个迭代器对象
    return (value for value in list_param)
    
if __name__ == '__main__':
    #遍历迭代器
    for i in create_iterator([1,2,3]):
        print(i)
    #使用__doc__输出函数中的文档字符串属性
    print(create_iterator.__doc__)
    #使用__dir__输出函数中所有的属性和方法
    print(create_iterator.__dir__())


    
补充
类的函数称为方法(method),模块里的函数称为函数(function);
每一个包,模块,类,函数,方法都应该包含文档,包括类的__init__方法;
包的文档写在__init__.py文件中;
文档有单行文档和多行文档;
单行文档:
不要重复函数的声明语句,例如:function(a,b)->list;
指明做什么和返回什么,例如Do X and return a list.;
使用三引号,方便换行;

多行文档:
如果模块是一个脚本,也可就是单文件程序,模块的文档应该写明脚本的使用方法;
模块的文档需要写明包含的类,异常,函数;
如果是包,在__init__.py中,写明包里面包含的模块,子包;
如果是函数或类方法,应该写明函数或方法的作用,参数,返回,副作用,异常和调用的限制等;
如果是类,写明类的行为,和实例参数,构造方法写在__init__中;
使用三引号,而且两个三引号都应该单独成行

10.了解类型注解么?
首先,python是一种动态语言,变量和函数的参数是不区分类型的.
Python解释器会在运行的时候动态判断变量和参数的类型,这样的好处是编写代码速度很快,很灵活,但是坏处也很明显,不好维护,可能代码写过一段时间重新看就很难理解了,因为那些变量、参数、函数返回值的类型,全都给忘记了。
在阅读别人的代码时,无法看出变量或参数的类型,这样对工作效率带来很大的影响.
因此,在Python3中新添加了"类型注解"特性,可以给参数,函数返回值和变量的类型加上注解,该注解仅仅是注释而已,对代码运行不会产生任何影响,真正的变量类型还是有python解释器决定,你所做的只是提高了代码的可读性,并不会像静态语言中变量类型定以后就无法修改(强转除外).

def list_to_str(param_list:list,connect_str:str=" ") -> str:
    """列表转字符串
    
    使用join方法将列表转为字符串并返回
    :param param_list:列表
    :param connect_str:需要插入的字符.默认为一个空格
    :return :转换成功的字符串
    """
    demo_tuple: tuple = (1,2)
    demo_dict: dict = {"1":1}
    return connect_str.join(param_list)
    
if __name__ == '__main__':
    result = list_to_str(["Hello","world"])
    print(result)


以上代码可以看出,一般变量和函数参数注解格式为"参数:类型",默认参数是在类型的后面加"=默认值",函数的返回值注解格式为"->类型:",函数的冒号在注解后方.

类型注解仅仅起到注释作用,那我们应该如何知道他的正确性呢?
Python提供了一个工具方便我们测试类型注解的正确性
pip install mypy
使用方法:
mypy demo.py  
若无错误则无输出,反之会输出如下:
D:\code\web\flaskweb>mypy demo.py
demo.py:12: error: Incompatible return value type (got "str", expected "int")  


11.例举你知道 Python 对象的命名规范,例如方法或者类等
变量命名:字母数字下划线,不能以数字开头
 单下划线开头变量
 单下划线开头变量标明是一个受保护(protected)的变量,原则上不允许直接访问, 但外部类还是可以访问到这个变量.这只是一个约定,用于警告说明这是一个私有变量,外部类不要去访问他.
 双下划线开头变量
 双下划线开头的,表示的是私有类型(private)的变量.只能是允许这个类本身进行访问了,连子类也不可以.
 以双下划线开头,并且以双下划线结尾的,是内置变量.
 内置变量是可以直接访问的,不是private变量,如__init__,__import__或是__file__
 *不要自己定义内置变量
 xxx_,单下划线结尾的变量一般只是为了避免与python关键字的命名冲突
 USER_CONSTANT,大写加下划线,对于不会发生改变的全局变量,使用大写加下划线
 
函数和方法(类中叫方法,模块中叫函数)命名:
 总体而言应该使用小写和下划线,如:create_user():
 私有方法:小写和一个前导下划线,如def _create_user(self):
 私有方法和私有变量一样,并不是真正的私有访问权限.
 一般函数不要使用两个前导下划线(当遇到两个前导下划线时,python的名称改编特性将发挥作用)
 特殊方法:小写和两个前导下划线,两个后置下划线 def __init__(self):
 这种风格只应用于特殊函数,比如操作符重载等.
 函数参数:小写和下划线,缺省值等号两边无空格 def __init__(self,param=None):
 不要滥用*args和**kwargs,可能会破坏函数的健壮性
 
类命名:
 类总是使用驼峰格式命名,所有单词首字母大写,其余字母小写,如:Class CreateUser():
 类名应该简明,精确,并足以从中理解类所完成的工作.
 常见的一个方法是使用表示其类型或特性的后缀,例如:SQLEngine,MimeTypes
 对于基类而言,可以使用一个Base或者Abstract前缀
 
包和模块:
 小写字母,数字和下划线

12.Python 中的注释有几种?
单行注释以"#"开头
多行注释使用三个单引号或者双引号" ''' "或者" """ "
单引号和双引号混用,使用一种引号后其注释中的所有引号应该使用另一种

13.如何优雅的给一个函数加注释?
见第9条

14.如何给变量加注释?
变量注释使用行内注释,根据PEP8规范应该在代码后至少两个空格,注释由"#"和一个空格开始:
user_name = "Robin"   # 用户姓名
user_age = 26         # 用户年龄
user_gender = 1       # 用户性别,男为1,女为0
一行文本不宜超过79个字符(PEP8规范)

15.Python 代码缩进中是否支持 Tab 键和空格混用。
python是一门用空格缩进来区分代码层次的语言,其实python并没有强制要求用Tab缩进或用空格缩进,甚至空格按几个都没有强制要求(但在PEP8中建议了使用4个空格作为缩进)
但是却绝对不能混用Tab和空格!
python中不提倡使用tab缩进,不同编辑器对于TAB的解释是不同的,有的编辑器tab是4个字符宽,有的8个字符宽.如果有的地方用tab,有的地方用空格,在不同的地方,原本对其的代码就可能会不对齐.

16.是否可以在一句 import 中导入多个库?
可以在一句import中导入多个库,但一般不建议这么做,原因如下:
 更易于阅读
 更易于搜索
 更易于编辑
 更易于维护
另,导入多个模块语句最好以下面方式书写,使用空行将其分割
 python 标准库模块
 Python 第三方模块
 自定义模块
 
有的程序员喜欢这样导入模块
 from socker import *
 这样写的好处就是不需要我们一个个列出"socker"需要的方法,但是这样引入的弊端如下:
 占用更多的内存空间,不必要的方法或类可能会进行初始化
 代码可读性差,模块内部突然冒出一个没有见过也没有归属的方法,很是头疼

17.在给 Py 文件命名的时候需要注意什么?
在为python文件命名时,我们需要注意
不能以python中的关键字命名;
不能以标准库或常用第三方库命名,除非你想重写这些库;
不能用除字母数字下划线以外的字符命名,注意不要使用中文命名任何路径和可执行文件;
数字不能作为开头.

18.例举几个规范 Python 代码风格的工具
Pylint
安装:pip install pylint
使用:pylint demo.py
Black
安装:pip install black
使用:black demo.py
Autopep8
安装:pip install autopep8
使用:autopep8 --in-place --aggressive demo.py
Flake8
安装:pip install flake8
使用:flake8 demo.py


数据类型、字符串
19.列举 Python 中的基本数据类型?
数值型--int、float、complex
布尔型--bool、True、False
字符串--str
列表--list
元组--tuple
字典--dict

20.如何区别可变数据类型和不可变数据类型
可变数据类型:在内存id不变的情况下,数据的值可以改变

不可变数据类型:数据的值不能发生改变,如果值发生改变,那么内存id也会改变,这样就不是同一数据了

demo_list = [1,2,3]
print(id(demo_list))   # 4874760
demo_list.append(4)
print(id(demo_list))   # 4874760

demo_tuple = (1,2,3)
print(id(demo_tuple))  # 65021344
# demo_tuple.append(4)   # 不可变类型不能对值进行修改
demo_tuple = (1,2,3,4)   # 重新对变量赋值,变量和数据类型需要区分,变量!=数据,变量只是数据的载体
print(id(demo_tuple))  # 42116344


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

def first_capital(change_sentence:str)->str:
    """句子所有的单词首字母大写
    
    将句子的首字母大写并返回
    :param change_sentence:需要转换的句子
    :return:返回转换后的字符串
    """
    # 将句子使用split切割为list数组
    split_list = change_sentence.split()
    # 遍历列表长度值
    for i in range(len(split_list)):
        # 使用capitalize()函数将每个单词首字母转换成大写
        split_list[i] = split_list[i].capitalize()
        # 也可以用upper()方法,upper()可以把所有的小写字母转为大写,lower()是转小写
        # split_list[i] = split_list[i][0].upper() + split_list[i][1:]
    # 使用join将列表转为字符串,join方法将某一字符插入到列表字符的间隔中
    split_list = " ".join(split_list)
    return split_list
    
if __name__ == '__main__':
    change_sentence = "hello world"
    print(first_capital(change_sentence))
    # 有需要的同学可以使用匿名函数,列表推导式和map方法一行写出以上代码.map(function,iterable),返回一个迭代器
    print(" ".join(list(map(lambda word: word.capitalize(), change_sentence.split()))))

22.如何检测字符串中只含有数字?
isdigit()方法检测字符串是否只由数字组成.
 

demo_str = "123456"
print(demo_str.isdigit())   # 输出True
demo_str = "this is string example ... wow!!!"
print(demo_str.isdigit())   # 输出false

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

from functools import reduce

#第一种方法,使用字符串切片
demo_str = "ilovechina"
print(demo_str[::-1])

#第二种方法,使用列表的reverse方法
list_str = list(demo_str)
list_str.reverse()
print("".join(list_str))

#第三种方法:reduce累加方法
#具体步骤是将前两个字符初始化添加到lambda函数中,得到的结果在下一个字母做累加直到结束
#第一次:l + i = li
#第二次:o + li = oli
#第三次:v + oli = voli
#...
#第九次:a + nihcevoli = anihcevoli
print(reduce(lambda x, y: y+x, demo_str))

#使用栈,先进后出
def stack_demo(demo_str):
    #模拟全部入栈
    list_stack = list(demo_str)
    result_str = ""
    while len(list_stack) > 0:
        #模拟出栈
        result_str += list_stack.pop()
    return result_str

print(stack_demo(demo_str))

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

在 Python 中,提供了很多种字符串格式化的方式,分别是 %-formatting、str.format 和 f-string 。

%- 格式化

这种格式化方式来自于 C 语言风格的 sprintf 形式:
name = "weapon"
"Hello, %s." % name

C 语言的给实话风格深入人心,通过 % 进行占位。

为什么 %-formatting不好
不好的地方在于,如果字符串较长或较多的参数,那么可读性就变得很差。

str.format 格式化

PEP-3101 带来了 str.format ,它是对 %-formatting 的改进。它使用正常的函数调用语法,并且可以通过对要转换为字符串的对象的 __format __() 方法进行扩展。
"Hello, {}. You are {}.".format(name, age)
并支持字典形式传参,免于位置参数带来的麻烦:
"Hello, {name}. You are {age}.".format(name=name, age=age)
这两种方式代码效果相同,只是第一种方法需要严格控制传入的参数位置,而第二种方法没有这种限制, 并增加了代码的可读性。各种技巧可查看 Format Specification Mini-Language

为什么 str.format() 并不好
虽然它解决了字符串冗长情况下的可读性,但需要对字典传参基本是要重写一遍变量名,不够优雅。

f-string 格式化
PEP-0498 带来了 f-string 方式,它从 Python3.6 开始支持。这种方式也是使用 __format__ 协议进行格式化。
 
name = "Eric"
age = 74
f"Hello, {name}. You are {age}."

语法上与 str.format() 类似,但更为简洁,当字符串较长时也不会繁琐。更强大的是它支持任意的表达式。我们可以在花括号内进行四则运算或函数调用等:f"{2 * 6}" 或者 f"{name.lower()} is funny" 。

并且它性能也最好。
    
 

import timeit
def add():
  status = 200
  body = 'hello world'
  return 'Status: ' + str(status) + '\r\n' + body + '\r\n'
def old_style():
  status = 200
  body = 'hello world'
  return 'Status: %s\r\n%s\r\n' % (status, body)
def formatter1():
  status = 200
  body = 'hello world'
  return 'Status: {}\r\n{}\r\n'.format(status, body)
def formatter2():
  status = 200
  body = 'hello world'
  return 'Status: {status}\r\n{body}\r\n'.format(status=status, body=body)
def f_string():
  status = 200
  body = 'hello world'
  return f'Status: {status}\r\n{body}\r\n'
perf_dict = {
  'add': min(timeit.repeat(lambda: add())),
  'old_style': min(timeit.repeat(lambda: old_style())),
  'formatter1': min(timeit.repeat(lambda: formatter1())),
  'formatter2': min(timeit.repeat(lambda: formatter2())),
  'f_string': min(timeit.repeat(lambda: f_string())),
}
print(perf_dict)

结果:    
{
  'add': 0.8815229000000002,
  'old_style': 0.6351808999999999,
  'formatter1': 0.7536176999999995,
  'formatter2': 1.2277180999999997,
  'f_string': 0.4891379000000011
}

f-string 格式化的方式性能最好。

为何 f-string 速度如此快

从指令来看,f'Status: {status}\r\n{body}\r\n' 翻译成:
8 LOAD_CONST        3 ('Status: ')
10 LOAD_FAST        0 (status)
12 FORMAT_VALUE       0
14 LOAD_CONST        4 ('\r\n')
16 LOAD_FAST        1 (body)
18 FORMAT_VALUE       0
20 LOAD_CONST        4 ('\r\n')
22 BUILD_STRING       5

正如指令中所示的,f-string 是运行时渲染的,底层中转成了类似 "Status: " + status+ "\r\n" + body + "\r\n" 的形式。正如 PEP-0498 中提到的:

F-strings provide a way to embed expressions inside string literals, using a minimal syntax. It should be noted that an f-string is really an expression evaluated at run time, not a constant value. In Python source code, an f-string is a literal string, prefixed with 'f', which contains expressions inside braces. The expressions are replaced with their values.

而其他方式则是要先创建字符串常量值,再进行替换之类的操作。

总结

我们仍然可以使用以前的方式进行格式化,但在此推荐 f-string 方式,因为它使用更简洁,更易读且更方便,性能又更好,完全没理由拒绝啊。

从今天开始使用 f-string!25.有一个字符串开头和末尾都有空格,比如“ adabdw ”,要求写一个函数把这个字符串的前后空格都去掉。

demo_str = " adabdw "
#去除两端空格使用:strip()
print(demo_str.strip())

#去除右端空格使用:rstrip()
print(demo_str.rstrip())

#去除左端空格使用:lstrip()
print(demo_str.lstrip())

#replace替换
print(demo_str.replace(" ",""))

#正则
import re
print(re.sub(" ","",demo_str))

26.获取字符串”123456“最后的两个字符。
 

demo_str = "123456"
print(demo_str[-2:])

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

import chardet
demo_str = "demo string".encode("gbk")
demo_str = demo_str.decode('gbk').encode('utf-8')
print(demo_str)
#chardet.detect()可以检测编码格式
print(chardet.detect(demo_str))
#sys.getdefaultencoding()获取当前系统的编码格式
print(sys.getdefaultencoding())

28. (1)s="info:xiaoZhang 33 shandong",用正则切分字符串输出['info', 'xiaoZhang', '33', 'shandong'](2) a = "你好 中国 ",去除多余空格只留一个空格。
(1)

import re
demo_str = "info:xiaoZhang 33 shandong"
#compile函数用于编译正则表达式,生成一个正则表达式(pattern)对象
#由题可知需要去除字母数字之外的字符,故使用"\W"做匹配
pattern = re.compile(r'\W')
#re.split()方法按照能够匹配的子串将字符串分割后返回列表
print(pattern.split(demo_str))


(2)
 

import re
a = "你好 中国 "
print(a.rstrip())

29. (1)怎样将字符串转换为小写 (2)单引号、双引号、三引号的区别?
列表
(1)

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


(2)
单双引号的混合使用可以避免转义字符的使用,使代码看上去更简洁优美
三引号可以用来做多行注释,这是单双引号不具备的
30.已知 AList = [1,2,3,1,2],对 AList 列表元素去重,写出具体过程。

import pandas
#第一种方法使用set集合,先转为集合载转回列表
AList = [1,2,3,1,2]
result_list = list(set(AList))
print(result_list)

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

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

#第四种,使用pandas.unique()方法
AList = [1,2,3,1,2]
result_list = pandas.unique(AList).tolist()
print(result_list)


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

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

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

listA = [1,2,A,B]
listB = [2,3,a,B,c]

#找相同
same_list = [i for i in listA if i in listB]
print (same_list)

#找不同
different_list = [i for i in listA if i not in listB]
print(different_list)

#进阶版\
listA = [1,2,"A","B"]
listB = [2,3,"a","B","c"]
Listall= listA+listB
listC = []
listD = []
for i in Listall:
    if i in listB and i in listA:
        listC.append(i)
    else:
        listD.append(i)
print(set(listC))
print(set(listD))

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

A=question_list = [[1,2],[3,4],[5,6]]
#使用列表推导式嵌套的时候,注意前后的调用关系,前推导式的值需要在后面书写才能生效
#需要输出的值放到推导式的最前面,生成输出值的推导式在最后面
print([list_int for inside_list in question_list for list_int in inside_list])

print(A[0][0]+A[0][1]+A[1][0]+A[1][1]+A[2][0]+A[2][1])

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

list_a = [1,5,7,9]
list_b = [2,2,6,8]
# 第一种方法,使用运算符"+"
combine_list = list_a +list_b
print(combine_list)

#第二种办法:使用运算符extend()方法
list_a.extend(list_b)
print(list_a)

# 第三种方法:使用append
list_a = [1,5,7,9]
for i in list_b:
    list_a.append(i)
print(list_a)

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

import random
demo_list = [1,2,3,4,5,6,7,8,9,0,15,49,12,23]
# 使用random.shuffle打乱一个list数组
random.shuffle(demo_list)
print(demo_list)


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

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

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

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

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

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

a = {"A":1,"B":2}
b = {"C":3,"D":4}
# 使用update方法
a.update(b)
print(a)

# 补充clear方法,清空字典中的键值对
a.clear()
print(a)

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

# 生成一个以姓名为键的字典,值为"student"
student_dic = {name:"student" for name in ["Robin","Bob","Jams"]}
print(student_dic) #{'Robin':'student','Bob':'student','Jams':'student'}

# 将一个字典中的姓名首字母转换为大写
name_dic = {1:"robin",2:"bob",3:"jams"}
# items()以列表返回可遍历的(键,值)元组数组
# dict_items([(1,'robin'),(2,'bob'),(3,'jams')])
result_dic = {k:v.capitalize() for k, v in name_dic.items()}
print(result_dic) #{1:'Robin',2:'Bob',3:'Jams'}

创建字典
# 创建空字典

>>> dic = {}
>>> type(dic)


# 直接赋值创建

>>> dic = {'spam':1, 'egg':2, 'bar':3}
>>> dic
{'bar': 3, 'egg': 2, 'spam': 1}

# 通过关键字dict和关键字参数创建

>>> dic = dict(spam = 1, egg = 2, bar =3)
>>> dic
{'bar': 3, 'egg': 2, 'spam': 1}

# 通过二元组列表创建

>>> list = [('spam', 1), ('egg', 2), ('bar', 3)]
>>> dic = dict(list)
>>> dic
{'bar': 3, 'egg': 2, 'spam': 1}

# dict和zip结合创建

>>> dic = dict(zip('abc', [1, 2, 3]))
>>> dic
{'a': 1, 'c': 3, 'b': 2}

# 通过字典推导式创建

>>> dic = {i:2*i for i in range(3)}
>>> dic
{0: 0, 1: 2, 2: 4}

# 通过dict.fromkeys()创建

通常用来初始化字典, 设置value的默认值

>>> dic = dict.fromkeys(range(3), 'x')
>>> dic
{0: 'x', 1: 'x', 2: 'x'}

# 其他

>>> list = ['x', 1, 'y', 2, 'z', 3]
>>> dic = dict(zip(list[::2], list[1::2]))
>>> dic
{'y': 2, 'x': 1, 'z': 3}

40.如何把元组("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)

综合
41.Python 常用的数据结构的类型及其特性?
python中常见的数据结构有元组(tuple)、列表(list)、字典(dic)、集合(set)

元组用小括号表示()

列表用中括号表示[]

字典用大括号表示{}

集合用关键字set()来表示

字符串用“  ”或者‘  ’表示

数值型数据直接用本身表示即可,不需要添加任何修饰

相互之间的区别为元组和字典中的键、字符串、整型和浮点型为不可变类型,也就是说不能对其进行修改;而列表和集合为可变类型,可以直接对其进行修改。同时因为列表和集合为可变类型,因此不能作为字典中的键
无法直接对元组进行增加、删除、修改等操作,只能查询,但可以合并两个元组生成新的元组。
42.如何交换字典 {"A":1,"B":2}的键和值?
 

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

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

demo_list = [1,2,3]
# 列表转元祖使用tuple()
result_tup = tuple(demo_list)
print(type(result_tup))
print(result_tup)
# 元祖转列表用list()
result_list = list(result_tup)
print(type(result_list))
print(result_list)

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

import itertools

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(gener)
    # 不能直接对生成器和迭代器进行切片
    # print(fbnq(20)[2])
    # 可以使用itertools.islice()对迭代器进行切片
    # itertools是一个很强大的内置模块,有需要可以了解一下
    gener_clip = itertools.islice(gener,10,20)
    for i in gener_clip:
        print(i)

45.请将[i for i in range(3)]改成生成器
(i for i in range(3)) # 方括号改成尖括号即可

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

a="hello"
b="你好"
a.encode()
b.encode()

47.下面的代码输出结果是什么?
a = (1,2,3,[4,5,6,7],8)
a[2] = 2  # TypeError:'tuple' object does not support item assignment
元祖是不可变类型,因此不能修改元祖内的值
a[2] = 2 使得元祖中对索引值为"2"的元素进行了修改,内存id发生了变化

48.下面的代码输出的结果是什么?
a = (1,2,3,[4,5,6,7],8)
a[3][0] = 2  # (1,2,3,[2,5,6,7],8)
列表是可变数据类型,数据的值可以修改的
这里只修改了元祖子对象的值,而不是修改了元祖的值
修改可变类型的值不会改变内存id,因此元祖的引用还是没有发生变化
可以这么理解,只要不修改元祖中值得内存id,那么就可以进行"修改元祖"操作
扩展,面试官可能会问到:元祖是否可以被修改?
答:元祖是不可变数据类型,因此不能修改元祖中的值,但是如果元祖中有可变数据类型,那么可以修改可变数据类型中的值,
修改可变数据类型的值不会使其内存id法还是能变化,所以元祖中元素的内存id也没有改变,因此就做到了"修改元祖"的操作

 

操作类题目
49.Python 交换两个变量的值
a,b = b,a

50.在读文件操作的时候会使用 read、readline 或者 readlines,简述它们各自的作用
read:读取整个文件.
readline: 读取下一行,使用生成器方法
readlines: 读取整个文件到一个迭代器以供我们遍历51.json 序列化时,可以处理的数据类型有哪些?如何定制支持 datetime 类型?
序列化是指把变量从内存中变成可存储或传输的过程称之为序列化用(使用dump或者dumps),把变量内容从序列化的对象重新读到
内存里称之为反序列化(使用load或者loads)
json序列化时,可以处理列表,字典,字符,数值,布尔和None
对于不支持处理的对象如datetime,就可以通过重写JSONEncoder里的deafult方法来定制
定制datetime类型如下:

from datetime import datetime
import json
from json import JSONEncoder

class DatetimeEncoder(JSONEncoder):
    """扩展JSONEncoder类中的default方法
    
    判断传入的类型是否是datetime类型,如果
    是则转为str字符,否则不是返回父类的值
    """
    def default(self,o): #重写deafult方法
        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))

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

import json
dict_demo = {"name":"旭东"}
# 使用dumps的默认参数ensure_ascii
print(json.dumps(dict_demo,ensure_ascii=False))

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

def read_file(file_name):
    """读文件
    
    读取文件并返回文件数据
    :param file_name:文件名
    :return:文件的所有数据
    """
    with open(file_name,"r") as F:
        return F.read()
        
def write_filw(file_name,file_data):
    """写文件
    
    将数据写入到指定文件中
    :param file_name:文件名
    :param file_data:需要写入的数据
    :return:
    """
    with open(file_name,"w") as F:
        F.write(file_data)
 
def letter_sort(letter_str,reverse_flag =False):
    """字母排序
    
    使用sorted排序算法
    :param letter_str:排序字母字符串
    :param reverse_flag:排序顺序,False为正序,True为反序
    :return:排序后的新字符串
    """
    return "".join(sorted(letter_str,reverse=reverse_flag))
    
if __name__ == '__main__':
    test1_data = read_file("test1.txt")
    test2_data = read_file("test2.txt")
    new_data = letter_sort(test1_data+test2_data)
    write_filw("new.txt",new_data)

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

from datetime import datetime
from datetime import timedelta

def date_calculation(now_date,offset):
    """获取日期函数
    
    获取几天前或者几天后的日期
    :param now_data:当前日期
    :param offset:日期偏移量,负数为前
    :return:结果日期
    """
    # 格式转换
    now_date = datetime.strptime(now_date,"%Y%m%d").date()
    # 计算偏移
    offset_date = timedelta(days = offset)
    return (now_date + offset_date).strftime("%Y%m%d")
    
if __name__ == '__main__':
    result_day = date_calculation("20190918",30)
    print(result_day)

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

def out_func(n):
    """闭包函数
    
    :param n:整数参数n
    :return:内层函数
    """
    def in_func(num):
        return n*num
    return in_func
    
if __name__ == '__main__':
    demo =out_func(3)
    print(demo(4))

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

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
        
if __name__ =='__main__':
    print(str_append(4))

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

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

58.with 语句的作用,写一段代码?
with语句:"上下文管理器",用于资源访问的场合,作用是资源释放和异常处理(详细内容在第3条问题汇总)

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)

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

import json
dict_demo = {"a":1,"b":2}
#序列化:使用json.dumps()将Python类型转换为json字符串
json_demo = json.dumps(dict_demo)
print(type(json_demo))
# 使用json.dump()将python数据序列化到指定文件中
with open("demo.json", "w") as file_obj:
    json.dump(dict_demo,file_obj)
 
# 反序列化:使用json.loads()将json字符类型转换为Python类型
dict_demo = json.loads(json_demo)
print(type(dict_demo))
#使用json.load()将json字符类型从文件中读出来
with open("demo.json","r") as file_obj:
    file_date = json.load(file_obj)
    print(file_date)

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

import re

def capital_count(file_name):
    """计算文件中的大写字母数量
    
    读取文件并计算文件数据的大写字母数量,返回大写字母数量
    :param file_name:文件名
    :return:文件中大写字母数量
    """
    # 定义大写字母数量变量
    upper_count = 0
    # 打开文件对象,读取文件数据
    with open(file_name,"r") as file_obj:
        file_data = file_obj.read()
    # 删除掉除字母之外的所有字符
    file_data = re.sub("[^a-zA-Z]","",file_data)
    print(file_data)
    # 遍历所有字母,使用isupper()判断是否是大写字母,并且计数
    for i in file_data:
        if i.isupper():
            upper_count +=1
    return upper_count
    
# 上文中直接将file_data = re.sub("[^A-Z]","",file_data),这样file_data就只剩大写字母,print(len(file_data))就是大写字母数量

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

import pymongo

# 连接本地数据库
db_client = pymongo.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)

62.说一说 Redis 的基本类型。
string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)

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

import redis
# 创建连接对象
connec_obj = redis.Redis(host = 'localhost',port =  6379,db = 0)
# 设置一个键值
connec_obj.set('test','1')
# 读取一个键值
connec_obj.get('test')

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

import pymysql
# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB",charset='uft-8')
# 使用cursor()方法获取操作游标
cursor = db.cursor()

# 使用execute方法执行SQL语句
cursor.execute("SELECT VERSION")
# 使用fetchone()方法获取一条数据
data = cursor.fetchone()
# 关闭数据库连接
db.close()

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

66.了解数据库的三范式么?
https://www.jianshu.com/p/5a8bb84289a9
通俗解释
属性不可分割:字段不能再分割,如"年级班级"可以分割为"年级"和"班级"两个字段(表中的列只能含有原子性(不可再分)的值。)
唯一主键:一张表中需要有一个唯一主键用来区分每行数据,如"学生学号"(满足第一范式,没有部分依赖)
消除冗余和传递依赖:不同表中不能存在重复的字段数据,如"学生"表中的"院系"字段和"班级"表中的"院系"字段,我们可以关联两张表的字段而无需再"学生"表中再加一个"院系"(满足第二范式,没有传递依赖)

67.了解分布式锁么?
在开发中可能会用到多线程和多进程,如果不同线程或者不同进程抢占同一个资源,对其行读写操作可能会导致数据不一致,导致数据不是在我们预想的情况下改变.这里为了保证线程或者进程安全,python中引入了线程锁和进程锁,保证率数据的一致性和完整性.

而为了保证分布式系统的数据安全,可以使用分布式锁来解决这一问题(秒杀场景).分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性.分布式锁的实现有很多种,常见的有redis,zookeeper和数据库mysql等68.用 Python 实现一个 Reids 的分布式锁的功能。

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()

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

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录.
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是十分致命的.
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构.
 

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)])

高级特性
70.函数装饰器有什么作用?请列举说明?
装饰器: https://www.liujiangblog.com/course/python/39
函数装饰器主要是在不修改代码的前提下进行功能的扩展,满足面向对象的"开闭原则"
应用场景:引入日志,函数执行时间统计,执行函数前预备处理,执行函数后清理功能,权限校验等场景,缓存,事务处理

71.Python 垃圾回收机制?
整数

小整数:python对小整数的定义是[-5,257]这些整数对象是提前建立好的,不会被垃圾回收,在一个python程序中,所有位于这个范围的整数使用的都是同一个对象,单个字母同样也是如此.
大整数:每一个大整数的创建均在内存中会分配一个内存空间,所以大整数的内存空间是需要被回收的.

引用计数为主,标记清除和分代回收为辅:

引用计数:python里面每一个东西都是对象,他们的核心就是一个结构体:PyObject,PyObject是每个对象必须有的内容,其中ob_refcnt就是作为引用计数.
当一个对象有新的引用时,他的ob_refcnt就会增加,当引用他的对象被删除,他的ob_refcnt就会减少,当引用计数为0时,该对象生命就结束了.
引用计数机制的优点:简单;实时性:一旦没有引用,内存直接释放了.不用像其他机制等到特定时机.实时性还带来一个好处:处理回收内存的时间分摊到了平时.
引用计数机制的缺点:维护引用计数消耗资源;循环引用.

分代回收:分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,python 将内存分为了3"代"
,分别为年轻代(第0代),中年代(第1代),老年代(第2代),他们对应的垃圾收集频率随对象的存活时间的增大而减小.
每个分代集合中索引值越大的代表存活时间越长,越不容易被回收.
分代回收是建立在标记清除计数基础之上.分代回收同样作为python的辅助垃圾收集计数处理那些容器对象

72.魔法函数 __call__怎么使用?
__call__允许一个类的实例像函数一样被调用
 

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)

73.如何判断一个对象是函数还是方法?
在类外声明def为函数
类中声明def:使用类调用为函数,使用实例化对象调用为方法
可以使用isinstance()判断传入的类型是否是datetime类型

from types import FunctionType
from types import MethodType

class DemoClass(object):
    def __init__(self):
        pass
    def run_method(self):
        pass

def demo_func():
    pass
    
if __name__ =='__main__':
    demo_obj = DemoClass()
    print(demo_obj.run_method)
    print(demo_func)
    print(DemoClass.run_method)
    print(isinstance(demo_obj.run_method,FunctionType)) #False
    print(isinstance(demo_obj.run_method,MethodType)) #True

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

75.Python 中的接口如何实现?
类定义接口
函数定义接口

76.Python 中的反射了解么?
计算机中的反射,是在运行的时候来自我检查,并对内部成员进行操作.就是说这变量的类型可以动态的改变,在运行的时候确定它的作用.
在Python中,能够通过一个对象,找出其type,class,attribute或method的能力,称为反射或自省,具有反射能力的函数有type(),isinstance(),callable().dir().getattr()等
Python的反射是一个很强大的功能,个人认为每个python程序员都应该掌握这一用法
python反射机制:https://www.cnblogs.com/Guido-admirers/p/6206212.html

77.metaclass 作用?以及应用场景?
https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072

78.hasattr() getattr() setattr()的用法
这三种方法用于为对象属性的存在判断,获取和添加,简而言之就是对象属性的"增,改,查"

hasattr(object,name):判断对象是否存在name属性
class A():
    name = "python"
    def func(self):
        return "A()类的方法func()"
        
if __name__ =='__main__':
    print(hasattr(A,"name"))  # True
    print(hasattr(A,"age"))   # False
    
getattr(object,name[,default]):获取object对象name属性的值,若没有name属性,则返回default值
class A():
    name = "python"
    def func(self):
        return "Hello world"
        
if __name__ =='__main__':
    print(getattr(A,"name"))  # "python"
    print(getattr(A,"age"))   # Error:class A has no attribute 'age'
    print(getattr(A,"age",18)) # 18
    print(getattr(A,"func"))   #
    print(getattr(A(),"func"))() # 'Hello world',获取到的方法需要实例化后才能调用,类方法则不需要

setattr(object,name,value):给object对象name属性赋值value,若没有name属性,会在对象中创建属性并赋值value,如果原本存在给定的属性name,则setattr会更改属性的值为给定的value
class A():
    name = "python"
    def func(self):
        return "Hello world"
        
if __name__ =='__main__':
    setattr(A,"name","java")
    print(getattr(A,"name"))  # "java"
    setattr(A,'age',20)
    print(getattr(A,"age"))   # 20

 

79.请列举你知道的 Python 的魔法方法及用途。
个人认为这个被问到的几率很大,
在Python中,所有以"__"双下划线包起来的方法都称为"魔法方法"
魔法方法python解释器自动给出默认的,因此需要改变内部的功能,其他时刻使用默认魔法方法.
最常用的三个:"__init__","__new__","__del__"
__new__是用来创建类并返回这个类的实例,
__init__将传入的参数来初始化该实例,以及初始化实例属性,与__new__共同构成了"构造函数"
__del__将实例化后的对象销毁,即为析构函数

类调用:__call__
__call__允许一个类像函数一样被调用

属性访问:__getattr__,__setattr__,__delattr__
__getattr__访问对象不存在的属性时,调用该方法,用于定义访问行为
__setattr__设置对象属性时调用
__delattr__删除对象属性时调用

上下文管理器:__enter__,__exit__
__enter__():在执行语句之前,首先执行该方法,通常返回一个实例对象,如果with语句有as目标,则将对象赋值给as目标.
__exit__():执行语句结束后,自动调用__exit__()方法,用户释放资源,若此方法返回布尔值True,程序会忽略异常.

迭代器方法:__iter__和__next__
__iter__:返回一个容器迭代器,很多情况下会返回迭代器,尤其是当内置的iter()方法被调用的时候,以及当使用for x in container:方式循环的时候.迭代器是它们本身的对象,它们必须定义返回self的__iter__方法.
__next__:返回迭代器的下一个元素

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

demo_obj = range(1,11)    # 创建一个未知类型的对象
print(type(demo_obj))     # 使用type()判断对象类型

81.Python 的传参是传值还是传址?
Python对可变对象(字典或列表)传址,对不可变对象(数字,字符或元祖)传值.
 

def demo_func(parm):
    """输出整数或列表改变后的值
    
    param parm:整数或列表
    return:
    """
    if isinstance(parm,int):
        parm+=1
    elif isinstance(parm,list):
        parm.append(1)
    print(parm)
if __name__ == '__main__':
    # 定义整数类型
    int_parm = 1
    # 函数内整数值修改(不可变类型不能修改值,其实这里是其变量另外赋值)
    demo_func(int_parm)   # 输出为2
    # 输出整数值,查看对象的值是否被修改
    print(int_parm)       # 输出为1,值未改变,说明传值
    # 定义列表类型
    list_parm =[1,2,3]
    # 函数修改列表
    demo_func(list_parm) # 输出[1,2,3,1]
    # 查看函数外部列表是否发生改变
    print(list_parm)     # 输出[1,2,3,1],列表发生改变,说明传址

82.Python 中的元类(metaclass)使用举例
与77题重复

83.简述 any()和 all()方法
any()判断一个tuple或者list是否全为空,全空False,不全为空返回True,空列表和空元祖为False;
all()判断一个tuple或者list是否全为非空,有一空则False,全不空True,空列表和空元组为True.

84.filter 方法求出列表所有奇数并构造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
 

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

85.什么是猴子补丁?
猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更
为什么叫猴子补丁?
这个词原来为Guerrilla Patch,杂牌军,游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写成了monkey(猴子)
还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch

使用线程时,通常在模块头部加入:gevent.monkey.patch_all(),用于将标准库中的thread/socket等给替换掉,这样我们在后面使用socket的时候可以跟平常一样使用,无需修改任何代码,但是它变成非阻塞的了.
总结:猴子补丁就是程序功能的追加或者变更

网上还有一个例子:
之前做的一个游戏服务器,很多地方用的import json,后来发现ujson比自带json快了N倍,于是问题来了,难道几十个文件要一个个把import json改成import ujson吗?其实只需要在进程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
print 'mian.py',json.__name__
import  sub

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

87.当退出 Python 时是否释放所有内存分配?
循环引用其他对象或引用自全局命名空间的对象的模块,在python退出时并非完全释放.另外,也不会释放c库保留的内存部分

CPython会通过引用计数立即释放引用数量为0的对象(其它版本解释器并不保证);循环引用的对象会在下一次GC时释放,除非有至少两个对象都带有__del__析构函数,且直接或间接循环引用。这种情况下,所有循环引用的对象都无法被释放,会被gc放入特定的列表中保存。原因在于无法确定__del__的执行顺序。全局引用的对象无法被回收,但也不只是模块中直接或间接保存的对象,还包括未退出的线程使用的对象,解释器缓存的小整数和字符串,还有C模块里间接引用的对象等等。C扩展直接通过malloc分配的内存自然无法通过gc来回收,但一般如果存在没有被回收的内存说明是有内存泄漏的,这属于实现的bug;C扩展也可以保有Python对象,有些可能有全局作用,需要手动回收


正则表达式:https://deerchao.cn/tutorials/regex/regex.htm
88.使用正则表达式匹配出

百度一下,你就知道中的地址    a="张明 98 分",用 re.sub,将 98 替换为 100
 

import re
html_str = '

a="张明 98 分"
' result_str = re.sub(r'\d{1,2}',"100",html_str) print(result_str)

89.正则表达式匹配中(.*)和(.*?)匹配区别?
什么是贪婪匹配:贪婪匹配在匹配字符串时总是尝试匹配尽可能多的字符.
什么是非贪婪匹配:与贪婪匹配相反,非贪婪匹配在匹配字符串时总是尝试匹配尽可能少的字符.
python里数量词默认是贪婪模式的,在"*","?","+","{m,n}"后面加上?,可使贪婪模式变成非贪婪模式.

import re
demo_str= "abcdacsdn"
print("原始字符串 "+demo_str)

# 非贪婪匹配
non_greedy = "a.*?d"
print("非贪婪匹配 = "+ non_greedy)
pattern = re.compile(non_greedy)
result_list = re.findall(pattern,demo_str)
print("非贪婪匹配结果:")
print(result_list)

# 贪婪匹配
greedy = "a.*d"
print("贪婪匹配 = "+ greedy)
pattern = re.compile(greedy)
result_list = re.findall(pattern,demo_str)
print("贪婪匹配结果:")
print(result_list)

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

import re
text = input("Please input your Email address: \n")
# 匹配任意的邮箱,@前是19位的字符数字下划线组合
if re.match(r'^[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}\.[com,cn,net]{1,3}$',text):
    print("Email address is Right")
else:
    print("Please reset your right Email address!")


    
实例1、只允许英文字母、数字、下划线、英文句号、以及中划线组成

举例:[email protected]
分析邮件名称部分:

    26个大小写英文字母表示为a-zA-Z
    数字表示为0-9
    下划线表示为_
    中划线表示为-
    由于名称是由若干个字母、数字、下划线和中划线组成,所以需要用到+表示多次出现

根据以上条件得出邮件名称表达式:[a-zA-Z0-9_-]+


分析域名部分:

一般域名的规律为“[N级域名][三级域名.]二级域名.顶级域名”,比如“qq.com”、“www.qq.com”、“mp.weixin.qq.com”、“12-34.com.cn”,分析可得域名类似“** .** .** .**”组成。

    “**”部分可以表示为[a-zA-Z0-9_-]+
    “.**”部分可以表示为\.[a-zA-Z0-9_-]+
    多个“.**”可以表示为(\.[a-zA-Z0-9_-]+)+

综上所述,域名部分可以表示为[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+

最终表达式:
由于邮箱的基本格式为“名称@域名”,需要使用“^”匹配邮箱的开始部分,用“$”匹配邮箱结束部分以保证邮箱前后不能有其他字符,所以最终邮箱的正则表达式为:
^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

其他内容
91.解释一下 python 中 pass 语句的作用?
Python中的pass是空语句,是为了保持程序结构的完整性;
pass不做任何事情,一般用作占位语句;
一般在搭建程序框架的时候或在判断语句中使用.

92.简述你对 input()函数的理解
python3.x中输入()函数接受一个标准输入数据,返回为字符串类型.
python2.x中输入()想等于eval(raw_input(prompt)),用来获取控制台的输入
raw_input()将所有输入作为字符串看待,返回字符串类型.而input()在对纯数字输入时具有自己的特性,它返回所输入的数字的类型(int,float)

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

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

95.三元运算写法和应用场景?
三元运算符就是在赋值变量的时候,可以直接加判断,然后赋值格式条件为真时的结果if判断的条件else条件为假的结果
先定义变量:
a =1
b =2
第一种写法:
errorStr = "More" if a>b else "Less"
print(errorStr) # 运行结果为:Less
第二种写法:
print({True:"More",False:"Less"}[a > b]) # 运行结果为:Less
第三种写法:
print(("FalseValue","TrueValue")[a > b]) # 运行结果为:FalseValue
其中我们比较常见的是第一种.
第二三种是挺简洁的,但是写在项目里怕是接手的同事要抓狂了

96.了解 enumerate 么?
enumerate()函数用于将一个可遍历的数据对象(如列表,元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般在for循环当中.
以下是enumerate()方法的语法:
enumerate(sequence,[strat = 0])
参数
sequence--一个序列,迭代器或其他支持迭代对象
strat --下标起始位置

seasons = ['Spring','Summer','Fall','Winter']
print(list(enumerate(seasons)))  #[(0,'Spring'),(1,'Summer'),(2,'Fall'),(3,'Winter')]
print(list(enumerate(seasons,start=1)))  #[(1,'Spring'),(2,'Summer'),(3,'Fall'),(4,'Winter')]

97.列举 5 个 Python 中的标准模块
和第一题重复

98.如何在函数中设置一个全局变量
使用global99.pathlib 的用法举例
pathlib模块提供了一组面向对象的类,这些类可代表各种操作系统上的路径,程序可通过这些类操作路径

from pathlib import Path
# 1.查看路径
# 使用cmd()方法输出当前的工作目录
now_path = Path.cwd()
# 使用home()输出用户的主目录
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))
# 使用"/"直接拼接路径
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()方法
demo_file = Path.cwd()/'test.md'
# 这样写的好处就是open里面我们不需要再去传入路径了
# 直接指定文件读写模式即可,实际上这里的open方法底层也是调用了os.open的方法,使用哪种方式看个人喜好
with demo_file.open('r') as fid:
    file_data = fid.read()
print(file_data)
# 也可以不使用with, open的形式即可以进行读写
# .read_text():找到对应的路径然后打开文件,读成str格式.等同open操作文件的"r"格式.
# .read_bytes():读取字节流的方式.等同于open操作文件的"rb"格式
# .write_text():文件写的操作.等同于open操作文件的"w"格式
# .write_bytes():文件写的操作.等同于open操作文件的"wb"格式

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

更多pathlib的使用介绍请看:https://blog.csdn.net/AManFromEarth/article/details/80265843?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

100.Python 中的异常处理,写一个简单的应用场景
重复第九题

101.Python 中递归的最大次数,那如何突破呢?
最大次数位1000次,如何突破看:
https://code.activestate.com/recipes/474088/102.什么是面向对象的 mro
https://www.jianshu.com/p/f22792bcb9a0
MRO:Method Resolution Order(方法解析顺序)
MRO就是类的方法解析顺序表,其实也就是继承父类方法时的顺序表.
MRO示在Python多继承和钻石继承问题上的核心内容,它规定了如何,什么时候,怎么样去调用父类的方法
# 输出类的解析继承关系顺序:类名.__mro__
DemoClass.__mro__
案例:

class A(object):
    def f(self):
        print("A.f")

class B(A):
    def f(self):
        print("B.f")

class C(A):
    def f(self):
        print("C.f")  

class D(B,C):
    def f(self):
        # super().f()  # 此时按照mro调用顺序可知调用的是B类的方法
        # 想要调用C类的方法,查看mro之后使用super调用C的上一类
        super(B,self).f()
        # super(a_type,obj),其中第一个实参a_type是个类对象,第二个实参obj是个实例对象
        # 再次科普:self是实例对象本身,而不是类本身
if __name__ == '__main__':
    print(D.__mro__)
    # (,,,,)
    d= D()
    d.f()

103.isinstance 作用以及应用场景?
isinstance:判断对象是否是一个已知的类型
isinstance(object,classinfo)
object--实例对象
classinfo--可以是直接或间接类名,基本类型或者由它们组成的元组

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

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

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

104.什么是断言?应用场景?
官方解释:
Assert statements are a convenient way to insert debugging assertions into a program.
断言语句是将调试断言插入程序的便捷方式
assert condition:在condition为True时不出发,False触发AssertionError错误
>>> assert 1==1
>>> assert 1==0
Traceback (most recent call last):
  File "", line 1, in
AssertionError

如果没有特别的目的,断言应该用于如下情况:
.防御性的编程
.运行时对程序逻辑的检测
.合约性检查(比如前置条件,后置条件)
.程序中的常量
.检查文档

105.lambda 表达式格式以及应用场景?
lambda表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数.
lambda表达式:lambda 参数1,参数2...:参数表达式

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

举例:输出1到100内的奇数
 

print(list(filter(lambda x: x%2==1,range(1,101))))

列表的排序:按照绝对值大小排序
 

list_demo = [3,5,-4,-1,0,-2,-6]
# sorted和lambda也是很好的组合,这里abs是绝对值函数
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
print(y1)


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

Python2.x中默认都是经典类,只有显式继承了object才是新式类,即:
class Person(object):pass 新式类写法
class Person():pass 经典类写法
class Person:pass 经典类写法

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

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

class A(object):
    def f(self):
        print("A.f")
        
if __name__ == '__main__':
    print(dir())
    # ['A', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
    print(dir(A))
    # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'f']


    
108.一个包里有三个模块,demo1.py, demo2.py, demo3.py,但使用 from tools import *导入模块时,如何保证只有 demo1、demo3 被导入了。
python包中使用__init__.py确认导入的包
https://pic3.zhimg.com/80/v2-f04a5f47a15e0fc489351b50b03da36b_720w.jpg

109.列举 5 个 Python 中的异常类型以及其含义
https://www.csdn.net/gather_2d/MtTaIg3sNjQ3MjAtYmxvZwO0O0OO0O0O.html
 

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已合并到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  # 与Byte和bytearray相关的警告的基类
     +--ResourceWarning  # 与资源使用相关的警告的基类,被默认警告过滤器忽略

110.copy 和 deepcopy 的区别是什么?
copy仅拷贝对象本身,而不拷贝对象中引用的其它对象
deepcopy 除拷贝对象本身,而且拷贝对象中引用的其它对象(子对象).
copy不会为子对象额外创建新的内存空间,当子对象被修改之后,这个子对象的引用都会发生改变;
deepcopy是一个新对象的创建,只是用了和被拷贝对象相同的值,子对象改变不会影响被拷贝对象.

111.代码中经常遇到的*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})

112.Python 中会有函数或成员变量包含单下划线前缀和结尾,和双下划线前缀结尾,区别是什么?
单下划线
单下划线开头的命名方式常被用于模块中,在一个模块中以单下划线开头的变量和方法会被默认划入模块内部范围.当使用from my_module import * 导入时,单下划线开头的变量和方法是不会被导入的,但是使用import my_module导入的话,仍然可以用my_module._var这样的形式访问属性或方法.
单下划线结尾的命名方式也存在,但是不常用,其实也不推荐用.这种命名方式的作用就是为了和python的一些内置关键词区分开来,假设我们想给一个变量命名为class,但是这会跟Python的关键词class冲突,所以我们只好退一步使用单下划线 结尾命名,也就是class_.

双下划线
双下划线开头和结尾的是一些python的"魔术"对象,如类成员的__init__,__del__,__add__,__getitem__,以及全局的__file__,__name__等.Python官方推荐永远不要将这样的命名方式应用于自己的变量或函数,而是按照文档说明来使用.
双下划线开头的命名方式有实际的作用,采用这种命名的变量或方法无法直接通过"对象名.变量名(方法名)"这样的方式访问.

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

114.举例 sort 和 sorted 的区别
 

demo_list = [1,3,4,2,7,5]
# sorted是一个函数,返回一个新的list
result_list = sorted(demo_list)
print(result_list)
# sort是实例方法,直接作用在list本身,没有返回新的list
demo_list.sort()
print(demo_list)

115.什么是负索引?
负索引是指用负数作为索引,-1代表数组的最后一位

116.pprint 模块是干什么的?
 

# pprint用于输出一个整齐美观的Python数据的结构
import pprint
demo_list = [str(i)*20 for i in range(10)]
# indent是指句首缩进
pp_object == pprint.PrettyPrinter(indent = 4)
pp_object.pprint(demo_list) # 整齐输出
print(demo_list)  # 只输出一行

117.解释一下 Python 中的赋值运算符
https://www.runoob.com/python3/python3-basic-operators.html  运算符大全
 

=    简单的赋值运算符    c = a + b 将 a + b 的运算结果赋值为 c
+=    加法赋值运算符    c += a 等效于 c = c + a
-=    减法赋值运算符    c -= a 等效于 c = c - a
*=    乘法赋值运算符    c *= a 等效于 c = c * a
/=    除法赋值运算符    c /= a 等效于 c = c / a
%=    取模赋值运算符    c %= a 等效于 c = c % a
**=    幂赋值运算符    c **= a 等效于 c = c ** a
//=    取整除赋值运算符    c //= a 等效于 c = c // a
:=    海象运算符,可在表达式内部为变量赋值。Python3.8 版本新增运算符。在这个示例中,赋值表达式可以避免调用 len() 两次:
if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

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

and;or;not
and    x and y    布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。     
or    x or y    布尔"或" - 如果 x 是 True,它返回 x 的值,否则它返回 y 的计算值。    
not    not x    布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。

119.讲讲 Python 中的位运算符
按位运算符是把数字看作二进制来进行计算的。Python中的按位运算法则如下:

&   按位与运算符:按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0
|    按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。
^    按位异或运算符:当两对应的二进位相异时,结果为1
~    按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。~x 类似于 -x-1
<<    左移动运算符:运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。
>>    右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数

示例:

#!/usr/bin/python3
 
a = 60            # 60 = 0011 1100
b = 13            # 13 = 0000 1101
c = 0
 
c = a & b;        # 12 = 0000 1100
print ("1 - c 的值为:", c)
 
c = a | b;        # 61 = 0011 1101
print ("2 - c 的值为:", c)
 
c = a ^ b;        # 49 = 0011 0001
print ("3 - c 的值为:", c)
 
c = ~a;           # -61 = 1100 0011
print ("4 - c 的值为:", c)
 
c = a << 2;       # 240 = 1111 0000
print ("5 - c 的值为:", c)
 
c = a >> 2;       # 15 = 0000 1111
print ("6 - c 的值为:", c)

以上实例输出结果:

1 - c 的值为: 12
2 - c 的值为: 61
3 - c 的值为: 49
4 - c 的值为: -61
5 - c 的值为: 240
6 - c 的值为: 15

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

.二进制数字由0和1组成,我们使用0b或0B前缀表示二进制数
print(int(0b1010))  # 10

.使用bin()函数将一个数字转换为它的二进制形式
print(bin(0xf)) # 0b1111

.八进制数由数字0-7组成,用前缀0o或0O表示八进制数
print(oct(8))  # 0o10

.十六进制数由数字0-9和字母a-f组成,用前缀0x或0X表示16进制数
print(hex(16))  # 0x10
print(hex(15))  # 0xf

121.怎样声明多个变量并赋值?
a,b=1,2

 

算法和数据结构

122.已知:Alist = [1,2,3] Bset = {1,2,3}
(1) 从 AList 和 BSet 中 查找 4,最坏时间复杂度那个大?
(2) 从 AList 和 BSet 中 插入 4,最坏时间复杂度那个大?

在查找成功的情况下,若待查找的数据元素恰好是数组的第一个元素,则只需比较一次即可找到,这就是最好情况,T(n)=O(1),称最好时间复杂度。
若是最后一个元素,则要比较n次才能找到。T(n)=O(n),称最坏时间复杂度

在查找不成功的情况下,无论何时进行不成功的查找都需要进行n次比较,T(n)=O(n)。
成功查找是的平均次数:(n+1)/2,T(n)=O(n)。 称平均时间复杂度。一般取最坏或平均时间复杂度。

算法完成工作最少需要多少基本操作,最优时间复杂度
算法完成工作最多需要多少基本操作,最坏时间复杂度
算法完成工作平均需要多少基本操作,平均时间复杂度

最优时间复杂度,意义与价值不大,只能反映最乐观的情况,没有参考价值。
最坏时间复杂度,提供了一种保障,表明算法在此程度的基本操作中一定可以完成任务。
平均时间复杂度,时对算法的一个全面评价,它完整全面的反映了这个算法的性质,但又不可以保证每个计算都可以在这个基本操作内完成。
所以,我们主要关心,最坏时间复杂度。

时间复杂度的基本规则
1.基本操作,即只有常数项,认为其时间复杂度为O(1)
2.顺序结构,时间复杂度按加法进行计算
3.循环结构,时间复杂度按乘法进行操作
4.条件结构,时间复杂度取最大值(最复杂的分支)
5.判断一个算法的效率时,往往只需要关注操作数量的最高次项,其他的次要项和常数项可以忽略(大O记法)
6.在没有特殊说明的时候,我们分析的算法的时间复杂度都是指最坏时间复杂度


.Python的列表内部实现是数组(具体实现要看解析器,CPython的实现),因此就有数组的特点.超过容量会增加更多的容量,set,get是O(1),但del,insert,ub的性能是O(n)
.关于字典需要了解的是hash函数和哈希桶.一个好的hash函数使到哈希桶中的值只有一个,若多个key hash到了同一个哈希桶中,称之为哈希冲突.查找值时,会先定位到哈希桶中,再遍历hash桶.在hash基本没有冲突的情况下get,set,delete,in方面都是O(1)
.集合内部实现是dict的.在in操作上是O(1),这一点比list要强

哈希桶相关知识:https://blog.csdn.net/wei_cheng18/article/details/79821965?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

由此可知:
(1)查找操作set优于list
(2)插入操作两个相同

123.用 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
    # 当前索引下标
    now_index = 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]:
            print("找到数据","索引是{}".format(mid_index))
            print("一共查找{}次".format(now_index))
            break
        else:
            # 大于需要的猜的数,则将最大下标变为中间的,因为中间的已经猜过了,所以减一
            max_index = mid_index - 1
        # 索引值加一
        now_index += 1
        
if __name__ == '__main__':
    list1 = [i for i in range(0,1000)]
    num = 0
    binary_search(list1,num)

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

#单例模式就是确保一个类只有一个实例.当你希望整个系统中,某个类只有一个实例时,单例模式就派上了用场
class SingleCase(object):
    _instance = None    #定义一个类属性做判断

    def __new__(cls,*args,**kwargs):
        if cls._instance is None:
            #如果__instance为空证明是第一次创建实例
            #通过父类的__new__(cls)创建实例
            cls._instance = super().__new__(cls,*args,**kwargs)
        return cls._instance

if __name__ == '__main__':
    a = SingleCase()
    b = SingleCase()
    print(type(a))
    print(type(b))


    
125.使用 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(gener)
    for i in gener:
        print(i)

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

from collections import Counter
result = Counter([1,2,2,2,2,3,3,3,4,4,4,4])
print(result)
#Counter({2:4,4:4,3:3,1:1})

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

from collections import Counter
result = Counter([1,2,2,2,2,3,3,3,4,4,4,4])
print(result)
#Counter({2:4,4:4,3:3,1:1})

128.写一个冒泡排序

def bubble_sort(parm_list):
    """冒泡排序
    
    冒泡排序思路:判断前后两个值得大小,若前大于后则调换两个值得位置
    每一轮循环都可以将最大值放到末尾,所以需要迭代次数为数组的大小,
    因为每次都将最大值放到最后,所以内层迭代就不需要全部检测一遍
    :param parm_list:
    :return:
    """
    for n in range(len(parm_list)):
        for now_index in range(len(parm_list)-n-1):
            if parm_list[now_index] > parm_list[now_index + 1]:
                parm_list[now_index],parm_list[now_index + 1] = parm_list[now_index + 1],parm_list[now_index]
    print(parm_list)

if __name__ == '__main__':
        bubble_sort([1,2,3,7,5,4,6])

129.写一个快速排序

def quick_sort(parm_list):
    """快速排序
    
    每次选取第一个值为基准值,再把列表中比基准值大的组成新列表,小的组成另一个新列表
    再次对两个新列表进行操作,直到新列表为空
    :param: parm_list:参数列表
    :return:
    """
    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

if __name__ == '__main__':
    demo_list = [4,23,5,6,43,14,9,-23,2,6,123,12,3,3,3,3,1]
    print(quick_sort(demo_list))


130.写一个拓扑排序
 

def topology_sort(relation_parm):
    """简单拓扑排序
    
    简单实现拓扑排序算法,不考虑时间复杂度
    :param relation_parm:拓扑关系字典参数
    :return:排序后结果
    """
    in_degrees = dict((u,0) for u in relation_parm)
    vertex_num = len(in_degrees)
    for key in relation_parm:
        for value in relation_parm[key]:
            in_degrees[value]+= 1
    zero_list = [u for u in in_degrees if in_degrees[u]== 0]
    result_list = []
    while zero_list:
        last_zero = zero_list.pop()
        result_list.append(last_zero)
        for zero_value in relation_parm[last_zero]:
            in_degrees[zero_value] -= 1
            if in_degrees[zero_value] ==0:
                zero_list.append(zero_value)
    if len(result_list)!=vertex_num:
        print("there's a circle")
    else:
        return result_list
if __name__ =='__main__':
    relation_dict = {'a':'bcd','b':'','c':'be','d':'e','e':'','f':'de'}
    print(topology_sort(relation_dict))

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

def addBinary( a, b):
        """
        :type a: str
        :type b: str
        :rtype: str
        """
        a = int(a,2)
        b = int(b,2)
        return bin(a+b)[2:]
if __name__ =='__main__':
    a = "1010", b = "1011"
    print(addBinary(a,b))


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

def s_sort(arr):
    list_a = []
    list_b = []
    for item in arr:
        if item == '+':
            list_a.append(item)
        elif item == '-':
            list_b.append(item)
    list_a.extend(list_b)
    return  list_a



if __name__ == '__main__':
    array1 = ["-","-","+","-","+","-","+","+"]
    print(s_sort(array1))

133.单链表反转

class Node(object):
    def __init__(self, elem, next_=None):
        self.elem = elem
        self.next = next_
 
def reverseList(head):
    if head == None or head.next==None:  # 若链表为空或者仅一个数就直接返回
        return head
    pre = None
    next = None
    while(head != None):
        next = head.next     # 1
        head.next = pre     # 2
        pre = head      # 3
        head = next      # 4
    return pre

if __name__ == '__main__':
    l1 = Node(3)    # 建立链表3->2->1->9->None
    l1.next = Node(2)
    l1.next.next = Node(1)
    l1.next.next.next = Node(9)
    l = reverseList(l1)
    print (l.elem, l.next.elem, l.next.next.elem, l.next.next.next.elem)


134.交叉链表求交点
https://blog.csdn.net/fengxinlinux/article/details/78885764?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

我们可以从头遍历两个链表。创建两个栈,第一个栈存储第一个链表的节点,第二个栈存储第二个链表的节点。每遍历到一个节点时,就将该节点入栈。两个链表都入栈结束后。则通过top判断栈顶的节点是否相等即可判断两个单链表是否相交。因为我们知道,若两个链表相交,则从第一个相交节点开始,后面的节点都相交。
若两链表相交,则循环出栈,直到遇到两个出栈的节点不相同,则这个节点的后一个节点就是第一个相交的节点。
node temp=NULL;  //存第一个相交节点

while(!stack1.empty()&&!stack1.empty())  //两栈不为空
{
    temp=stack1.top();  
    stack1.pop();
    stack2.pop();
    if(stack1.top()!=stack2.top())
    {
        break;
    }
}

 

135.用队列实现栈
用队列实现栈的下列操作:

    push(x) -- 元素 x 入栈
    pop() -- 移除栈顶元素
    top() -- 获取栈顶元素
    empty() -- 返回栈是否为空

这里我们使用两个队列来实现栈。我们首先定义了一个队列类(Queue),具有入队(push)和出队(pop)两个方法,以及队列长度(length)和队列是否为空(is_empty)两个属性:

    push():元素入队成为队尾,没有返回值;
    pop():队头元素出队,返回值为出队元素;
    length:获得当前队列长度;
    is_empty:获得当前队列是否为空,如果为空返回True。

首先,我们实例化一个基本队列queue1和一个辅助队列queue2,每次操作后,基本队列中的元素就是栈中的元素,辅助队列用于每一步操作中帮助实现元素暂存的功能。栈的每一个方法这样实现:

    push():元素入栈,直接在基本队列queue1中加入元素即可;
    pop():弹出栈顶元素,将基本队列queue1中的每次元素出队并依次加入辅助队列queue2中,当只剩下一个元素res时,把这个元素拿出来,不加入queue2,然后把辅助队列queue2中的所有元素依次返还基本队列queue1,并返回之前被拿出来的元素res即可。
    top():获取栈顶元素,实现方法与pop十分相似,唯一只是res会被再加入到queue2中;
    empty():判断栈是否为空,栈为空与基本队列queue1为空的判断结果完全一致,因此直接返回queue1的is_empty方法即可。

class Queue(object):
    def __init__(self):
        self.queue = []

    def push(self, x):
        self.queue.append(x)

    def pop(self):
        if self.is_empty:
            return None
        return self.queue.pop(0)

    @property
    def length(self):                   # 获取队列的当前长度
        return len(self.queue)

    @property                   
    def is_empty(self):                 # 获取队列的当前状态:是否为空
        return self.length == 0


class MyStack(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """

        self.queue1 = Queue()                       # 一个基本队列
        self.queue2 = Queue()                       # 一个辅助队列

    def push(self, x):
        """
        Push element x onto stack.
        :type x: int
        :rtype: None
        """
        self.queue1.push(x)                         # 加入元素

    def pop(self):
        """
        Removes the element on top of the stack and returns that element.
        :rtype: int
        """

        while self.queue1.length > 1:               # 卡住最后一个要出队的数
            self.queue2.push(self.queue1.pop())     # 队头元素出队出队后马上进入辅助队列

        res = self.queue1.pop()                     # 最后一个准备出队的元素就是要弹出的栈顶元素

        while not self.queue2.is_empty:             # 只要辅助队列还有元素
            self.queue1.push(self.queue2.pop())     # 将辅助队列中的所有元素依次归还基本队列
        return res                                  # 返回结果

    def top(self):
        """
        Get the top element.
        :rtype: int
        """
        while self.queue1.length > 1:               # 卡住最后一个要出队的数
            self.queue2.push(self.queue1.pop())     # 队头元素出队出队后马上进入辅助队列

        res = self.queue1.pop()                     # 最后一个准备出队的元素就是要返回的栈顶元素
        self.queue2.push(res)

        while not self.queue2.is_empty:             # 只要辅助队列还有元素
            self.queue1.push(self.queue2.pop())     # 将辅助队列中的所有元素依次归还基本队列
        return res                                  # 返回结果

    def empty(self):
        """
        Returns whether the stack is empty.
        :rtype: bool
        """
        return self.queue1.is_empty

136.找出数据流的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,
那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

题目:有一个源源不断地吐出整数的数据流,假设你有足够的空间来保存吐出的数。请设计一个名叫MedianHolder的结构,MedianHolder可以随时取得之前吐出所有数的中位数。

解析:关于此问题的主要解题思路为建立大根堆和小根堆,大根堆用来存储较小的数,小根堆用来存储较大的数,在读入数据的过程中要进行大根堆和小根堆的调整,使两者所保存的数据量的差值不大于2,主要的步骤如下:

    建立大根堆和小根堆;
    读入第一个数据,将其放入大根堆中并建堆;
    读入数据,比较该数据与大根堆堆顶元素的大小,如果该值小于大根堆的堆顶,将该值加入大根堆中并进行hepify操作,否则进行下一步操作;
    如果小根堆为空,将该值放入小根堆中并建堆,并返回,否则比较该数据与小根堆堆顶元素的大小,如果该值小于小根堆的堆顶元素,则将该值加入到大根堆中,否则将其加入小根堆中,随后对该数据加入的堆进行heapify操作;
    加入数据后对,大根堆和小根堆进行相应的调整: 如果大根堆的size比小根堆的size大2,那么从大根堆里将堆顶弹出,并放入小根堆中;如果小根堆的size比大根堆的size大2,那么从小根堆里将堆顶弹出,并放入大根堆中。

建堆完成后,如果大根堆和小根堆的size相等,那么取两个堆得堆顶元素的平均数即可,如果不相等,那么取size较大的堆的堆顶元素即可。

首先,我们利用Python自带的heapq模块分别建立大根堆和小根堆。

import heapq
     
class BigHeap():
    def __init__(self):
        self.arr = list()
    def heap_insert(self, val):
        heapq.heappush(self.arr, -val)
    def heapify(self):
        heapq.heapify(self.arr)
    def heap_pop(self):
        return -heapq.heappop(self.arr)
    def get_top(self):
        if not self.arr:
            return
        return -self.arr[0]

import heapq
     
class SmallHeap():
    def __init__(self):
        self.arr = list()
    def heap_insert(self, val):
        heapq.heappush(self.arr, val)
    def heapify(self):
        heapq.heapify(self.arr)
    def heap_pop(self):
        return heapq.heappop(self.arr)
    def get_top(self):
        if not self.arr:
            return
        return self.arr[0]

接下来我们设计MedianHolder结构来获取数据流中的中位数。

class MedianHolder():
     
    def __init__(self):
        self.bigHeap = BigHeap()
        self.smallHeap = SmallHeap()
     
    def addNum(self, num):
        if len(self.bigHeap.arr) == 0:
            self.bigHeap.heap_insert(num)
            return
        if self.bigHeap.get_top() >= num:
            self.bigHeap.heap_insert(num)
        else:
            if len(self.smallHeap.arr) == 0:
                self.smallHeap.heap_insert(num)
                return
            if self.smallHeap.get_top() > num:
                self.bigHeap.heap_insert(num)
            else:
                self.smallHeap.heap_insert(num)
        self.modifyTwoHeapSize()
     
    def getMedian(self):
        smallHeapSize = len(self.smallHeap.arr)
        bigHeapSize = len(self.bigHeap.arr)
        if smallHeapSize + bigHeapSize == 0:
            return None
        smallHeapHead = self.smallHeap.get_top()
        bigHeapHead = self.bigHeap.get_top()
        if (smallHeapSize + bigHeapSize) %2 == 0:
            return (smallHeapHead+bigHeapHead)/2
        else:
            return smallHeapHead if smallHeapSize > bigHeapSize else bigHeapHead
     
    def modifyTwoHeapSize(self):
        smallHeapSize = len(self.smallHeap.arr)
        bigHeapSize = len(self.bigHeap.arr)
        if smallHeapSize == bigHeapSize + 2:
            self.bigHeap.heap_insert(self.smallHeap.heap_pop())
        if bigHeapSize == smallHeapSize + 2:
            self.smallHeap.heap_insert(self.bigHeap.heap_pop())

测试用例如下:

if __name__ == '__main__':
    arr = [68,51,42,92,13,46,24,58,62,72,32]
    medianHold = MedianHolder()
    for i in range(len(arr)):
        medianHold.addNum(arr[i])
        print(medianHold.getMedian())

137.二叉搜索树中第 K 小的元素
递归:
 

class Solution:
    def kthSmallest(self, root, k):
        """
        :type root: TreeNode
        :type k: int
        :rtype: int
        """
        def inorder(r):
            return inorder(r.left) + [r.val] + inorder(r.right) if r else []
    
        return inorder(root)[k - 1]

迭代:
 

class Solution:
    def kthSmallest(self, root, k):
        """
        :type root: TreeNode
        :type k: int
        :rtype: int
        """
        stack = []
        
        while True:
            while root:
                stack.append(root)
                root = root.left
            root = stack.pop()
            k -= 1
            if not k:
                return root.val
            root = root.right

爬虫相关
138.在 requests 模块中,requests.content 和 requests.text 什么区别
.content中间存的是字节码
.text存的是.content编码后的字符串
139.简要写一下 lxml 模块的使用方法框架

from lxml import etree

text = '''

'''
# 获取html内容元素,转为etree类型
htmlEmt = etree.HTML(text)
print(type(htmlEmt))
# 将内容元素转换为字符串
result = etree.tostring(htmlEmt)
# utf-8格式输出,lxml有自动修正html的功能
print(result.decode("utf-8"))
# 获取所有的
  • 标签 result = htmlEmt.xpath('//li') print(result) # 获取标签数量 print(len(result)) result = htmlEmt.xpath('//li/@class') print(result) # 获取
  • 标签下href为link1.html的标签 result = htmlEmt.xpath('//li/a[@href="link1.html"]') print(result) print(result[0].text) # 获取最后一个
  • 的href result = htmlEmt.xpath('//li[last()]/a/@href') print(result) # 获取倒数第二个元素的内容 result = htmlEmt.xpath('//li[last()-1]/a') print(result[0].text) # 获取class为ltem-0的标签名 result = htmlEmt.xpath('//*[@class="item-0"]') print(result[0].tag)
  • 140.说一说 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请求程序,停止结束.

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

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

    用过user-agend中间件,代理ip中间件,selenium中间件,cookie中间件

    143.你写爬虫的时候都遇到过什么?反爬虫措施,你是怎么解决的?
    反扒策略1:通过UA限制或者其他头信息限制
    解决方案:构建用户代理池或其他头信息

    反扒策略2:通过访问者IP限制
    解决方案:构建IP代理池

    反扒策略3:通过验证码限制
    解决方案:手工打码,验证码接口自动识别或者通过机器学习自动识别

    反扒策略4:通过数据的异步加载限制
    解决方案:抓包分析或者使用PhantomJS

    反扒策略5:通过Cookie限制
    解决方案:进行Cookie处理

    反扒策略6:通过JS限制(如请求的数据通过JS随机生成等)
    解决方案:分析JS解密或者使用PhantomJS

    144.为什么会用到代理?
    安全避免同一个代理IP访问同一个网页,对于长时间访问同一个网页的IP,极大可能性IP会被封掉。
    方便解决IP代理问题技术含量高,找代理处理方便省事。
    成本低自己去维护服务器成本过高,不利于长久持续发展。

    当然,并不是所有的代理IP都能起到这个作用。代理IP分为高匿名、透明(普通匿名也认为是透明)两种;透明代理IP服务器端看到的是你的真实IP和代理IP,高匿名代理IP服务器端只能看到代理IP。所以说,还必须使用高匿名代理IP。

    145.代理失效了怎么处理?
    1、将代理IP及其协议载入ProxyHandler赋给一个opener_support变量;
    2、将opener_support载入build_opener方法,创建opener;
    3、安装opener。具体代码如下:
     

    from urllib import requestdef
    def ProxySpider(url, proxy_ip, header):
        opener_support = request.ProxyHandler({'http': proxy_ip})
        opener = request.build_opener(opener_support)
        request.install_opener(opener)
        req = request.Request(url, headers=header)
        rsp = request.urlopen(req).read()
        return rsp

    146.列出你知道 header 的内容以及信息
    http报文中包含了request line、header、空白行、请求正文;而header中包含

        User-Agent:产生请求的浏览器类型

        Accept:client端可识别的内容类型列表

        Host:请求的主机名,允许多个域名同处一个ip地址,即虚拟主机

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

    <1>.先要解析出 baidu.com 对应的 ip 地址
    <2>. 要先使用 arp 获取默认网关的 mac 地址
    <3>. 组织数据发送给默认网关(ip 还是 dns 服务器的 ip,但是 mac 地址是默认网关的 mac 地址)
    <4>. 默认网关拥有转发数据的能力,把数据转发给路由器
    <5>. 路由器根据自己的路由协议,来选择一个合适的较快的路径转发数据给目的网关
    <6>. 目的网关(dns 服务器所在的网关),把数据转发给 dns 服务器
    <7>. dns 服务器查询解析出 baidu.com 对应的 ip 地址,并原路返回请求这个域名的 client
    <8>. 得到了 baidu.com 对应的 ip 地址之后,会发送 tcp 的 3 次握手,进行连接
    <9>. 使用 http 协议发送请求数据给 web 服务器
    <10>. web 服务器收到数据请求之后,通过查询自己的服务器得到相应的结果,原路返回给浏览器。
    <11>. 浏览器接收到数据之后通过浏览器自己的渲染功能来显示这个网页。
    <12>. 浏览器关闭 tcp 连接,即 4 次挥手结束,完成整个访问过程

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

    <1>.高匿ip代理池

    <2>.切换ua

    <3>.爬慢点

    <4>.如果是简单的数字或者英文呢的验证码 识别他  

    149.scrapy 和 scrapy-redis 有什么区别?为什么选择 redis 数据库?
    Scrapy是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,
    而提供了一些以redis为基础的组件(仅有组件)

    150.分布式爬虫主要解决什么问题
    ip;
    带宽;
    cpu;
    io.

    151.写爬虫是用多进程好?还是多线程好? 为什么?
    IO密集型代码(文件处理,网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率).在实际的数据采集过程中,既考虑网速和响应的问题,也需要考虑自身机器的硬件情况,来设置多进程或多线程.

    phantomjs或者chrome-headless来抓取的爬虫,应当是多进程的,因为每一个phan/chro实例就是一个进程了,并发只能是多进程.此外爬虫中还是数据处理业务,如果数据处理业务是一个比较耗时的计算型操作,那么对数据处理部分应当设为多进程,但更多可能会考虑该部分数据处理操作和爬虫程序解耦,也就是先把数据抓取下来,时候单独运行另外的程序解析数据.

    152.解析网页的解析器使用最多的是哪几个
    lxml,re,beautifulsope

    153.需要登录的网页,如何解决同时限制 ip,cookie,session(其中有一些是动态生成的)在不使用动态爬取的情况下?
    解决限制IP可以使用代理IP地址池,服务器;
    不适用动态爬取的情况下可以使用反编译JS文件获取相应的文件,或者换用其他平台(比如手机端)看看是否可以获取相应的json

    154.验证码的解决(简单的:对图像做处理后可以得到的,困难的:验证码是点击,拖动等动态进行的?)
    图形验证码:干扰,杂色不是特别多的图片可以使用开源库Tesseract进行识别,太过复杂的需要借助第三方打码平台.

    点击和拖动滑块验证码可以借助selenium,无图形界面浏览器(chromdriver或者phantomjs)和pilow包来模拟人的点击和滑动操作,pillow可以根据色差识别需要滑动的位置.

    手动打码(有的验证码确实无解)

    155.使用最多的数据库(mysql,mongodb,redis 等),对他的理解?
    MySQL数据库:开源免费的关系型数据库,需要事先创建数据库,数据表和表的字段,表与表之间可以进行关联(一对多,多对多),是持久化存储.

    Mongodb数据库:是非关系型数据库,数据库的三元素是,数据库 集合 文档,可以进行持久化存储,也可作为内存数据库,存储数据不需要事先设定格式,数据以键值对的形式存储.

    redis数据库:非关系型数据库,使用前可以不用设置格式,以键值对的方式保存,文件格式相对自由,主要用于缓存数据库,也可以进行持久化存储.

    网络编程
    156.TCP 和 UDP 的区别?
    <1>面向连接(TCP)与面向非连接(UDP)
    <2>对系统资源的要求(TCP较多,UDP较少)
    <3>TCP可靠传输,UDP不可靠传输
    <4>TCP应用少量数据(信令),UDP传输大量数据(数据)
    <5>TCP速度慢,UDP速度快

    157.简要介绍三次握手和四次挥手
    (1)第一次握手:客户端发送SYN包(SYN=j)到服务器,并进入SYS_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

    158.什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?
    <1>粘包的概念
    粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小进行数据读出,
    若双方的size不一致时就会使指发送方发送的若干数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.

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

    <3>粘包的处理方式
    (1)当时短连接的情况下,不用考虑粘包的情况
    (2)如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
    (3)如果双方建立长连接,需要在连接后一段时间内发送不同结构数据

    接收方创建预处理线程,对接收到的数据包进行预处理,将黏连的包分开;
    分包是指在出现粘包的时候我们的接收方要进行分包处理.(在场链接中都会出现)数据包的边界发生错位,导致读出的错误的数据分包,进而曲解原始数据含义
    粘包情况有两种,一种是粘在一起的包都是完整的数据包,另一种情况是黏在一起的包有不完整的包

    并发
    159.举例说明 conccurent.future 的中线程池的用法

    concurent是比起threading更加底层的库,比起Python提供的 threading有几个好处

    <1>可以通过主线程调度子线程,获得子线程状态以及结果
    <2>concurent中多线程和多进程接口一致,切换起来非常平滑

    如何调用:
    注意,executor.submit()是非阻塞的,调用后主线程会继续运行,所以下面的代码会先打印False,随后两个线程运行完毕了再打印结果task1.done() 判断是否结束,非阻塞
    task1.result() 获取返回结果,阻塞式
    task2.cancel() 取消任务,如果取消成功会返回True,否则返回False,线程只能在运行之前取消,运行开始或结束后取消都会失败

    from concurrent import futures
    
    from concurrent.futures import ThreadPoolExecutor
    import time
    
    #线程池
    
    def doThings(takeTime):
        time.sleep(takeTime)
        print('finished!')
        return takeTime
    
    executor = ThreadPoolExecutor(max_workers=2)
    
    #通过submit函数提交函数到线程池中
    
    task1 = executor.submit(doThings,(3))
    task2 = executor.submit(doThings,(2))
    
    print(task1.done()) # False finished!
    time.sleep(3)
    print(task1.done()) # finished! True
    
    print(task1.result()) # 3
    
    print(task2.cancel()) # False
    
    #已经运行完了,取消失败

    获取已经成功的task的返回
    使用list批量化提交数据
    as_completed:本身是个生成器,会获取已经完成的task

    from concurrent.futures import ThreadPoolExecutor,as_completed
    import time
    
    #线程池
    
    def doThings(takeTime):
        time.sleep(takeTime)
        print('finished!')
        return takeTime
    
    executor = ThreadPoolExecutor(max_workers=2)
    
    tasks = [1,2,3,1,1,5,2,3,1,2,3,4]
    all_task = [executor.submit(doThings,(t)) for t in tasks]
    for future in as_completed(all_task):
        data = future.result()
        print(data)

    运行时会不停的返回结果。
    通过executor获得已经完成的task

    差异:
        executor.map实际上和Python的map类似,会依此对可迭代对象进行调用,返回对象直接是data,而不是future对象
        返回顺序是tasks的顺序,而不是谁先完成就返回谁。
        
     

    tasks = [1,2,3,1,1,5,2,3,1,2,3,4]
    for data in executor.map(doThings,tasks):
        print(data)

    多进程编程
    多进程使用
    对比一下多线程和多进程的调用,发现修改非常煎蛋,只需要把ThreadPoolExecutor 替换成ProcessPoolExecutor就能从多线程切换成多进程


    多线程斐波那契:

    def fib(n):
        if n<=2:
            return 1
        return fib(n-1)+fib(n-2)
    
    if __name__ == "__main__":
        with ThreadPoolExecutor(3) as executor:
            all_task = [executor.submit(fib, (num)) for num in range(30,37)]
            start_time = time.time()
            for future in as_completed(all_task):
                data = future.result()
                print("exe result: {}".format(data))
    
            print("last time is: {}".format(time.time()-start_time))
            
    #多线程耗时6.77秒
    #last time is: 6.776002407073975

    多进程斐波那契:

    def fib(n):
        if n<=2:
            return 1
        return fib(n-1)+fib(n-2)
    
    if __name__ == "__main__":
        with ProcessPoolExecutor(3) as executor:
            all_task = [executor.submit(fib, (num)) for num in range(30,37)]
            start_time = time.time()
            for future in as_completed(all_task):
                data = future.result()
                print("exe result: {}".format(data))
    
            print("last time is: {}".format(time.time()-start_time))
    #多进程耗时3.9秒       
    # last time is: 3.917999029159546


    PS:举例:

    from concurrent.futures import ThreadPoolExecutor, as_completed, wait
    import time
    
    # 线程池  为什么要线程池?
    # 主线程中可以获取某一个线程的状态或者某一个任务的状态,以及返回值
    # 当一个线程结束后主线程能立刻知道线程结束了
    # futures 可以让多线程和多进程接口一致
    
    
    def get_html(times):
        time.sleep(3)
        print('thread is successed', times)
        return '完成了'
    
    
    if __name__ == '__main__':
        executor = ThreadPoolExecutor(max_workers=3)
    
        # result = task1.done()
        # is_cancel = task2.cancel()  # 取消线程运行  返回结果是 是否被取消  如果线程已经启动或者结束是不能被取消的
        # print(task1)
        # print(result)
        # time.sleep(3)
        # result1 = task1.done()  # done方法判断是否执行完成
        # print(task1.result())  # result方法获取函数返回值
        # print(result1)
    
        # 一次提交多个线程的做法
        urls = ['https://www.zhijinyu.com/{}'.format(x) for x in range(10)]
        all_tasks = [executor.submit(get_html, url) for url in urls]  # 提交多个线程
        print(all_tasks)
        wait(all_tasks, return_when='FIRST_COMPLETED')  # 阻塞所有线程完成才执行后面的问题   有一个参数叫return_when  什么时候继续执行
        """
         return_when: Indicates when this function should return. The options
                are:
    
                FIRST_COMPLETED - Return when any future finishes or is  # 第一个执行完毕
                                  cancelled.  #
                FIRST_EXCEPTION - Return when any future finishes by raising an    # 通过提出future,在任何future结束时返回。 如果没有将来会引发异常,那么它等同于ALL_COMPLETED。
                                  exception. If no future raises an exception
                                  then it is equivalent to ALL_COMPLETED.
                ALL_COMPLETED -   Return when all futures finish or are cancelled.
            """
        print('main')
        # for future in as_completed(all_tasks):   # 获取所有线程的执行结果
        #     data = future.result()
        #     print(data)
    
    
        # # 通过executor获取已经完成的task
        # for future in executor.map(get_html, urls):   # map返回的顺序和url的顺序是一致的
        #
        #     print(future)
    
    


    160.说一说多线程,多进程和协程的区别。
    一、概念

    1、进程

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

    2、线程

    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

    3、协程

    协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

     

    二、区别:

    1、进程多与线程比较

    线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:
    1) 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
    2) 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
    3) 线程是处理器调度的基本单位,但进程不是
    4) 二者均可并发执行
    5) 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

    2、协程多与线程进行比较

    1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。

    2) 线程进程都是同步机制,而协程则是异步

    3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

    三、进程和线程、协程在python中的使用

    1、多进程一般使用multiprocessing库,来利用多核CPU,主要是用在CPU密集型的程序上,当然生产者消费者这种也可以使用。多进程的优势就是一个子进程崩溃并不会影响其他子进程和主进程的运行,但缺点就是不能一次性启动太多进程,会严重影响系统的资源调度,特别是CPU使用率和负载。使用多进程可以查看文章《python 多进程使用总结》。注:python2的进程池在类中的使用会有问题,需要把类函数定义成全局函数。具体可参考 http://bbs.chinaunix.net/thread-4111379-1-1.html

    2、多线程一般是使用threading库,完成一些IO密集型并发操作。多线程的优势是切换快,资源消耗低,但一个线程挂掉则会影响到所有线程,所以不够稳定。现实中使用线程池的场景会比较多,具体可参考《python线程池实现》。

    3、协程一般是使用gevent库,当然这个库用起来比较麻烦,所以使用的并不是很多。相反,协程在tornado的运用就多得多了,使用协程让tornado做到单线程异步,据说还能解决C10K的问题。所以协程使用的地方最多的是在web应用上。

    总结一下就是IO密集型一般使用多线程或者多进程,CPU密集型一般使用多进程,强调非阻塞异步并发的一般都是使用协程,当然有时候也是需要多进程线程池结合的,或者是其他组合方式。

    161.简述 GIL
    Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。
    GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
    线程释放GIL锁的情况:在IO操作等可能会引起阻塞的systemcall之前,可以暂时释放GIL,但在执行完毕后,
    必须重新获取GILPython3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python2.x,tickets计数
    达到100
    Python使用多进程是可以利用多核的CPU资源的。
    多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
    结论:
    <1>.在处理像科学计算这类需要持续使用cpu的任务的时候单线程会比多线程快
    <2>.在处理像IO操作等可能引起阻塞的这类任务的时候多线程会比单线程

    162.进程之间如何通信
    主要Queue和Pipe这两种方式,Queue用于多个进程间实现通信,Pipe是两个进程的通信。

    <1>.管道:分为匿名管道和命名管道

    匿名管道:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,一般使用fock函数实现父子进程的通信

    命名函数:在内存中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,没有血缘关系的进程也可以进程间通信

    特点:面向字节流;生命周期随内核;自带同步互斥机制;半双工,单向通信,两个管道实现双向通信

    <2>.消息队列:在内核中创建一个队列,队列中每个元素是一个数据报,不同的进程可以通过句柄去访问这个队列。消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型。消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的,每个消息队列的总的字节数是有上限的,系统上消息队列的总数也有一个上限

    特点:消息队列可以被认为是一个全局的一个链表,链表节点中存放着数据报的类型和内容,有消息队列的标识符进行标记;消息队列允许一个或多个进程写入或读取消息;消息队列的生命周期随内核;消息队列可实现双向通信

    <3>.信号量:在内核中创建一个信号量集合(本质上是数组),数组的元素(信号量)都是1,使用P操作进行-1,使用V操作+1

    P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该程序的执行

    V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1

    PV操作用于同一个进程,实现互斥;PV操作用于不同进程,实现同步

    功能:对临界资源进行保护

    <4>.共享内存:将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式

    特点:不同从用户态到内核态的频繁切换和拷贝数据,直接从内存中读取就可以;共享内存是临界资源,所以需要操作时必须要保证原子性。使用信号量或者互斥锁都可以;生命周期随内核举例:Queue

    #!coding:utf-8
    from multiprocessing import Process, Queue
    import os,time,random
    
    #写数据进程执行的代码
    def proc_write(q,urls):
        print 'Process is write....'
        for url in urls:
            q.put(url)
            print 'put %s to queue... ' %url
            time.sleep(random.random())
    
    #读数据进程的代码
    def proc_read(q):
        print('Process is reading...')
        while True:
            url = q.get(True)
            print('Get %s from queue' %url)
    
    if __name__ == '__main__':
        #父进程创建Queue,并传给各个子进程
        q = Queue()
        proc_write1 = Process(target=proc_write,args=(q,['url_1','url_2','url_3']))
        proc_write2 = Process(target=proc_write,args=(q,['url_4','url_5','url_6']))
        proc_reader = Process(target=proc_read,args=(q,))
        #启动子进程,写入
        proc_write1.start()
        proc_write2.start()
    
        proc_reader.start()
        #等待proc_write1结束
        proc_write1.join()
        proc_write2.join()
        #proc_raader进程是死循环,强制结束
        proc_reader.terminate()


        
    Pipe  :

    #!coding:utf-8
    import multiprocessing
    import os,time,random
    
    #写数据进程执行的代码
    def proc_send(pipe,urls):
        #print 'Process is write....'
        for url in urls:
    
            print 'Process is send :%s' %url
            pipe.send(url)
            time.sleep(random.random())
    
    #读数据进程的代码
    def proc_recv(pipe):
        while True:
            print('Process rev:%s' %pipe.recv())
            time.sleep(random.random())
    
    if __name__ == '__main__':
        #父进程创建pipe,并传给各个子进程
        pipe = multiprocessing.Pipe()
        p1 = multiprocessing.Process(target=proc_send,args=(pipe[0],['url_'+str(i) for i in range(10) ]))
        p2 = multiprocessing.Process(target=proc_recv,args=(pipe[1],))
        #启动子进程,写入
        p1.start()
        p2.start()
    
        p1.join()
        p2.terminate()

    163.IO 多路复用的作用?
    IO多路复用,有些地方称之为event driven IO(事件驱动IO)。

    它的好处在于单个进程可以处理多个网络IO请求。select/epoll这两个是函数,它会不断轮询所有的socket,直到某个socket就绪有数据可达,就会通知用户进程,当用户进程调用了select函数,select是一个阻塞方法,会把进程阻塞住,同时会监听所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用readRecv操作,将数据从内核拷贝到用户进程。

    select虽然是阻塞的,但是它的优势在于它可以用一个进程处理多个连接,这个利用非阻塞的轮询方式是无法实现的,当连接数增多时优势就明显,而对于单个连接则跟同步IO区别不大甚至性能还要更低。

    select,poll,epoll都是IO多路复用的机制,IO多路复用就是通过机制用一个进程监视多个描述符,一旦某个描述符就绪(可读或者可写或者异常),能够通知进程进行响应的操作。但是select,poll,epoll本质上是同步IO,因为他们都需要在读写事件就绪后自己负责读写,这个过程是阻塞的。

    164.select、poll、epoll 模型的区别?
    select,poll,epoll都是IO多路复用中的模型
    首先要搞明白两个基本概念:I/O复用和(非)阻塞机制。
    I/O复用指的是允许计算机执行或者阻塞在一组数据流上,直到某个到达唤醒阻塞的进程,此时的I/O信道不仅仅是通过一个数据流,而是一组,所以是复用。

    阻塞和非阻塞:拿I/O为例子,如果是阻塞模型,那么程序一直会等到有数据来的时候才会继续向下执行,否则会一直等待数据的到来;如果是非阻塞模型,如果有数据,那么直接读取数据向下执行,没有数据也会继续向下执行,不过此时可能会进行一些其他的操作,比如Linux中设置一些错误的比特位等。

    select、poll和epoll这三个函数是Linux系统中I/O复用的系统调用函数。I/O复用使得这三个函数可以同时监听多个9文件描述符]()(File Descriptor, FD),因为每个文件描述符相当于一个需要 I/O的“文件”,在socket中共用一个端口。但是,三个函数的本身是阻塞的,因此即使是利用了I/O复用技术,如果程序不采用特别的措施,那么还是只能顺序处理每个文件描述符到来的I/O请求,因此这样默认服务器是串行的。而并发是把上面说的串行处理成同时或者同一时间段,本文暂时不讨论并发。

    select

    select是三者当中最底层的,它的事件的轮训机制是基于比特位的。每次查询都要遍历整个事件列表。
    理解select,首先要理解select要处理的fd_set数据结构,每个select都要处理一个fd_set结构。fd_set简单地理解为一个长度是1024的比特位,每个比特位表示一个需要处理的FD,如果是1,那么表示这个FD有需要处理的I/O事件,否则没有。Linux为了简化位操作,定义了一组宏函数来处理这个比特位数组。select给出一个一般的通用模型:

    int main() {
    
    
      fd_set read_fs, write_fs;
      struct timeval timeout;
      int max_sd = 0;  // 用于记录最大的fd,在轮询中时刻更新即可
     
      /*
       * 这里进行一些初始化的设置,
       * 包括socket建立,地址的设置等,
       * 同时记得初始化max_sd
       */
    
      // 初始化比特位
      FD_ZERO(&read_fs);
      FD_ZERO(&write_fs);
    
      int rc = 0;
      int desc_ready = 0; // 记录就绪的事件,可以减少遍历的次数
      while (1) {
        // 这里进行阻塞
        rc = select(max_sd + 1, &read_fd, &write_fd, NULL, &timeout);
        if (rc < 0) {
          // 这里进行错误处理机制
        }
        if (rc == 0) {
          // 这里进行超时处理机制
        }
    
        desc_ready = rc;
        // 遍历所有的比特位,轮询事件
        for (int i = 0; i <= max_sd && desc_ready; ++i) {
          if (FD_ISSET(i, &read_fd)) {
            --desc_ready;
            // 这里处理read事件,别忘了更新max_sd
          }
          if (FD_ISSET(i, &write_fd)) {
            // 这里处理write事件,别忘了更新max_sd
          }
        }
      }
    }


    poll

    可以认为poll是一个增强版本的select,因为select的比特位操作决定了一次性最多处理的读或者写事件只有1024个,而poll使用一个新的方式优化了这个模型。// 先宏定义长度

    #define MAX_POLLFD_LEN 200  
    
    int main() {
      /*
       * 在这里进行一些初始化的操作,
       * 比如初始化数据和socket等。
       */
    
      int rc = 0;
      pollfd fds[MAX_POLL_LEN];
      memset(fds, 0, sizeof(fds));
      int ndfs  = 1;  // 队列的实际长度,是一个随时更新的,也可以自定义其他的
      int timeout = 0;
      /*
       * 在这里进行一些感兴趣事件的注册,
       * 每个pollfd可以注册多个类型的事件,
       * 使用 | 操作即可,就行博文提到的那样。
       * 根据需要设置阻塞时间
       */
    
      int current_size = ndfs;
      int compress_array = 0;  // 压缩队列的标记
      while (1) {
        rc = poll(fds, nfds, timeout);
        if (rc < 0) {
        // 这里进行错误处理
        }
        if (rc == 0) {
        // 这里进行超时处理
        }
    
        for (int i = 0; i < current_size; ++i) {
          if (fds[i].revents == 0){  // 没有事件可以处理
            continue;
          }
          if (fds[i].revents & POLLIN) {  // 简单的例子,比如处理写事件
          
          }
          /*
           * current_size 是为了降低复杂度的,可以随时进行更新
           * ndfs如果要更新,应该是最后统一进行
           */
        }
    
        if (compress_array) {  // 如果需要压缩队列
          compress_array = 0;
          for (int i = 0; i < ndfs; ++i) {
            for (int j = i; j < ndfs; ++j) {
              fds[i].fd = fds[j + i].fd;
            }
            --i;
            --ndfs;
          }
        }
      }
    }

    epoll
    epoll是一个更加高级的操作,上述的select或者poll操作都需要轮询所有的候选队列逐一判断是否有事件,而且事件队列是直接暴露给调用者的,比如上面select的write_fd和poll的fds,这样复杂度高,而且容易误操作。epoll给出了一个新的模式,直接申请一个epollfd的文件,对这些进行统一的管理,初步具有了面向对象的思维模式。

    #define MAX_EVENTS 10
    int main() {
        struct epoll_event ev, events[MAX_EVENTS];
        int listen_sock, conn_sock, nfds, epollfd;
    
        /* Code to set up listening socket, 'listen_sock',
         (socket(), bind(), listen()) omitted */
    
        epollfd = epoll_create1(0);
        if (epollfd == -1) {
            perror("epoll_create1");
              exit(EXIT_FAILURE);
        }
    
        ev.events = EPOLLIN;
        ev.data.fd = listen_sock;
        if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
            perror("epoll_ctl: listen_sock");
            exit(EXIT_FAILURE);
        }
    
        for (;;) {
            // 永久阻塞,直到有事件
            nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
            if (nfds == -1) {  // 处理错误
                perror("epoll_wait");
                exit(EXIT_FAILURE);
            }
    
            for (n = 0; n < nfds; ++n) {
                if (events[n].data.fd == listen_sock) {
                    conn_sock = accept(listen_sock, (struct sockaddr *) &addr, &addrlen);
                    if (conn_sock == -1) {
                        perror("accept");
                        exit(EXIT_FAILURE);
                    }
                    setnonblocking(conn_sock);
                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = conn_sock;
                    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
                        perror("epoll_ctl: conn_sock");
                        exit(EXIT_FAILURE);
                    }
                } else {
                    do_use_fd(events[n].data.fd);
                }
            }
        }
        return 0;
    }

    版权声明:本文为CSDN博主「Erick_Lv」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_35976351/article/details/85228002

    165.什么是并发和并行?
    举个简单的例子:

        你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行.

        你吃饭吃到一半,电话来了,你停了下来接了电话,接完后电话以后继续吃饭,这说明你支持并发。

    你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
    并行与并发的理解
    并发:交替处理多个任务的能力;
    并行:同时处理多个任务的能力;
    并发的关键是你有处理多个任务的能力,不一定要同时。

    并行的关键是你有同时处理多个任务的能力,强调的是同时.

    所以它们最大的区别就是:是否是『同时』处理任务。

    对于一个多核cpu来说并行显然要比并发快的多
    由此我们可以知道一个多核cpu在处理多个任务的时候如果想要发挥最大功效就要实现并行

    那我们在使用多线程和多进程来写程序的时候就是为了让多核cup发挥他最大的功效实现并行,
    也就是我们面试题参考答案的结果166.一个线程 1 让线程 2 去调用一个函数怎么实现?

    import threading
    
    def func1(t2):
        print('正在执行函数func1')
        t2.start()
    
    
    def func2():
        print('正在执行函数func2')
    
    
    if __name__ == '__main__':
        t2 = threading.Thread(target=func2)
        t1 = threading.Thread(target=func1, args=(t2,))
        t1.start()

    167.解释什么是异步非阻塞?


    在IO和网络编程中,我们经常看到几个概念:同步、异步、阻塞、非阻塞。
    同步和异步

    同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。
    阻塞和非阻塞

    阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。

     
    理解方式

    乍一看这四个概念的解释会瞬间感到头大,也经常讲同步异步等同于阻塞非阻塞,其实,区分他们非常简单。

    同步异步与阻塞非阻塞的主要区别是针对对象不同。

     

    同步异步是针对调用者来说的,调用者发起一个请求后,一直干等被调用者的反馈就是同步,不必等去做别的事就是异步。

    阻塞非阻塞是针对被调用者来说的,被调用者收到一个请求后,做完请求任务后才给出反馈就是阻塞,收到请求直接给出反馈再去做任务就是非阻塞。

     

    在公交站等公交

    对调用者-乘客而言:

    1,一直干望着公交来的方向,就是同步。

    2,不望着公交来的方向,掏出笔记本改bug,听公交站广播是否到车,就是异步。

    对被动用者-公交系统而言:

    1,公交站有广播的就是非阻塞的。

    2,公交站没有广播的就是阻塞的。


    168.threading.local 的作用?
    threading.local()这个方法的特点用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,如果你在开发多线程应用的时候  需要每个线程保存一个单独的数据供当前线程操作,可以考虑使用这个方法,简单有效。举例:每个子线程使用全局对象a,但每个线程定义的属性a.xx是该线程独有的,Python提供了 threading.local 类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其它线程不可见(本质上就是不同的线程使用这个对象时为其创建一个独立的字典)

    import time
    import threading
    
    local = threading.local()
    
    def func(n):
        print(threading.current_thread())
        local.val = n
        time.sleep(2)
    
        print(n)
    
    for i in range(10):
        t = threading.Thread(target=func,args=(i,))
        t.start()

     

    Git 面试题
    169.说说你知道的 git 命令
    https://blog.csdn.net/halaoda/article/details/78661334?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

    170.git 如何查看某次提交修改的内容
    git查看某一段时间更新代码量的命令

     git log --since=2018-09.01 --until=2018-09.27 --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\n",add,subs,loc }' -

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    你可能感兴趣的:(python,面试题)