赵走x博客
网站访问量:151549
首页
书籍
软件
工具
古诗词
搜索
登录
Python面试整理:2、基础篇(答案)
Python面试整理:1、基础篇(问题)
Python面试整理:2、基础篇(答案)
资源编号:75741
精品推荐
Python 面试题整理
热度:90
答案
# 1、基础答案 1.代码不会正常运行,抛出TypeError 异常。 2. ``` a=1 b=2 a,b=b,a ``` 3.sys.stdout.write() 4. ``` 1 1 1 1 2 1 3 2 3 ``` 5.在Python3 中,input()获取用户输入,不论用户输入的是什么,获取到的都是字符串类型的。 6. ``` {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5} range(0, 10) [] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]] ``` 7.不一样,两个输出结果分别为: ``` [{'num': 0}, {'num': 1}, {'num': 2}, {'num': 3}, {'num': 4}, {'num': 5}, {'num': 6}, {'num': 7}, {'num': 8}, {'num': 9}] [{'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}, {'num': 9}] ``` 原因是:字典是可变对象,在下方的l.append(a)的操作中是把字典a 的引用传到列表l 中,当后 续操作修改a[‘num’]的值的时候,l 中的值也会跟着改变,相当于浅拷贝。 8. 方法一: 可以通过生成器,分多次读取,每次读取数量相对少的数据(比如500MB)进行处理,处理结束后 在读取后面的500MB 的数据。 方法二: 可以通过linux 命令split 切割成小文件,然后再对数据进行处理,此方法效率比较高。可以按照行 数切割,可以按照文件大小切割。 9. ``` def get_lines(): l = [] with open('file.txt','rb') as f: data = f.readlines(60000) l.append(data) yield l ``` 要考虑到的问题有: 内存只有4G 无法一次性读入10G 的文件,需要分批读入。分批读入数据要记录每次读入数据的位 置。分批每次读入数据的大小,太小就会在读取操作上花费过多时间。 10. read:读取整个文件。 readline:读取下一行,使用生成器方法。 readlines:读取整个文件到一个迭代器以供我们遍历。 11. ``` def print_directory_contents(sPath): """ 这个函数接收文件夹的名称作为输入参数 返回该文件夹中文件的路径 以及其包含文件夹中文件的路径 """ import os for sChild in os.listdir(sPath): sChild=os.path.join(sPath,sChild) if os.path.isdir(sChild): print_directory_contents(sChild) else: print(sChild) ``` 12.会继续处理finally 中的代码;用raise 方法可以抛出自定义异常。 13.init 在对象创建后,对对象进行初始化。 new 是在对象创建之前创建一个对象,并将该对象返回给init。 14. ``` import datetime date1=datetime.date(year=2019,month=6,day=26) date2=datetime.date(year=2019,month=1,day=1) print((date1-date2).days) ``` 15. ``` a = "not 404 found 闪电盒子 2018 北京" import re pattern=re.compile('[0-9a-zA-Z]') result=re.sub(pattern,'',a).strip() print(result) ``` 16.dir()是一个函数,返回的是list; __dict__是一个字典,键为属性名,值为属性值; dir()用来寻找一个对象的所有属性,包括__dict__中的属性,__dict__是dir()的子集; 并不是所有对象都拥有__dict__属性。许多内建类型就没有__dict__属性,如list,此时就需要用dir()来列出对象的所有属性。 ``` {'__module__': '__main__', 'x': 2, 'hah':
, '__dict__':
, '__weakref__':
, '__doc__': None} ['__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__', 'hah', 'x'] ``` 17. 创建数据库: ``` use db.demo ``` 创建索引: ``` db.demo.createIndex({'title':1}) ``` 设置过期时间(在创建索引时设置) ``` db.demo.createIndex({'title':1},{expireAfterSeconds:60*60*24*7}) ``` 18. ``` import random random.shuffle(alist) ``` 19.os.path主要用于对系统路径文件的操作,sys.path主要对Python解释器的系统环境参数的操作(动态改变Python解析器搜索路径)。 20. os.remove():删除文件 os.rename():文件重命名 os.walk():生成目录树下的所有文件名 os.chdir():改变目录 os.mkdir:创建目录 os.rmdir:删除目录 os.dirlist():列出指定目录的文件 os.getcwd():获取当前工作目录 os.chmod():改变目录权限 os.path.basename():去掉目录路径,返回文件名 os.path.dirname():去掉文件名,返回目录路径 os.path.join():将分离的各部分组合成一个路径名。 os.path.split():返回(dirname,basename)元祖 os.getsize():返回文件大小 os.path.exists():文件是否存在 os.path.isabs():是否为绝对路径 os.path.isdir():是否为目录 os.path.isfile():是否为文件 21.Python 是强类型的动态脚本语言。 强类型:不允许不同类型相加。 动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。 脚本语言:一般也是解释型语言,运行代码只需要一个解释器,不需要编译。 22.Python 自省是Python 具有的一种能力,使程序员面向对象的语言所写的程序在运行时,能够获得对象的类Python 型。 23. ``` sorted(d.items(),key=lambda x:x[1]) ``` 24. ``` [x*11 for x in range(10)] ``` 25.输出结果为: ``` [10, 'a'] [123] [10, 'a'] ``` 新的默认列表只在函数被定义的那一刻创建一次。当extendList 被没有指定特定参数list 调用时,这组list 的值随后将被使用。这是因为带有默认参数的表达式在函数被定义的时候被计算,不是在调用的时候被计算。 26. 类方法:是类对象的方法,在定义时需要在上方使用“@classmethod”进行装饰,形参为cls, 表示类对象,类对象和实例对象都可调用; 类实例方法:是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身; 静态方法:是一个任意函数,在其上方使用“@staticmethod”进行装饰,可以用对象直接调用, 静态方法实际上跟该类没有太大关系。 27. hasattr(对象,属性); getattr(对象,属性); setattr(对象,属性,值) 28.内存管理机制:引用计数、垃圾回收、内存池。 引用计数: 引用计数是一种非常高效的内存管理手段, 当一个Python 对象被引用时其引用计数增加1, 当 其不再被一个变量引用时则计数减1. 当引用计数等于0 时对象被删除。 垃圾回收: 1. 引用计数 引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当Python 的某 个对象的引用计数降为0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如 某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0, 那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了 2. 标记清除 如果两个对象的引用计数都为1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被 回收的,也就是说,它们的引用计数虽然表现为非0,但实际上有效的引用计数为0。所以先将循环引 用摘掉,就会得出这两个对象的有效计数。 3. 分代回收 从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统 中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾 回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额 外操作。 举个例子: 当某些内存块M 经过了3 次垃圾收集的清洗之后还存活时,我们就将内存块M 划到一个集合 A 中去,而新分配的内存都划分到集合B 中去。当垃圾收集开始工作时,大多数情况都只对集合B 进 行垃圾回收,而对集合A 进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处 理的内存少了,效率自然就提高了。在这个过程中,集合B 中的某些内存块由于存活时间长而会被转 移到集合A 中,当然,集合A 中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而 被延迟。 内存池: 1. Python 的内存机制呈现金字塔形状,-1,-2 层主要有操作系统进行操作; 2. 第0 层是C 中的malloc,free 等内存分配和释放函数进行操作; 3. 第1 层和第2 层是内存池,有Python 的接口函数PyMem_Malloc 函数实现,当对象小于 256K 时有该层直接分配内存; 4. 第3 层是最上层,也就是我们对Python 对象的直接操作; Python 在运行期间会大量地执行malloc 和free 的操作,频繁地在用户态和核心态之间进行切 换,这将严重影响Python 的执行效率。为了加速Python 的执行效率,Python 引入了一个内存池 机制,用于管理对小块内存的申请和释放。 Python 内部默认的小块内存与大块内存的分界点定在256 个字节,当申请的内存小于256 字节 时,PyObject_Malloc 会在内存池中申请内存;当申请的内存大于256 字节时,PyObject_Malloc 的 行为将蜕化为malloc 的行为。当然,通过修改Python 源代码,我们可以改变这个默认值,从而改变Python 的默认内存管理行为。 调优手段(了解) 1.手动垃圾回收 2.调高垃圾回收阈值 3.避免循环引用(手动解循环引用和使用弱引用) 29.指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的 消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪 费。导致程序运行速度减慢甚至系统崩溃等严重后果。 有__del__() 函数的对象间的循环引用是导致内存泄漏的主凶。 不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。 通过Python 扩展模块gc 来查看不能回收的对象的详细信息。 可以通过sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为0 来判断是否内存 泄漏。 30.①从参数方面来讲: map()包含两个参数,第一个参数是一个函数,第二个是序列(列表或元组)。其中,函数(即map 的第一个参数位置的函数)可以接收一个或多个参数。 reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。 ②从对传进去的数值作用来讲: map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次。 reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用 (累积计算)。 reduce在Python3中取消了。 31. ``` class A(object): __instance = None def __new__(cls): if cls.__instance is None: cls.__instance = object.__new__(cls) return cls.__instance ``` 32.装饰器本质上是一个Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外 功能,装饰器的返回值也是一个函数对象。 ```import time def timeit(func): def wrapper(): start=time.clock() func() end=time.clock() print('程序运行时间',(end-start)) return wrapper() ``` 33.在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为 闭包。 34. 迭代器是一个更抽象的概念,任何对象,如果它的类有next 方法和iter 方法返回自己本身,对于string、list、 dict、tuple 等这类容器对象,使用for 循环遍历是很方便的。在后台for 语句对容器对象调用iter()函数,iter() 是python 的内置函数。iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素,next() 也是python 的内置函数。在没有后续元素时,next()会抛出一个StopIteration 异常。 生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数 据的时候使用yield 语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置 和所有的数据值) 区别:生成器能做到迭代器能做的所有事,而且因为自动创建了iter()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration 异常。 1、生成器一定是迭代器, 迭代对象不一定是迭代器,例如list、dict、str 2、生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用尖括号而不是方括号 ``` >>> # 列表解析生成列表 >>> [ x ** 3 for x in range(5)] [0, 1, 8, 27, 64] >>> >>> # 生成器表达式 >>> (x ** 3 for x in range(5))
at 0x000000000315F678> >>> # 两者之间转换 >>> list(x ** 3 for x in range(5)) [0, 1, 8, 27, 64] ``` 3、一个实现了iter方法的对象时可迭代的,一个实现next方法的对象是迭代器 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。 4、凡是可作用于for循环的对象都是Iterable类型; 5、凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列; 集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。 5、对yield的总结 (1)通常的for..in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。 它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。 (2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。 (3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。 (4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代 (5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行 (7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。 (8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。 (9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。 (10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None) 可能会问,为什么list、dict、str等数据类型不是Iterator? 这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。 Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。 35.魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个 方法就会在特殊的情况下被Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。它们经常是 两个下划线包围来命名的(比如__init__,__lt__),Python 的魔法方法是非常强大的,所以了解其使用方法也变得 尤为重要! __init__ 构造器,当一个实例被创建的时候初始化的方法。但是它并不是实例化调用的第一个方法。 __new__才是实例化对象调用的第一个方法,它只取下cls 参数,并把其他参数传给__init__。__new__很少使 用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。 __call__ 允许一个类的实例像函数一样被调用。 __getitem__ 定义获取容器中指定元素的行为,相当于self[key] 。 __getattr__ 定义当用户试图访问一个不存在属性的时候的行为。 __setattr__ 定义当一个属性被设置的时候的行为。 __getattribute__ 定义当一个属性被访问的时候的行为。 36.match()函数只检测RE 是不是在string 的开始位置匹配, search()会扫描整个string 查找匹配; 也就是说match()只有在0 位置匹配成功的话才有返回, 如果不是开始位置匹配成功的话,match()就返回none。 37.re.findall()、re.sub() 38.进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间 片、pid。 创建进程: 1.首先要导入multiprocessing 中的Process; 2.创建一个Process 对象; 3.创建Process 对象时,可以传递参数; p = Process(target=XXX, args=(元组,) , kwargs={key:value}) target = XXX 指定的任务函数,不用加() args=(元组,) , kwargs={key:value} 给任务函数传递的参数 4.使用start()启动进程; 5.结束进程。 Process 语法结构: Process([group [, target [, name [, args [, kwargs]]]]]) target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码 args:给target 指定的函数传递的参数,以元组的形式进行传递 kwargs:给target 指定的函数传递参数,以字典的形式进行传递 name:给进程设定一个名字,可以省略 group:指定进程组,大多数情况下用不到 Process 创建的实例对象的常用方法有: start():启动子进程实例(创建子进程) is_alive():判断进程子进程是否还在活着 join(timeout):是否等待子进程执行结束,或者等待多少秒 terminate():不管任务是否完成,立即终止子进程 Process 创建的实例对象的常用属性: name:当前进程的别名,默认为Process-N,N 为从1 开始递增的整数 pid:当前进程的pid(进程号) 给子进程指定函数传递参数Demo: ``` from multiprocessing import Process import time import os def pro_finc(name,age,**kwargs): for i in range(5): print('子进程正在运行中,name={0},age={1},pid={2}'.format(name,age,os.getpid())) print(kwargs) time.sleep(0.5) if __name__ == '__main__': #创建Process对象 process=Process(target=pro_finc,args=('mark',18),kwargs={'test':True}) process.start() time.sleep(1) process.terminate() process.join() ``` 输出: ``` 子进程正在运行中,name=mark,age=18,pid=3996 {'test': True} 子进程正在运行中,name=mark,age=18,pid=3996 {'test': True} 子进程正在运行中,name=mark,age=18,pid=3996 {'test': True} ``` 注意:进程间不共享全局变量。 进程之间的通信-Queue 在初始化Queue()对象时,(例如q=Queue(),若在括号中没有指定最大可接受的消息数量,或数 量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头) Queue.qsize():返回当前队列包含的消息数量。 Queue.empty():如果队列为空,返回True,反之False。 Queue.full():如果队列满了,返回True,反之False。 Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block 默认值为 True。 如果block 使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞 (停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout 秒,若还 没读取到任何消息,则抛出"Queue.Empty"异常; 如果block 值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常; Queue.get_nowait():相当Queue.get(False); Queue.put(item,[block[, timeout]]):将item 消息写入队列,block 默认值为True; 如果block 使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此 时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待 timeout 秒,若还没空间,则抛出"Queue.Full"异常; 如果block 值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常; Queue.put_nowait(item):相当Queue.put(item, False); 进程间通信Demo: ``` from multiprocessing import Process, Queue import time import random def write(q): for value in ['a', 'b', 'c', 'd']: print('put {0} to queue...'.format(value)) q.put(value) time.sleep(random.random()) def read(q): while True: if not q.empty(): value = q.get(True) print('get {0} from queue ...'.format(value)) time.sleep(random.random()) else: break if __name__ == '__main__': q = Queue() # 创建Process对象 pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) pw.start() # 等待pw结束 pw.join() pr.start() pr.join() print('程序结束了') ``` 运行结果: ``` put a to queue... put b to queue... put c to queue... put d to queue... get a from queue ... get b from queue ... get c from queue ... get d from queue ... 程序结束了 ``` 进程池Pool的Demo: ``` import time import os import random from multiprocessing import Pool def worker(msg): start = time.time() print('{0}开发执行,进程号为{1}'.format(msg, os.getpid())) time.sleep(random.random()) end = time.time() print('{0}执行完毕,耗时{1}'.format(msg, end - start)) if __name__ == '__main__': pool = Pool(3) for i in range(10): pool.apply_async(worker, (i,)) print('---start----') # 关闭后pool不再接受新的请求 pool.close() # 等到pool所有进程完成,必须放在close()方法后面 pool.join() print('---end----') ``` 运行结果: ``` ---start---- 0开发执行,进程号为8221 1开发执行,进程号为8222 2开发执行,进程号为8223 0执行完毕,耗时0.16651701927185059 3开发执行,进程号为8221 1执行完毕,耗时0.28675365447998047 4开发执行,进程号为8222 3执行完毕,耗时0.20254898071289062 5开发执行,进程号为8221 2执行完毕,耗时0.6237771511077881 6开发执行,进程号为8223 4执行完毕,耗时0.3470950126647949 7开发执行,进程号为8222 5执行完毕,耗时0.4648277759552002 8开发执行,进程号为8221 8执行完毕,耗时0.04522085189819336 9开发执行,进程号为8221 6执行完毕,耗时0.3142421245574951 7执行完毕,耗时0.3910789489746094 9执行完毕,耗时0.2758359909057617 ---end---- ``` multiprocessing.Pool 常用函数解析: * apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args 为传递给func 的参数列表,kwds 为传递给func的关键字参数列表; * close():关闭Pool,使其不再接受新的任务; * terminate():不管任务是否完成,立即终止; * join():主进程阻塞,等待子进程的退出, 必须在close 或terminate 之后使用; 进程池中使用Queue 如果要使用Pool 创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是 multiprocessing.Queue(),否则会得到一条如下的错误信息: RuntimeError: Queue objects should only be shared between processes through inheritance. ``` import os from multiprocessing import Pool, Manager import time def reader(q): print('reader启动{0},父进程为{1}'.format(os.getpid(), os.getppid())) for i in range(q.qsize()): print('reader从Queue获得消息{0}'.format(q.get(True))) def writter(q): print('writer启动{0},父进程为{1}'.format(os.getpid(), os.getppid())) for x in 'mark': q.put(x) if __name__ == '__main__': print('---start-{0}---'.format(os.getpid())) q = Manager().Queue() pool = Pool() pool.apply_async(writter, (q,)) time.sleep(1) pool.apply_async(reader, (q,)) pool.close() pool.join() print('---end-{0}---'.format(os.getpid())) ``` 运行结果: ``` ---start-9021--- writer启动9023,父进程为9021 reader启动9024,父进程为9021 reader从Queue获得消息m reader从Queue获得消息a reader从Queue获得消息r reader从Queue获得消息k ---end-9021--- ``` 39.这个问题被问的概率相当之大,其实多线程,多进程,在实际开发中用到的很少,除非是那些对项 目性能要求特别高的,有的开发工作几年了,也确实没用过,你可以这么回答,给他扯扯什么是进程, 线程(cpython 中是伪多线程)的概念就行,实在不行你就说你之前写过下载文件时,用过多线程技术, 或者业余时间用过多线程写爬虫,提升效率。 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最 小单位,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大。 线程: 调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个 线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。 协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和 栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存 器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切 换非常快。 40.线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即: 数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 那么怎么解决多线程竞争问题?-- 锁。 锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下 的原子操作问题。 锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了 锁的致命问题:死锁。 锁(Lock)是Python 提供的对线程控制的对象。有互斥锁、可重入锁、死锁。 若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁, 互相干等着,程序无法执行下去,这就是死锁。 GIL 锁(有时候,面试官不问,你自己要主动说,增加b 格,尽量别一问一答的尬聊,不然最后等 到的一句话就是:你还有什么想问的么?) GIL 锁全局解释器锁(只在cpython 里才有) 作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以cpython 里的多线程其实是伪 多线程! 所以Python 里常常使用协程技术来代替多线程,协程是一种更轻量级的线程, 进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent 下切换是遇到了 耗时操作才会切换。 三者的关系:进程里有线程,线程里有协程。 41.每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程 访问该对象。 同一个进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作 尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁, 保证每个线程对该对象的操作都得到正确的结果。 42.同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。 异步:多个任务之间没有先后顺序,可以同时执行有时候一个任务可能要在必要的时候获取另一个 同时执行的任务的结果,这个就叫回调! 阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。 非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。 同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。 43.孤儿进程:父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进 程号为1)所收养,并由init 进程对它们完成状态收集工作。 僵尸进程:进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用wait 或waitpid 获 取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。 避免僵尸进程的方法: 1.fork 两次用孙子进程去完成子进程的任务; 2.用wait()函数使父进程阻塞; 3.使用信号量,在signal handler 中调用waitpid,这样父进程不用阻塞。 44.多进程适合在CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。 多线程适合在IO 密集型操作(读写数据操作较多的,比如爬虫)。 45.线程是并发,进程是并行; 进程之间相互独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。 并行:同一时刻多个任务同时在运行。 并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情 况。 实现并行的库有:multiprocessing 实现并发的库有:threading 程序需要执行较多的读写、请求和回复任务的需要大量的IO 操作,IO 密集型操作使用并发更好。 CPU 运算量大的程序程序,使用并行会更好。 46. 使用udp 发送/接收数据步骤: 1.创建客户端套接字 2.发送/接收数据 3.关闭套接字 发送方Demo: ``` import socket if __name__ == '__main__': # 创建UDP套接字 # socket.AF_INET表示IPv4协议,socker.AF_INET6表示IPv6协议 # socker.SOCK_DGRM:数据包套接字 udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 准备接收方的地址 rever_addr=('localhost',7777) send_data='我是要发送的数据' # 发送数据 udp_socket.sendto(send_data.encode('utf-8'),rever_addr) # 关闭套接字 udp_socket.close() ``` 接收方Demo: ``` import socket if __name__ == '__main__': # 创建UDP套接字 # socket.AF_INET表示IPv4协议,socker.AF_INET6表示IPv6协议 # socker.SOCK_DGRM:数据包套接字 udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 绑定端口号 local_addr=('localhost',7777) udp_socket.bind(local_addr) # 等待接收方发送的数据,如果没有收到数据就会就会一直阻塞,知道收到数据 # 接收到的数据是一个元组:(接受到的数据,接收方的ip和端口) # 1024表示本次接受的最大字节数 recv_data,addr=udp_socket.recvfrom(1024) print('接收到数据:{0},地址为:{1}'.format(recv_data.decode('utf-8'), addr)) # 关闭套接字 udp_socket.close() ``` 47. TCP 客户端的创建流程: 1.创建TCP 的socket 套接字 2.连接服务器 3.发送数据给服务器端 4.接收服务器端发送来的消息 5.关闭套接字 TCP服务器端Demo: ``` import socket if __name__ == '__main__': # 创建tcp服务器端的socket server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 绑定端口号 server_socket.bind(('',8888)) #listen是套接字变成被动套接字 server_socket.listen(128) # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务 client_socket,client_addr=server_socket.accept() recv_data=client_socket.recv(1024) print('接收到客户端的消息:{0},地址为:{1}'.format(recv_data.decode('utf-8'),client_addr)) client_socket.close() server_socket.close() ``` TCP客户端Demo: ``` import socket if __name__ == '__main__': client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) client_socket.connect(('',8888)) client_socket.send('我是发送你数据'.encode('utf-8')) client_socket.close() ``` 注意点: * tcp 服务器一般都需要绑定,否则客户端找不到服务器 * tcp 客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port 等信息就好,本地客户端可以随机 * tcp 服务器中通过listen 可以将socket 创建出来的主动套接字变为被动的,这是做tcp 服务器时必须要做的 * 当客户端需要链接服务器时,就需要使用connect 进行链接,udp 是不需要链接的而是直接发送,但是tcp 必须先链接,只有链接成功才能通信 * 当一个tcp 客户端连接服务器时,服务器端会有1 个新的套接字,这个套接字用来标记这个客户 端,单独为这个客户端服务 * listen 后的套接字是被动套接字,用来接收新的客户端的连接请求的,而accept 返回的新套接字是标识这个新客户端的 * 关闭listen 后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。 * 关闭accept 返回的套接字意味着这个客户端已经服务完毕 * 当客户端的套接字调用close 后,服务器端会recv 解阻塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线;同理当服务器断开tcp 连接的时候客户端同样也会收到0 字节数据。 48.在socket 通信过程中不断循环检测一个全局变量(开关标记变量),一旦标记变量变为关闭,则调 用socket 的close 方法,循环结束,从而达到关闭连接的目的。 49.UDP 是面向无连接的通讯协议,UDP 数据包括目的端口号和源端口号信息。 优点:UDP 速度快、操作简单、要求系统资源较少,由于通讯不需要连接,可以实现广播发送 缺点:UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数 据是否会正确接收,也不重复发送,不可靠。 TCP 是面向连接的通讯协议,通过三次握手建立连接,通讯完成时四次挥手 优点:TCP 在数据传递时,有确认、窗口、重传、阻塞等控制机制,能保证数据正确性,较为可靠。 缺点:TCP 相对于UDP 速度慢一点,要求系统资源较多。 50. 1.发送http 请求动态资源给web 服务器 2.web 服务器收到请求后通过WSGI 调用一个属性给应用程序框架 3.应用程序框架通过引用WSGI 调用web 服务器的方法,设置返回的状态和头信息。 4.调用后返回,此时web 服务器保存了刚刚设置的信息 5.应用程序框架查询数据库,生成动态页面的body 的信息 6.把生成的body 信息返回给web 服务器 7.web 服务器吧数据返回给浏览器 51. 先要解析出baidu.com 对应的ip 地址 1.要先使用arp 获取默认网关的mac 地址 2.组织数据发送给默认网关(ip 还是dns 服务器的ip,但是mac 地址是默认网关的mac 地址) 3.默认网关拥有转发数据的能力,把数据转发给路由器 4.路由器根据自己的路由协议,来选择一个合适的较快的路径转发数据给目的网关 5.目的网关(dns 服务器所在的网关),把数据转发给dns 服务器 6.dns 服务器查询解析出baidu.com 对应的ip 地址,并原路返回请求这个域名的client得到了baidu.com 对应的ip 地址之后,会发送tcp 的3 次握手,进行连接 7.使用http 协议发送请求数据给web 服务器 8.web 服务器收到数据请求之后,通过查询自己的服务器得到相应的结果,原路返回给浏览器。 9.浏览器接收到数据之后通过浏览器自己的渲染功能来显示这个网页。 10.浏览器关闭tcp 连接,即4 次挥手结束,完成整个访问过程 52. 1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。 2、cookie 不是很安全,别人可以分析存放在本地的cookie 并进行cookie 欺骗考虑到安全应当使 用session。 3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用服务器的性能考虑到减轻服 务器性能方面,应当使用cookie。 4、单个cookie 保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20 个cookie。 5、建议: 将登陆信息等重要信息存放为SESSION 其他信息如果需要保留,可以放在cookie中。 Session 采用的是在服务器端保持状态的方案,而Cookie 采用的是在客户端保持状态的方案。但 是禁用Cookie 就不能得到Session。因为Session 是用Session ID 来确定当前对话所对应的服务器Session,而Session ID 是通过Cookie 来传递的,禁用Cookie 相当于失去了SessionID,也就得不到Session。 53.  三次握手过程: 1 首先客户端向服务端发送一个带有SYN 标志,以及随机生成的序号100(0 字节)的报文 2 服务端收到报文后返回一个报文(SYN200(0 字节),ACk1001(字节+1))给客户端 3 客户端再次发送带有ACk 标志201(字节+)序号的报文给服务端 至此三次握手过程结束,客户端开始向服务端发送数据。 1 客户端向服务端发起请求:我想给你通信,你准备好了么? 2 服务端收到请求后回应客户端:I'ok,你准备好了么 3 客户端礼貌的再次回一下客户端:准备就绪,咱们开始通信吧! 整个过程跟打电话的过程一模一样:1 喂,你在吗2 在,我说的你听得到不3 恩,听得到(接下来请 开始你的表演) 补充:SYN:请求询问,ACk:回复,回应。 四次挥手过程: 由于TCP 连接是可以双向通信的(全双工),因此每个方向都必须单独进行关闭(这句话才是 精辟,后面四个挥手过程都是其具体实现的语言描述) 四次挥手过程,客户端和服务端都可以先开始断开连接 1 客户端发送带有fin 标识的报文给服务端,请求通信关闭 2 服务端收到信息后,回复ACK 答应关闭客户端通信(连接)请求 3 服务端发送带有fin 标识的报文给客户端,也请求关闭通信 4 客户端回应ack 给服务端,答应关闭服务端的通信(连接)请求 54. HTTP 协议传输的数据都是未加密的,也就是明文的,因此使用HTTP 协议传输隐私信息非常不安 全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于 对HTTP 协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS 协议是由SSL+HTTP 协 议构建的可进行加密传输、身份认证的网络协议,要比http 协议安全。 HTTPS 和HTTP 的区别主要如下: 1、https 协议需要到ca 申请证书,一般免费证书较少,因而需要一定费用。 2、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl 加密传输协议。 3、http 和https 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。 4、http 的连接很简单,是无状态的;HTTPS 协议是由SSL+HTTP 协议构建的可进行加密传输、 身份认证的网络协议,比http 协议安全。 HTTPS优点: 1、使用HTTPS 协议可认证用户和服务器,确保数据发送到正确的客户机和服务器; 2、HTTPS 协议是由SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比http 协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。 3、HTTPS 是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击 的成本 缺点: 1.HTTPS 协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起 不到什么作用 2.HTTPS 协议还会影响缓存,增加数据开销和功耗,甚至已有安全措施也会受到影响也会因此 而受到影响。 3.SSL 证书需要钱。功能越强大的证书费用越高。个人网站、小网站没有必要一般不会用。 4.HTTPS 连接服务器端资源占用高很多,握手阶段比较费时对网站的相应速度有负面影响。 5.HTTPS 连接缓存不如HTTP 高效。 HTTPS 是如何实现安全传输数据的: HTTPS 其实就是在HTTP 跟TCP 中间加多了一层加密层TLS/SSL。SSL 是个加密套件,负责对HTTP 的数据进行加密。TLS 是SSL 的升级版。现在提到HTTPS,加密套件基本指的是TLS。原先是应用层将 数据直接给到TCP 进行传输,现在改成应用层将数据给到TLS/SSL,将数据加密后,再给到TCP 进行 传输。 55.@app.route('
')中URL 显式支持string、int、float、path 4 种类型,隐式支持正则。 第一步:写正则类,继承BaseConverter,将匹配到的值设置为regex 的值。 ``` class RegexUrl(BaseConverter): def __init__(self, url_map, *args): super(RegexUrl, self).__init__(url_map) self.regex = args[0] ``` 第二步:把正则类赋值给我们定义的正则规则。 ``` app.url_map.converters['re'] = RegexUrl ``` 第三步:在URL 中使用正则。 ``` @app.route('/regex/
') def regex111(id): return 'id:%s'%id ``` 56.current_app、g 是应用上下文。 request、session 是请求上下文。 手动创建上下文的两种方法: ``` with app.app_context() app = current_app._get_current_object() ``` 两者区别: 请求上下文:保存了客户端和服务器交互的数据。 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用 信息等。 两者作用: 请求上下文(request context): Flask 从客户端收到请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象是一 个很好的例子,它封装了客户端发送的HTTP 请求。 要想让视图函数能够访问请求对象,一个显而易见的方式是将其作为参数传入视图函数,不过 这会导致程序中的每个视图函数都增加一个参数,除了访问请求对象,如果视图函数在处理请求时还 要访问其他对象,情况会变得更糟。为了避免大量可有可无的参数把视图函数弄得一团糟,Flask 使用上下文临时把某些对象变为全局可访问。 应用上下文(application context): 它的字面意思是应用上下文,但它不是一直存在的,它只是request context 中的一个对app 的代理(人),所谓local proxy。它的作用主要是帮助request 获取当前的应用,它是伴request 而生,随request 而灭的。 57.Flask 中有三个session: 第一个:数据库中的session,例如:db.session.add() 第二个:在flask_session 扩展中的session,使用:from flask_session import Session,使用 第三方扩展的session 可以把信息存储在服务器中,客户端浏览器中只存储sessionid。 第三个:flask 自带的session,是一个请求上下文, 使用:from flask import session。自带的session 把信息加密后都存储在客户端的浏览器cookie 中。 58.  图中Browse 是浏览器,WebServerA 是受信任网站/被攻击网站A,WebServerB 是恶意网站/点 击网站B。 (1)一开始用户打开浏览器,访问受信任网站A,输入用户名和密码登陆请求登陆网站A。 (2)网站A 验证用户信息,用户信息通过验证后,网站A 产生Cookie 信息并返回给浏览器。 (3)用户登陆网站A 成功后,可以正常请求网站A。 (4)用户未退出网站A 之前,在同一浏览器中,打开一个TAB 访问网站B。 (5)网站B 看到有人方式后,他会返回一些攻击性代码。 (6)浏览器在接受到这些攻击性代码后,促使用户不知情的情况下浏览器携带Cookie(包括 sessionId)信息,请求网站A。这种请求有可能更新密码,添加用户什么的操作。 从上面CSRF 攻击原理可以看出,要完成一次CSRF 攻击,需要被攻击者完成两个步骤: 1.登陆受信任网站A,并在本地生成COOKIE。 2.在不退出A 的情况下,访问危险网站B。 如果不满足以上两个条件中的一个,就不会受到CSRF 的攻击,以下情况可能会导致CSRF: 1.登录了一个网站后,打开一个tab 页面并访问另外的网站。 2.关闭浏览器了后,本地的Cookie 尚未过期,你上次的会话还没有已经结束。(事实上,关闭浏 览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了……) 解决办法:就是在表单中添加from.csrf_token。 59. 1.uWSGI 是一个Web 服务器,它实现了WSGI 协议、uwsgi、http 等协议。Nginx 中 HttpUwsgiModule 的作用是与uWSGI 服务器进行交换。WSGI 是一种Web 服务器网关接口。它是一个Web 服务器(如nginx,uWSGI 等服务器)与web 应用(如用Flask 框架写的程序)通信的一种 规范。 要注意WSGI / uwsgi / uWSGI 这三个概念的区分。 WSGI 是一种通信协议。 uwsgi 是一种线路协议而不是通信协议,在此常用于在uWSGI 服务器与其他网络服务器的数据通信。 uWSGI 是实现了uwsgi 和WSGI 两种协议的Web 服务器。 2.nginx 是一个开源的高性能的HTTP 服务器和反向代理: * 作为web 服务器,它处理静态文件和索引文件效果非常高; * 它的设计非常注重效率,最大支持5 万个并发连接,但只占用很少的内存空间; * 稳定性高,配置简洁; * 强大的反向代理和负载均衡功能,平衡集群中各个服务器的负载压力应用。 60. 1、优化算法时间 算法的时间复杂度对程序的执行效率影响最大,在Python 中可以通过选择合适的数据结构来优化 时间复杂度,如list 和set 查找某一个元素的时间复杂度分别是O(n)和O(1)。不同的场景有不同的 优化方式,总得来说,一般有分治,分支界限,贪心,动态规划等思想。 2、循环优化 每种编程语言都会强调需要优化循环。当使用Python 的时候,你可以依靠大量的技巧使得循环运行得更快。然而,开发者经常漏掉的一个方法是: 避免在一个循环中使用点操作。每一次你调用方法str.upper,Python 都会求该方法的值。然而, 如果你用一个变量代替求得的值,值就变成了已知的,Python 就可以更快地执行任务。优化循环的关键,是要减少Python 在循环内部执行的工作量,因为Python 原生的解释器在那种情况下,真的会减缓执行的速度。(注意:优化循环的方法有很多,这只是其中的一个。例如,许多程序员都会说,列表推导是在循环中提高执行速度的最好方式。这里的关键是,优化循环是程序取得更高的执行速度的更 好方式之一。) 3、函数选择 在循环的时候使用xrange 而不是range;使用xrange 可以节省大量的系统内存,因为xrange() 在序列中每次调用只产生一个整数元素。而range()將直接返回完整的元素列表,用于循环时会有不必要的开销。在python3 中xrange 不再存在,里面range 提供一个可以遍历任意长度的范围的iterator。 4、并行编程 因为GIL 的存在,Python 很难充分利用多核CPU 的优势。但是,可以通过内置的模块 multiprocessing 实现下面几种并行模式: * 多进程:对于CPU 密集型的程序,可以使用multiprocessing 的Process,Pool 等封装好的类, 通过多进程的方式实现并行计算。但是因为进程中的通信成本比较大,对于进程之间需要大量数据交互 的程序效率未必有大的提高。 * 多线程:对于IO 密集型的程序,multiprocessing.dummy 模块使用multiprocessing 的接口封 装threading,使得多线程编程也变得非常轻松(比如可以使用Pool 的map 接口,简洁高效)。 * 分布式:multiprocessing 中的Managers 类提供了可以在不同进程之共享数据的方式,可以在此基础上开发出分布式的程序。 不同的业务场景可以选择其中的一种或几种的组合实现程序性能的优化。 5、使用性能分析工具 除了上面在ipython 使用到的timeit 模块,还有cProfile。cProfile 的使用方式也非常简单: python-mcProfile filename.py,filename.py 是要运行程序的文件名,可以在标准输出中看到每一个函数被调用的次数和运行的时间,从而找到程序的性能瓶颈,然后可以有针对性地优化。 6、set 的用法 set 的union,intersection,difference 操作要比list 的迭代要快。因此如果涉及到求list 交集,并集或者差的问题可以转换为set 来操作。 7、PyPy PyPy 是用RPython(CPython 的子集)实现的Python,根据官网的基准测试数据,它比CPython 实现的Python 要快6 倍以上。快的原因是使用了Just-in-Time(JIT)编译器,即动态编译器,与静态编译器(如gcc,javac 等)不同,它是利用程序运行的过程的数据进行优化。由于历史原因,目前pypy中还保留着GIL,不过正在进行的STM 项目试图将PyPy 变成没有GIL 的Python。如果python 程序中含有C 扩展(非cffi 的方式),JIT 的优化效果会大打折扣,甚至比CPython 慢(比Numpy)。 所以在PyPy 中最好用纯Python 或使用cffi 扩展。 61.情景:用户发起request,并等待response 返回。在本些views 中,可能需要执行一段耗时的程序,那么用户就会等待很长时间,造成不好的用户体验,比如发送邮件、手机验证码等。 使用celery 后,情况就不一样了。解决:将耗时的程序放到celery 中执行。 将多个耗时的任务添加到队列queue 中,也就是用redis 实现broker 中间人,然后用多个worker 去监听队列里的任务去执行。  1.任务task:就是一个Python 函数。 2.队列queue:将需要执行的任务加入到队列中。 3.工人worker:在一个新进程中,负责执行队列中的任务。 4.代理人broker:负责调度,在布置环境中使用redis。 62.web 开发中,部署方式大致类似。简单来说,使用Nginx 主要是为了实现分流、转发、负载均衡,以及分担服务器的压力。Nginx 部署简单,内存消耗少,成本低。Nginx 既可以做正向代理,也可以做反向代理。 正向代理:请求经过代理服务器从局域网发出,然后到达互联网上的服务器。 特点:服务端并不知道真正的客户端是谁。 反向代理:请求从互联网发出,先进入代理服务器,再转发给局域网内的服务器。 特点:客户端并不知道真正的服务端是谁。 区别:正向代理的对象是客户端。反向代理的对象是服务端。 63. Nginx 相对Apache 的优点: 轻量级web 服务,比apache 占用更少的内存及资源; 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并发连接,而apache 则是阻塞型的,在高 并发下nginx 能保持低资源低消耗高性能; 配置简洁; 高度模块化的设计,编写模块相对简单; 社区活跃。 Apache 相对Nginx 的优点: rewrite ,比nginx 的rewrite 强大; 模块超多,基本想到的都可以找到; 少bug ,nginx 的bug 相对较多; 超稳定。 64. git clone 克隆指定仓库 git status 查看当前仓库状态 git diff 比较版本的区别 git log 查看git 操作日志 git reset 回溯历史版本 git add 将文件添加到暂存区 git commit 将文件提交到服务器 git checkout 切换到指定分支 git rm 删除指定文件 65.一般团购,秒杀,特价之类的活动,这样会使访问量激增,很多人抢购一个商品,作为活动商品, 库存肯定是很有限的。控制库存问题,数据库的事务功能是控制库存超卖的有效方式。 1.在秒杀的情况下,肯定不能如此频率的去读写数据库,严重影响性能问题,必须使用缓存,将需 要秒杀的商品放入缓存中,并使用锁来处理并发情况,先将商品数量增减(加锁、解析)后在进行其他 方面的处理,处理失败再将数据递增(加锁、解析),否则表示交易成功。 2.这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大了,要用到缓存。 3.首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。这个直 接可以使用加乐观锁的机制去解决高并发的问题。 66.Python 自带:urllib 第三方:requests 框架: Scrapy urllib.urlopen 可以接受一个Request 对象或者url,(在接受Request 对象时候,并以此可以来设置一个URL 的headers)。 scrapy 是封装起来的框架,它包含了下载器,解析器,日志及异常处理,基于多线程,twisted 的 方式处理,对于固定单个网站的爬取开发,有优势,但是对于多网站爬取,并发及分布式处理方面,不 够灵活,不便调整与括展。 request 是一个HTTP 库, 它只是用来,进行请求,对于HTTP 请求,他是一个强大的库,下载, 解析全部自己处理,灵活性更高,高并发与分布式部署也非常灵活,对于功能可以更好实现 Scrapy 优点: scrapy 是异步的; 采取可读性更强的xpath 代替正则; 强大的统计和log 系统; 同时在不同的url 上爬行; 支持shell 方式,方便独立调试; 写middleware,方便写一些统一的过滤器; 通过管道的方式存入数据库; Scrapy 缺点: 基于python 的爬虫框架,扩展性比较差; 基于twisted 框架,运行中的exception 是不会干掉reactor,并且异步框架出错后是不会停掉 其他任务的,数据出错后难以察觉。 67.IO 密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO 操作会进行IO 等待,造成不必要的时间浪费,而开启多线程能在线程A 等待时,自动切换到线程B,可以不浪费CPU 的资源,从而能提升程序执行效率)。在实际的数据采集过程中,既考虑网速和响应的问题,也需要考虑 自身机器的硬件情况,来设置多进程或多线程。 68.通过Headers 反爬虫: 从用户请求的Headers 反爬虫是最常见的反爬虫策略。很多网站都会对Headers 的User-Agent 进行检测,还有一部分网站会对Referer 进行检测(一些资源网站的防盗链就是检测Referer)。如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent 复制到爬虫的 Headers 中;或者将Referer 值修改为目标网站域名。对于检测Headers 的反爬虫,在爬虫中修改或者添加Headers 就能很好的绕过。 基于用户行为反爬虫: 还有一部分网站是通过检测用户行为,例如同一IP 短时间内多次访问同一页面,或者同一账户短时 间内多次进行相同操作。 大多数网站都是前一种情况,对于这种情况,使用IP 代理就可以解决。可以专门写一个爬虫,爬取 网上公开的代理ip,检测后全部保存起来。这样的代理ip 爬虫经常会用到,最好自己准备一个。有了 大量代理ip 后可以每请求几次更换一个ip,这在requests 或者urllib 中很容易做到,这样就能很容易的绕过第一种反爬虫。 对于第二种情况,可以在每次请求后随机间隔几秒再进行下一次请求。有些有逻辑漏洞的网站,可 以通过请求几次,退出登录,重新登录,继续请求来绕过同一账号短时间内不能多次进行相同请求的限 制。 动态页面的反爬虫: 上述的几种情况大多都是出现在静态页面,还有一部分网站,我们需要爬取的数据是通过ajax 请求 得到,或者通过JavaScript 生成的。首先用Fiddler 对网络请求进行分析。如果能够找到ajax 请求, 也能分析出具体的参数和响应的具体含义,我们就能采用上面的方法,直接利用requests 或者urllib 模拟ajax 请求,对响应的json 进行分析得到需要的数据。 能够直接模拟ajax 请求获取数据固然是极好的,但是有些网站把ajax 请求的所有参数全部加密了。 我们根本没办法构造自己所需要的数据的请求。这种情况下就用selenium+phantomJS,调用浏览器 内核,并利用phantomJS 执行js 来模拟人为操作以及触发页面中的js 脚本。从填写表单到点击按钮再 到滚动页面,全部都可以模拟,不考虑具体的请求和响应过程,只是完完整整的把人浏览页面获取数据 的过程模拟一遍。 用这套框架几乎能绕过大多数的反爬虫,因为它不是在伪装成浏览器来获取数据(上述的通过添加 Headers 一定程度上就是为了伪装成浏览器),它本身就是浏览器,phantomJS 就是一个没有界面的 浏览器,只是操控这个浏览器的不是人。利selenium+phantomJS 能干很多事情,例如识别点触式 (12306)或者滑动式的验证码,对页面表单进行暴力破解等。 69.Selenium 是一个Web 的自动化测试工具,可以根据我们的指令,让浏览器自动加载页面,获取需 要的数据,甚至页面截屏,或者判断网站上某些动作是否发生。Selenium 自己不带浏览器,不支持浏 览器的功能,它需要与第三方浏览器结合在一起才能使用。但是我们有时候需要让它内嵌在代码中运行, 所以我们可以用一个叫PhantomJS 的工具代替真实的浏览器。Selenium 库里有个叫WebDriver 的 API。WebDriver 有点儿像可以加载网站的浏览器,但是它也可以像BeautifulSoup 或者其他 Selector 对象一样用来查找页面元素,与页面上的元素进行交互(发送文本、点击等),以及执行其他动作来运行网络爬虫。 PhantomJS 是一个基于Webkit 的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器要高效。相比传统的Chrome或Firefox 浏览器等,资源消耗会更少。 如果我们把Selenium 和PhantomJS 结合在一起,就可以运行一个非常强大的网络爬虫了,这 个爬虫可以处理JavaScript、Cookie、headers,以及任何我们真实用户需要做的事情。 主程序退出后,selenium 不保证phantomJS 也成功退出,最好手动关闭phantomJS 进程。(有 可能会导致多个phantomJS 进程运行,占用内存)。 WebDriverWait 虽然可能会减少延时,但是目前存在bug(各种报错),这种情况可以采用sleep。 phantomJS 爬数据比较慢,可以选择多线程。如果运行的时候发现有的可以运行,有的不能,可 以尝试将phantomJS 改成Chrome。 70.透明代理的意思是客户端根本不需要知道有代理服务器的存在,但是它传送的仍然是真实的IP。你 要想隐藏的话,不要用这个。 普通匿名代理能隐藏客户机的真实IP,但会改变我们的请求信息,服务器端有可能会认为我们使用 了代理。不过使用此种代理时,虽然被访问的网站不能知道你的ip 地址,但仍然可以知道你在使用代 理,当然某些能够侦测ip 的网页仍然可以查到你的ip。 高匿名代理不改变客户机的请求,这样在服务器看来就像有个真正的客户浏览器在访问它,这时客 户的真实IP 是隐藏的,服务器端不会认为我们使用了代理。 设置代理有以下两个好处: 1,让服务器以为不是同一个客户端在请求 2,防止我们的真实地址被泄露,防止被追究 71. a)response.text 返回的是Unicode 型数据; a)response.content 返回的是bytes 类型,也就是二进制数据; b)获取文本使用,response.text; b)获取图片,文件,使用response.content; c)response.text 类型:str 解码类型: 根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码 如何修改编码方式:response.encoding=”gbk” c)response.content 类型:bytes 解码类型: 没有指定 如何修改编码方式:response.content.decode(“utf8”) 72.Robots 协议:网站通过Robots 协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。 73.使用生成器读取文件。每次读取65536 行,一共进行1500 次,当读取不到内容时关闭文件。每 次读取,最终要得到100 个频数最高的词。每500 次,进行一次合并和统计,得到最多50000 个 词,对这50000 个词提取其中频数最高的100 个词。最终对最多300 个筛选出来的词,进行合并 和统计,提取最终频数最高的100 个词。 筛选出100 个高频词的步骤: 1、统计每个词出现的次数。维护一个Key 为词,Value 为该词出现次数的hash 表。每次读取 一个词,如果该字串不在hash 表中,那么加入该词,并且将Value 值设为1;如果该字串在hash 表中,那么将该字串的计数加一即可。 2、据每个词的引用次数进行排序。冒泡、快排等等都可以。 74. ``` select * from B inner join on B.name=A.name ``` 75. ``` re.sub('b+','b',a) ``` 76. ``` lxml ``` 77. ``` len key-name ``` 78.创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候 先查看数据是否已经在已访问的列表中,若已存在就直接跳过。 79.int、str、float、list、set、dict、tuple 80. ``` ^[A-Za-z]|_.*\d$ ``` 81.Python3不对,在Python3 中整数和字符不可以使用运算符做比较,在Python2 中可以。 82.(1) Scrapy 框架:很强