Python面试题

本书是一篇关于python的基础知识讲解书籍。主要讲解Python语言特性,操作系统,数据库,网络,Unix,数据结构,编程题 ,工厂方法,版权说明
展开查看详情

1. 目 录 致谢 Python语言特性 操作系统 数据库 网络 Unix 数据结构 编程题 工厂方法 版权说明 本文档使用 书栈(BookStack.CN) 构建 - 1 -

2.致谢 致谢 当前文档 《Python面试题》 由 进击的皇虫 使用 书栈(BookStack.CN) 进行构建,生成于 2018-03-24。 书栈(BookStack.CN) 仅提供文档编写、整理、归类等功能,以及对文档内容的生成和导出工 具。 文档内容由网友们编写和整理,书栈(BookStack.CN) 难以确认文档内容知识点是否错漏。如 果您在阅读文档获取知识的时候,发现文档内容有不恰当的地方,请向我们反馈,让我们共同携手, 将知识准确、高效且有效地传递给每一个人。 同时,如果您在日常生活、工作和学习中遇到有价值有营养的知识文档,欢迎分享到 书栈 (BookStack.CN) ,为知识的传承献上您的一份力量! 如果当前文档生成时间太久,请到 书栈(BookStack.CN) 获取最新的文档,以跟上知识更新换 代的步伐。 文档地址:http://www.bookstack.cn/books/interview_python 书栈官网:http://www.bookstack.cn 书栈开源:https://github.com/TruthHun 分享,让知识传承更久远! 感谢知识的创造者,感谢知识的分享者,也感谢每一位阅读到此处的 读者,因为我们都将成为知识的传承者。 本文档使用 书栈(BookStack.CN) 构建 - 2 -

3.Python语言特性 Python语言特性 1 Python的函数参数传递 看两个例子: 1. a = 1 2. def fun(a): 3. a = 2 4. fun(a) 5. print a # 1 1. a = [] 2. def fun(a): 3. a.append(1) 4. fun(a) 5. print a # [1] 所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。 通过 id 来看引用 a 的内存地址可以比较理解: 1. a = 1 2. def fun(a): 3. print "func_in",id(a) # func_in 41322472 4. a = 2 5. print "re-point",id(a), id(2) # re-point 41322448 41322448 6. print "func_out",id(a), id(1) # func_out 41322472 41322472 7. fun(a) 8. print a # 1 注:具体的值在不同电脑上运行时可能不同。 可以看到,在执行完 a = 2 之后, a 引用中保存的值,即内存地址发生变化,由原来 1 对象的 所在的地址变成了 2 这个实体对象的内存地址。 而第2个例子 a 引用保存的内存值就不会发生变化: 1. a = [] 2. def fun(a): 3. print "func_in",id(a) # func_in 53629256 本文档使用 书栈(BookStack.CN) 构建 - 3 -

4.Python语言特性 4. a.append(1) 5. print "func_out",id(a) # func_out 53629256 6. fun(a) 7. print a # [1] 这里记住的是类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更 改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而 list, dict, set 等则是可以修改的对象。(这就是这个问题的重点) 当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系 了.所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉. 而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在 内存里进行修改. 如果还不明白的话,这里有更好的解释: http://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by- reference 2 Python中的元类(metaclass) 这个非常的不常用,但是像ORM这种复杂的结构还是会需要的,详情请 看:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python 3 @staticmethod和@classmethod Python其实有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法,如下: 1. def foo(x): 2. print "executing foo(%s)"%(x) 3. 4. class A(object): 5. def foo(self,x): 6. print "executing foo(%s,%s)"%(self,x) 7. 8. @classmethod 9. def class_foo(cls,x): 10. print "executing class_foo(%s,%s)"%(cls,x) 11. 12. @staticmethod 13. def static_foo(x): 14. print "executing static_foo(%s)"%x 15. 16. a=A() 本文档使用 书栈(BookStack.CN) 构建 - 4 -

5.Python语言特性 这里先理解下函数参数里面的self和cls.这个self和cls是对类或者实例的绑定,对于一般的函数来 说我们可以这么调用 foo(x) ,这个函数就是最常用的,它的工作跟任何东西(类,实例)无关.对于实 例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是 foo(self, x) ,为什么要这么 做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样 的 a.foo(x) (其实是 foo(a, x) ).类方法一样,只不过它传递的是类而不是实 例, A.class_foo(x) .注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不 要改的好. 对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使 用 a.static_foo(x) 或者 A.static_foo(x) 来调用. \ 实例方法 类方法 静态方法 a = A() a.foo(x) a.class_foo(x) a.static_foo(x) A 不可用 A.class_foo(x) A.static_foo(x) 更多关于这个问题: 1. http://stackoverflow.com/questions/136097/what-is-the-difference- between-staticmethod-and-classmethod-in-python 2. https://realpython.com/blog/python/instance-class-and-static-methods- demystified/ 4 类变量和实例变量 类变量: ​ 是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中,num_of_instance 就是类变 量,用于跟踪存在着多少个Test 的实例。 实例变量: 实例化之后,每个实例单独拥有的变量。 1. class Test(object): 2. num_of_instance = 0 3. def __init__(self, name): 4. self.name = name 5. Test.num_of_instance += 1 6. 7. if __name__ == '__main__': 8. print Test.num_of_instance # 0 9. t1 = Test('jack') 10. print Test.num_of_instance # 1 11. t2 = Test('lucy') 12. print t1.name , t1.num_of_instance # jack 2 本文档使用 书栈(BookStack.CN) 构建 - 5 -

6.Python语言特性 13. print t2.name , t2.num_of_instance # lucy 2 补充的例子 1. class Person: 2. name="aaa" 3. 4. p1=Person() 5. p2=Person() 6. p1.name="bbb" 7. print p1.name # bbb 8. print p2.name # aaa 9. print Person.name # aaa 这里 p1.name="bbb" 是实例调用了类变量,这其实和上面第一个问题一样,就是函数传参的问 题, p1.name 一开始是指向的类变量 name="aaa" ,但是在实例的作用域里把类变量的引用改变了,就 变成了一个实例变量,self.name不再引用Person的类变量name了. 可以看看下面的例子: 1. class Person: 2. name=[] 3. 4. p1=Person() 5. p2=Person() 6. p1.name.append(1) 7. print p1.name # [1] 8. print p2.name # [1] 9. print Person.name # [1] 参考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions- in-one-line-except-block 5 Python自省 这个也是python彪悍的特性. 自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对 象的类型.比如type(),dir(),getattr(),hasattr(),isinstance(). 1. a = [1,2,3] 2. b = {'a':1,'b':2,'c':3} 3. c = True 4. print type(a),type(b),type(c) # <type 'list'> <type 'dict'> <type 'bool'> 本文档使用 书栈(BookStack.CN) 构建 - 6 -

7.Python语言特性 5. print isinstance(a,list) # True 6 字典推导式 可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的: 1. d = {key: value for (key, value) in iterable} 7 Python中单下划线和双下划线 1. >>> class MyClass(): 2. ... def __init__(self): 3. ... self.__superprivate = "Hello" 4. ... self._semiprivate = ", world!" 5. ... 6. >>> mc = MyClass() 7. >>> print mc.__superprivate 8. Traceback (most recent call last): 9. File "<stdin>", line 1, in <module> 10. AttributeError: myClass instance has no attribute '__superprivate' 11. >>> print mc._semiprivate 12. , world! 13. >>> print mc.__dict__ 14. {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'} __foo__ :一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例 如 __init__() , __del__() , __call__() 这些特殊方法 _foo :一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.不能用from module import * 导入,其他方面和公有一样访问; __foo :这个有真正的意义:解析器用 _classname__foo 来代替这个名字,以区别和其他类相同的命 名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问. 详情见:http://stackoverflow.com/questions/1301346/the-meaning-of-a-single- and-a-double-underscore-before-an-object-name-in-python 或者: http://www.zhihu.com/question/19754941 8 字符串格式化:%和.format 本文档使用 书栈(BookStack.CN) 构建 - 7 -

8.Python语言特性 .format在许多方面看起来更便利.对于 % 最烦人的是它无法同时传递一个变量和元组.你可能会想 下面的代码不会有什么问题: 1. "hi there %s" % name 但是,如果name恰好是(1,2,3),它将会抛出一个TypeError异常.为了保证它总是正确的,你必须这 样做: 1. "hi there %s" % (name,) # 提供一个单元素的数组而不是一个参数 但是有点丑..format就没有这些问题.你给的第二个问题也是这样,.format好看多了. 你为什么不用它? 不知道它(在读这个之前) 为了和Python2.5兼容(譬如logging库建议使用 % (issue #4)) http://stackoverflow.com/questions/5082452/python-string-formatting-vs- format 9 迭代器和生成器 这个是stackoverflow里python排名第一的问题,值得一看: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do- in-python 这是中文版: http://taizilongxu.gitbooks.io/stackoverflow-about- python/content/1/README.html 这里有个关于生成器的创建问题面试官有考: 问: 将列表生成式中[]改成() 之后数据结构是否改变? 答案:是,从列表变为生成器 1. >>> L = [x*x for x in range(10)] 2. >>> L 3. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 4. >>> g = (x*x for x in range(10)) 5. >>> g 6. <generator object <genexpr> at 0x0000028F8B774200> 通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创 建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后 本文档使用 书栈(BookStack.CN) 构建 - 8 -

9.Python语言特性 面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表(节省大量内存空间)。在 Python中,我们可以采用生成器:边循环,边计算的机制—>generator 10 *args and **kwargs 用 *args 和 **kwargs 只是为了方便并没有强制使用它们. 当你不确定你的函数里将要传递多少参数时你可以用 *args .例如,它可以传递任意数量的参数: 1. >>> def print_everything(*args): 2. for count, thing in enumerate(args): 3. ... print '{0}. {1}'.format(count, thing) 4. ... 5. >>> print_everything('apple', 'banana', 'cabbage') 6. 0. apple 7. 1. banana 8. 2. cabbage 相似的, **kwargs 允许你使用没有事先定义的参数名: 1. >>> def table_things(**kwargs): 2. ... for name, value in kwargs.items(): 3. ... print '{0} = {1}'.format(name, value) 4. ... 5. >>> table_things(apple = 'fruit', cabbage = 'vegetable') 6. cabbage = vegetable 7. apple = fruit 你也可以混着用.命名参数首先获得参数值然后所有的其他参数都传递给 *args 和 **kwargs .命名 参数在列表的最前端.例如: 1. def table_things(titlestring, **kwargs) *args 和 **kwargs 可以同时在函数的定义中,但是 *args 必须在 **kwargs 前面. 当调用函数时你也可以用 * 和 ** 语法.例如: 1. >>> def print_three_things(a, b, c): 2. ... print 'a = {0}, b = {1}, c = {2}'.format(a,b,c) 3. ... 4. >>> mylist = ['aardvark', 'baboon', 'cat'] 5. >>> print_three_things(*mylist) 6. 本文档使用 书栈(BookStack.CN) 构建 - 9 -

10.Python语言特性 7. a = aardvark, b = baboon, c = cat 就像你看到的一样,它可以传递列表(或者元组)的每一项并把它们解包.注意必须与它们在函数里的参 数相吻合.当然,你也可以在函数定义或者函数调用时用*. http://stackoverflow.com/questions/3394835/args-and-kwargs 11 面向切面编程AOP和装饰器 这个AOP一听起来有点懵,同学面阿里的时候就被问懵了… 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测 试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与 函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外 的功能。 这个问题比较大,推荐: http://stackoverflow.com/questions/739654/how-can-i- make-a-chain-of-function-decorators-in-python 中文: http://taizilongxu.gitbooks.io/stackoverflow-about- python/content/3/README.html 12 鸭子类型 “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。 比如在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相 同的方法,我们把它们当作文件使用。 又比如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参 数可以是list/tuple/dict/字符串/生成器等. 鸭子类型在动态语言中经常使用,非常灵活,使得python不想java那样专门去弄一大堆的设计模 式。 13 Python中重载 引自知乎:http://www.zhihu.com/question/20053359 函数重载主要是为了解决两个问题。 本文档使用 书栈(BookStack.CN) 构建 - 10 -

11.Python语言特性 1. 可变参数类型。 2. 可变参数个数。 另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相 同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个 名字不同的函数。 好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需 要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。 那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省 参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的 参数终归是需要用的。 好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。 14 新式类和旧式类 这个面试官问了,我说了老半天,不知道他问的真正意图是什么. stackoverflow 这篇文章很好的介绍了新式类的特性: http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html 新式类很早在2.2就出现了,所以旧式类完全是兼容的问题,Python3里的类全部都是新式类.这里有 一个MRO问题可以了解下(新式类是广度优先,旧式类是深度优先),里讲的也很多. 一个旧式类的深度优先的例子 1. class A(): 2. def foo1(self): 3. print "A" 4. class B(A): 5. def foo2(self): 6. pass 7. class C(A): 8. def foo1(self): 9. print "C" 10. class D(B, C): 11. pass 12. 13. d = D() 14. d.foo1() 15. 本文档使用 书栈(BookStack.CN) 构建 - 11 -

12.Python语言特性 16. # A 按照经典类的查找顺序 从左到右深度优先 的规则,在访问 d.foo1() 的时候,D这个类是没有的..那么 往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从 而导致C重写的foo1()被绕过 15 __new__ 和 __init__ 的区别 这个 __new__ 确实很少见到,先做了解吧. 1. __new__ 是一个静态方法,而 __init__ 是一个实例方法. 2. __new__ 方法会返回一个创建的实例,而 __init__ 什么都不返回. 3. 只有在 __new__ 返回一个cls的实例时后面的 __init__ 才能被调用. 4. 当创建一个新实例时调用 __new__ ,初始化一个实例时用 __init__ . stackoverflow ps: __metaclass__ 是创建类时起作用.所以我们可以分别使 用 __metaclass__ , __new__ 和 __init__ 来分别在类创建,实例创建和实例初始化的时候做一些小 手脚. 16 单例模式 ​ 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只 有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个, 单例模式是最好的解决方案。 __new__() 在 __init__() 之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模 式。单例模式是指创建唯一对象,单例模式设计的类只能实例 这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的. 1 使用 __new__ 方法 1. class Singleton(object): 2. def __new__(cls, *args, **kw): 3. if not hasattr(cls, '_instance'): 4. orig = super(Singleton, cls) 5. cls._instance = orig.__new__(cls, *args, **kw) 6. return cls._instance 7. 8. class MyClass(Singleton): 9. a = 1 本文档使用 书栈(BookStack.CN) 构建 - 12 -

13.Python语言特性 2 共享属性 创建实例时把所有实例的 __dict__ 指向同一个字典,这样它们具有相同的属性和方法. 1. 2. class Borg(object): 3. _state = {} 4. def __new__(cls, *args, **kw): 5. ob = super(Borg, cls).__new__(cls, *args, **kw) 6. ob.__dict__ = cls._state 7. return ob 8. 9. class MyClass2(Borg): 10. a = 1 3 装饰器版本 1. def singleton(cls, *args, **kw): 2. instances = {} 3. def getinstance(): 4. if cls not in instances: 5. instances[cls] = cls(*args, **kw) 6. return instances[cls] 7. return getinstance 8. 9. @singleton 10. class MyClass: 11. ... 4 import方法 作为python的模块是天然的单例模式 1. # mysingleton.py 2. class My_Singleton(object): 3. def foo(self): 4. pass 5. 6. my_singleton = My_Singleton() 7. 8. # to use 9. from mysingleton import my_singleton 10. 11. my_singleton.foo() 本文档使用 书栈(BookStack.CN) 构建 - 13 -

14.Python语言特性 单例模式伯乐在线详细解释 17 Python中的作用域 Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。 当 Python 遇到一个变量的话他会按照这样的顺序进行搜索: 本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域 (Global)→内置作用域(Built-in) 18 GIL线程全局锁 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务,python的多线程起到作 用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。 见Python 最难的问题 解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能). 19 协程 知乎被问到了,呵呵哒,跪了 简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换 时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态. Python里最常见的yield就是协程的思想!可以查看第九个问题. 20 闭包 闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码 的可重复使用性。 当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满 足以下几点: 1. 必须有一个内嵌函数 2. 内嵌函数必须引用外部函数中的变量 本文档使用 书栈(BookStack.CN) 构建 - 14 -

15.Python语言特性 3. 外部函数的返回值必须是内嵌函数 感觉闭包还是有难度的,几句话是说不明白的,还是查查相关资料. 重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被 销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上. 闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样. 21 lambda函数 其实就是一个匿名函数,为什么叫lambda?因为和后面的函数式编程有关. 推荐: 知乎 22 Python函数式编程 这个需要适当的了解一下吧,毕竟函数式编程在Python中也做了引用. 推荐: 酷壳 python中函数式编程支持: filter 函数的功能相当于过滤器。调用一个布尔函数 bool_func 来迭代遍历每个seq中的元素;返 回一个使 bool_seq 返回值为true的元素的序列。 1. >>>a = [1,2,3,4,5,6,7] 2. >>>b = filter(lambda x: x > 5, a) 3. >>>print b 4. >>>[6,7] map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2: 1. >>> a = map(lambda x:x*2,[1,2,3]) 2. >>> list(a) 3. [2, 4, 6] reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘: 1. >>> reduce(lambda x,y:x*y,range(1,4)) 2. 6 本文档使用 书栈(BookStack.CN) 构建 - 15 -

16.Python语言特性 23 Python里的拷贝 引用和copy(),deepcopy()的区别 1. import copy 2. a = [1, 2, 3, 4, ['a', 'b']] #原始对象 3. 4. b = a #赋值,传对象的引用 5. c = copy.copy(a) #对象拷贝,浅拷贝 6. d = copy.deepcopy(a) #对象拷贝,深拷贝 7. 8. a.append(5) #修改对象a 9. a[4].append('c') #修改对象a中的['a', 'b']数组对象 10. 11. print 'a = ', a 12. print 'b = ', b 13. print 'c = ', c 14. print 'd = ', d 15. 16. 输出结果: 17. a = [1, 2, 3, 4, ['a', 'b', 'c'], 5] 18. b = [1, 2, 3, 4, ['a', 'b', 'c'], 5] 19. c = [1, 2, 3, 4, ['a', 'b', 'c']] 20. d = [1, 2, 3, 4, ['a', 'b']] 24 Python垃圾回收机制 Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础 上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回 收”(generation collection)以空间换时间的方法提高垃圾回收效率。 1 引用计数 PyObject是每个对象必有的内容,其中 ob_refcnt 就是做为引用计数。当一个对象有新的引用时, 它的 ob_refcnt 就会增加,当引用它的对象被删除,它的 ob_refcnt 就会减少.引用计数为0时,该 对象生命就结束了。 优点: 1. 简单 2. 实时性 缺点: 本文档使用 书栈(BookStack.CN) 构建 - 16 -

17.Python语言特性 1. 维护引用计数消耗资源 2. 循环引用 2 标记-清除机制 基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节 点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标 记的对象释放。 3 分代技术 分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为 一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来 度量。 Python默认定义了三代对象集合,索引数越大,对象存活时间越长。 举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新 分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而 对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效 率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当 然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。 25 Python的List 推荐: http://www.jianshu.com/p/J4U6rR 26 Python的is is是对比地址,==是对比值 27 read,readline和readlines read 读取整个文件 readline 读取下一行,使用生成器方法 readlines 读取整个文件到一个迭代器以供我们遍历 28 Python2和3的区别 本文档使用 书栈(BookStack.CN) 构建 - 17 -

18.Python语言特性 推荐:Python 2.7.x 与 Python 3.x 的主要差异 29 super init super() lets you avoid referring to the base class explicitly, which can be nice. But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven’t already. Note that the syntax changed in Python 3.0: you can just say super(). __init__ () instead of super(ChildB, self). __init__ () which IMO is quite a bit nicer. http://stackoverflow.com/questions/576169/understanding-python-super-with- init-methods Python2.7中的super方法浅见 30 range and xrange 都在循环时使用,xrange内存性能更好。 for i in range(0, 20): for i in xrange(0, 20): What is the difference between range and xrange functions in Python 2.X? range creates a list, so if you do range(1, 10000000) it creates a list in memory with 9999999 elements. xrange is a sequence object that evaluates lazily. http://stackoverflow.com/questions/94935/what-is-the-difference-between- range-and-xrange-functions-in-python-2-x 本文档使用 书栈(BookStack.CN) 构建 - 18 -

19.操作系统 操作系统 1 select,poll和epoll 2 调度算法 3 死锁 4 程序编译与链接 1 预处理 2 编译 3 汇编 4 链接 5 静态链接和动态链接 6 虚拟内存技术 7 分页和分段 分页与分段的主要区别 8 页面置换算法 9 边沿触发和水平触发 1 select,poll和epoll 其实所有的I/O都是轮询的方法,只不过实现的层面不同罢了. 这个问题可能有点深入了,但相信能回答出这个问题是对I/O多路复用有很好的了解了.其中tornado 使用的就是epoll的. selec,poll和epoll区别总结 基本上select有3个缺点: 1. 连接数受限 2. 查找配对速度慢 3. 数据由内核拷贝到用户态 poll改善了第一个缺点 epoll改了三个缺点. 关于epoll的: http://www.cnblogs.com/my_life/articles/3968782.html 2 调度算法 1. 先来先服务(FCFS, First Come First Serve) 本文档使用 书栈(BookStack.CN) 构建 - 19 -

20.操作系统 2. 短作业优先(SJF, Shortest Job First) 3. 最高优先权调度(Priority Scheduling) 4. 时间片轮转(RR, Round Robin) 5. 多级反馈队列调度(multilevel feedback queue scheduling) 常见的调度算法总结:http://www.jianshu.com/p/6edf8174c1eb 实时调度算法: 1. 最早截至时间优先 EDF 2. 最低松弛度优先 LLF 3 死锁 原因: 1. 竞争资源 2. 程序推进顺序不当 必要条件: 1. 互斥条件 2. 请求和保持条件 3. 不剥夺条件 4. 环路等待条件 处理死锁基本方法: 1. 预防死锁(摒弃除1以外的条件) 2. 避免死锁(银行家算法) 3. 检测死锁(资源分配图) 4. 解除死锁 i. 剥夺资源 ii. 撤销进程 死锁概念处理策略详细介绍:https://wizardforcel.gitbooks.io/wangdaokaoyan- os/content/10.html 4 程序编译与链接 推荐: http://www.ruanyifeng.com/blog/2014/11/compiler.html Bulid过程可以分解为4个步骤:预处理(Prepressing), 编译(Compilation)、汇编 本文档使用 书栈(BookStack.CN) 构建 - 20 -

21.操作系统 (Assembly)、链接(Linking) 以c语言为例: 1 预处理 预编译过程主要处理那些源文件中的以“#”开始的预编译指令,主要处理规则有: 1. 将所有的“#define”删除,并展开所用的宏定义 2. 处理所有条件预编译指令,比如“#if”、“#ifdef”、 “#elif”、“#endif” 3. 处理“#include”预编译指令,将被包含的文件插入到该编译指令的位置,注:此过程是递归进 行的 4. 删除所有注释 5. 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错 误或警告时可显示行号 6. 保留所有的#pragma编译器指令。 2 编译 编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇 编代码文件。这个过程是整个程序构建的核心部分。 3 汇编 汇编器是将汇编代码转化成机器可以执行的指令,每一条汇编语句几乎都是一条机器指令。经过编 译、链接、汇编输出的文件成为目标文件(Object File) 4 链接 链接的主要内容就是把各个模块之间相互引用的部分处理好,使各个模块可以正确的拼接。 链接的主要过程包块 地址和空间的分配(Address and Storage Allocation)、符号决议 (Symbol Resolution)和重定位(Relocation)等步骤。 5 静态链接和动态链接 静态链接方法:静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来 静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库 动态链接方法:使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码 时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另 外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较 本文档使用 书栈(BookStack.CN) 构建 - 21 -

22.操作系统 短,但运行期间的性能比不上静态链接的程序 6 虚拟内存技术 虚拟存储器是指具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储系统. 7 分页和分段 分页: 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个 物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。 分段: 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储 分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。 分页与分段的主要区别 1. 页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系 统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地 实现共享,满足用户的需要. 2. 页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度 却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划 分. 3. 分页的作业地址空间是一维的.分段的地址空间是二维的. 8 页面置换算法 1. 最佳置换算法OPT:不可能实现 2. 先进先出FIFO 3. 最近最久未使用算法LRU:最近一段时间里最久没有使用过的页面予以置换. 4. clock算法 9 边沿触发和水平触发 边缘触发是指每当状态变化时发生一个 io 事件,条件触发是只要满足条件就发生一个 io 事件 本文档使用 书栈(BookStack.CN) 构建 - 22 -

23.数据库 数据库 1 事务 2 数据库索引 3 Redis原理 Redis是什么? Redis数据库 Redis缺点 4 乐观锁和悲观锁 5 MVCC MySQL 的innodb引擎是如何实现MVCC的 6 MyISAM和InnoDB 1 事务 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全 地执行,要么完全地不执行。 彻底理解数据库事务: http://www.hollischuang.com/archives/898 2 数据库索引 推荐: http://tech.meituan.com/mysql-index.html MySQL索引背后的数据结构及算法原理 聚集索引,非聚集索引,B-Tree,B+Tree,最左前缀原理 3 Redis原理 Redis是什么? 1. 是一个完全开源免费的key-value内存数据库 2. 通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构 strings、map、 list、sets、 sorted sets Redis数据库 ​ 通常局限点来说,Redis也以消息队列的形式存在,作为内嵌的List存在,满足实时的高并发需求。在使用缓存的时候,redis比 memcached具有更多的优势,并且支持更多的数据类型,把redis当作一个中间存储系统,用来处理高并发的数据库操作 本文档使用 书栈(BookStack.CN) 构建 - 23 -

24.数据库 速度快:使用标准C写,所有数据都在内存中完成,读写速度分别达到10万/20万 持久化:对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上,主要有两种策 略,一是根据时间,更新次数的快照(save 300 10 )二是基于语句追加方式(Append-only file,aof) 自动操作:对不同数据类型的操作都是自动的,很安全 快速的主—从复制,官方提供了一个数据,Slave在21秒即完成了对Amazon网站10G key set 的复制。 Sharding技术: 很容易将数据分布到多个Redis实例中,数据库的扩展是个永恒的话题,在关 系型数据库中,主要是以添加硬件、以分区为主要技术形式的纵向扩展解决了很多的应用场景, 但随着web2.0、移动互联网、云计算等应用的兴起,这种扩展模式已经不太适合了,所以近年 来,像采用主从配置、数据库复制形式的,Sharding这种技术把负载分布到多个特理节点上去 的横向扩展方式用处越来越多。 Redis缺点 是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要 局限在较小数据量的高性能操作和运算上。 Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运 维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。 4 乐观锁和悲观锁 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 乐观锁与悲观锁的具体区别: http://www.cnblogs.com/Bob-FD/p/3352216.html 5 MVCC ​ 全称是Multi-Version Concurrent Control,即多版本并发控制,在MVCC协议下,每个读操作会看到一个一致性的 snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时 间点,不同的事务看到的数据是不同的。 MySQL的innodb引擎是如何实现MVCC的 innodb会为每一行添加两个字段,分别表示该行创建的版本和删除的版本,填入的是事务的版本号, 这个版本号随着事务的创建不断递增。在repeated read的隔离级别(事务的隔离级别请看这篇文 章)下,具体各种数据库操作的实现: select:满足以下两个条件innodb会返回该行数据: 该行的创建版本号小于等于当前版本号,用于保证在select操作之前所有的操作已经执行落 本文档使用 书栈(BookStack.CN) 构建 - 24 -

25.数据库 地。 该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务 将该行删除了。 insert:将新插入的行的创建版本号设置为当前系统的版本号。 delete:将要删除的行的删除版本号设置为当前系统的版本号。 update:不执行原地update,而是转换成insert + delete。将旧行的删除版本号设置为当 前版本号,并将新行insert同时设置创建版本号为当前版本号。 其中,写操作(insert、delete和update)执行时,需要将系统版本号递增。 ​ 由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理 工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。 通过MVCC很好的实现了事务的隔离性,可以达到repeated read级别,要实现serializable还必 须加锁。 参考:MVCC浅析 6 MyISAM和InnoDB MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要 update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另 外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。 InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它 支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事 务。 mysql 数据库引擎: http://www.cnblogs.com/0201zcr/p/5296843.html MySQL存储引擎--MyISAM与InnoDB区别: https://segmentfault.com/a/1190000008227211 本文档使用 书栈(BookStack.CN) 构建 - 25 -

26.网络 网络 1 三次握手 2 四次挥手 3 ARP协议 4 urllib和urllib2的区别 5 Post和Get 6 Cookie和Session 7 apache和nginx的区别 8 网站用户密码保存 9 HTTP和HTTPS 10 XSRF和XSS 11 幂等 Idempotence 12 RESTful架构(SOAP,RPC) 13 SOAP 14 RPC 15 CGI和WSGI 16 中间人攻击 17 c10k问题 18 socket 19 浏览器缓存 20 HTTP1.0和HTTP1.1 21 Ajax 1 三次握手 1. 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分。客户端把这 段连接的序号设定为随机数 A。 2. 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身 又有一个随机序号 B。 3. 最后,客户端再发送一个ACK。当服务端受到这个ACK的时候,就完成了三路握手,并进入了连接 创建状态。此时包序号被设定为收到的确认号 A+1,而响应则为 B+1。 2 四次挥手 注意: 中断连接端可以是客户端,也可以是服务器端. 下面仅以客户端断开连接举例, 反之亦然. 1. 客户端发送一个数据分段, 其中的 FIN 标记设置为1. 客户端进入 FIN-WAIT 状态. 该状态 下客户端只接收数据, 不再发送数据. 2. 服务器接收到带有 FIN = 1 的数据分段, 发送带有 ACK = 1 的剩余数据分段, 确认收到客 本文档使用 书栈(BookStack.CN) 构建 - 26 -

27.网络 户端发来的 FIN 信息. 3. 服务器等到所有数据传输结束, 向客户端发送一个带有 FIN = 1 的数据分段, 并进入 CLOSE-WAIT 状态, 等待客户端发来带有 ACK = 1 的确认报文. 4. 客户端收到服务器发来带有 FIN = 1 的报文, 返回 ACK = 1 的报文确认, 为了防止服务器 端未收到需要重发, 进入 TIME-WAIT 状态. 服务器接收到报文后关闭连接. 客户端等待 2MSL 后未收到回复, 则认为服务器成功关闭, 客户端关闭连接. 图解: http://blog.csdn.net/whuslei/article/details/6667471 3 ARP协议 地址解析协议(Address Resolution Protocol),其基本功能为透过目标设备的IP地址,查询目 标的MAC地址,以保证通信的顺利进行。它是IPv4网络层必不可少的协议,不过在IPv6中已不再适 用,并被邻居发现协议(NDP)所替代。 4 urllib和urllib2的区别 这个面试官确实问过,当时答的urllib2可以Post而urllib不可以. 1. urllib提供urlencode方法用来GET查询字符串的产生,而urllib2没有。这是为何urllib常 和urllib2一起使用的原因。 2. urllib2可以接受一个Request类的实例来设置URL请求的headers,urllib仅可以接受URL。 这意味着,你不可以伪装你的User Agent字符串等。 5 Post和Get GET和POST有什么区别?及为什么网上的多数答案都是错的 知乎回答 get: RFC 2616 - Hypertext Transfer Protocol — HTTP/1.1 post: RFC 2616 - Hypertext Transfer Protocol — HTTP/1.1 6 Cookie和Session Cookie Session 储存位置 客户端 服务器端 目的 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 跟踪会话 安全性 不安全 安全 本文档使用 书栈(BookStack.CN) 构建 - 27 -

28.网络 session技术是要使用到cookie的,之所以出现session技术,主要是为了安全。 7 apache和nginx的区别 nginx 相对 apache 的优点: 轻量级,同样起web 服务,比apache 占用更少的内存及资源 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并发连接,而apache 则是阻塞型的, 在高并发下nginx 能保持低资源低消耗高性能 配置简洁 高度模块化的设计,编写模块相对简单 社区活跃 apache 相对nginx 的优点: rewrite ,比nginx 的rewrite 强大 模块超多,基本想到的都可以找到 少bug ,nginx 的bug 相对较多 超稳定 8 网站用户密码保存 1. 明文保存 2. 明文hash后保存,如md5 3. MD5+Salt方式,这个salt可以随机 4. 知乎使用了Bcrypy(好像)加密 9 HTTP和HTTPS 状态码 定义 1xx 报告 接收到请求,继续进程 2xx 成功 步骤成功接收,被理解,并被接受 3xx 重定向 为了完成请求,必须采取进一步措施 4xx 客户端出错 请求包括错的顺序或不能完成 5xx 服务器出错 服务器无法完成显然有效的请求 403: Forbidden 404: Not Found 本文档使用 书栈(BookStack.CN) 构建 - 28 -

29.网络 HTTPS握手,对称加密,非对称加密,TLS/SSL,RSA 10 XSRF和XSS CSRF(Cross-site request forgery)跨站请求伪造 XSS(Cross Site Scripting)跨站脚本攻击 CSRF重点在请求,XSS重点在脚本 11 幂等 Idempotence HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。(注意是副作用) GET http://www.bank.com/account/123456 ,不会改变资源的状态,不论调用一次还是N次都没有副作 用。请注意,这里强调的是一次和N次具有相同的副作用,而不是每次GET的结果相同。 GET http://www.news.com/latest-news 这个HTTP请求可能会每次得到不同的结果,但它本身并没有产生任何 副作用,因而是满足幂等性的。 DELETE方法用于删除资源,有副作用,但它应该满足幂等性。比如: DELETE http://www.forum.com/article/4231 ,调用一次和N次对系统产生的副作用是相同的,即删掉id为4231 的帖子;因此,调用者可以多次调用或刷新页面而不必担心引起错误。 POST所对应的URI并非创建的资源本身,而是资源的接收者。比如: POST http://www.forum.com/articles 的语义是在 http://www.forum.com/articles 下创建一篇帖子,HTTP响 应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们 具有不同的URI;所以,POST方法不具备幂等性。 PUT所对应的URI是要创建或更新的资源本身。比如: PUT http://www.forum/articles/4231 的语义是 创建或更新ID为4231的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方 法具有幂等性。 12 RESTful架构(SOAP,RPC) 推荐: http://www.ruanyifeng.com/blog/2011/09/restful.html 13 SOAP SOAP(原为Simple Object Access Protocol的首字母缩写,即简单对象访问协议)是交换数据 的一种协议规范,使用在计算机网络Web服务(web service)中,交换带结构信息。SOAP为了简 化网页服务器(Web Server)从XML数据库中提取数据时,节省去格式化页面时间,以及不同应用 本文档使用 书栈(BookStack.CN) 构建 - 29 -