Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Qiu-Ye/InterviewFAQ-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

4 Commits

Repository files navigation

InterviewFAQ-python

Table of Contents

Python语言特性

1 Python的函数参数传递

看两个例子:

a = 1
def fun(a):
 a = 2
fun(a)
print a # 1
a = []
def fun(a):
 a.append(1)
fun(a)
print a # [1]

所有的变量都可以理解是内存中一个对象的"引用",或者,也可以看似c中void*的感觉。

通过id来看引用a的内存地址可以比较理解:

a = 1
def fun(a):
 print "func_in",id(a) # func_in 41322472
 a = 2
 print "re-point",id(a), id(2) # re-point 41322448 41322448
print "func_out",id(a), id(1) # func_out 41322472 41322472
fun(a)
print a # 1

注:具体的值在不同电脑上运行时可能不同。

可以看到,在执行完a = 2之后,a引用中保存的值,即内存地址发生变化,由原来1对象的所在的地址变成了2这个实体对象的内存地址。

而第2个例子a引用保存的内存值就不会发生变化:

a = []
def fun(a):
 print "func_in",id(a) # func_in 53629256
 a.append(1)
print "func_out",id(a) # func_out 53629256
fun(a)
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)和实例方法,如下:

def foo(x):
 print "executing foo(%s)"%(x)
class A(object):
 def foo(self,x):
 print "executing foo(%s,%s)"%(self,x)
 @classmethod
 def class_foo(cls,x):
 print "executing class_foo(%s,%s)"%(cls,x)
 @staticmethod
 def static_foo(x):
 print "executing static_foo(%s)"%x
a=A()

这里先理解下函数参数里面的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 的实例。

实例变量:

实例化之后,每个实例单独拥有的变量。

class Test(object): 
 num_of_instance = 0 
 def __init__(self, name): 
 self.name = name 
 Test.num_of_instance += 1 
 
if __name__ == '__main__': 
 print Test.num_of_instance # 0
 t1 = Test('jack') 
 print Test.num_of_instance # 1
 t2 = Test('lucy') 
 print t1.name , t1.num_of_instance # jack 2
 print t2.name , t2.num_of_instance # lucy 2

补充的例子

class Person:
 name="aaa"
p1=Person()
p2=Person()
p1.name="bbb"
print p1.name # bbb
print p2.name # aaa
print Person.name # aaa

这里p1.name="bbb"是实例调用了类变量,这其实和上面第一个问题一样,就是函数传参的问题,p1.name一开始是指向的类变量name="aaa",但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.

可以看看下面的例子:

class Person:
 name=[]
p1=Person()
p2=Person()
p1.name.append(1)
print p1.name # [1]
print p2.name # [1]
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().

a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print type(a),type(b),type(c) # <type 'list'> <type 'dict'> <type 'bool'>
print isinstance(a,list) # True

6 字典推导式

可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的:

d = {key: value for (key, value) in iterable}

7 Python中单下划线和双下划线

>>> class MyClass():
... def __init__(self):
... self.__superprivate = "Hello"
... self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_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

.format在许多方面看起来更便利.对于%最烦人的是它无法同时传递一个变量和元组.你可能会想下面的代码不会有什么问题:

"hi there %s" % name

但是,如果name恰好是(1,2,3),它将会抛出一个TypeError异常.为了保证它总是正确的,你必须这样做:

"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

这里有个关于生成器的创建问题面试官有考: 问: 将列表生成式中[]改成() 之后数据结构是否改变? 答案:是,从列表变为生成器

>>> L = [x*x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000028F8B774200>

通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器:边循环,边计算的机制—>generator

10 *args and **kwargs

*args**kwargs只是为了方便并没有强制使用它们.

当你不确定你的函数里将要传递多少参数时你可以用*args.例如,它可以传递任意数量的参数:

>>> def print_everything(*args):
 for count, thing in enumerate(args):
... print '{0}. {1}'.format(count, thing)
...
>>> print_everything('apple', 'banana', 'cabbage')
0. apple
1. banana
2. cabbage

相似的,**kwargs允许你使用没有事先定义的参数名:

>>> def table_things(**kwargs):
... for name, value in kwargs.items():
... print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit

你也可以混着用.命名参数首先获得参数值然后所有的其他参数都传递给*args**kwargs.命名参数在列表的最前端.例如:

def table_things(titlestring, **kwargs)

*args**kwargs可以同时在函数的定义中,但是*args必须在**kwargs前面.

当调用函数时你也可以用***语法.例如:

>>> def print_three_things(a, b, c):
... print 'a = {0}, b = {1}, c = {2}'.format(a,b,c)
...
>>> mylist = ['aardvark', 'baboon', 'cat']
>>> print_three_things(*mylist)
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

函数重载主要是为了解决两个问题。

  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问题可以了解下(新式类是广度优先,旧式类是深度优先),<Python核心编程>里讲的也很多.

一个旧式类的深度优先的例子

class A():
 def foo1(self):
 print "A"
class B(A):
 def foo2(self):
 pass
class C(A):
 def foo1(self):
 print "C"
class D(B, C):
 pass
d = D()
d.foo1()
# 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__方法

class Singleton(object):
 def __new__(cls, *args, **kw):
 if not hasattr(cls, '_instance'):
 orig = super(Singleton, cls)
 cls._instance = orig.__new__(cls, *args, **kw)
 return cls._instance
class MyClass(Singleton):
 a = 1

2 共享属性

创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法.

class Borg(object):
 _state = {}
 def __new__(cls, *args, **kw):
 ob = super(Borg, cls).__new__(cls, *args, **kw)
 ob.__dict__ = cls._state
 return ob
class MyClass2(Borg):
 a = 1

3 装饰器版本

def singleton(cls, *args, **kw):
 instances = {}
 def getinstance():
 if cls not in instances:
 instances[cls] = cls(*args, **kw)
 return instances[cls]
 return getinstance
@singleton
class MyClass:
 ...

4 import方法

作为python的模块是天然的单例模式

# mysingleton.py
class My_Singleton(object):
 def foo(self):
 pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()

单例模式伯乐在线详细解释

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. 内嵌函数必须引用外部函数中的变量
  3. 外部函数的返回值必须是内嵌函数

感觉闭包还是有难度的,几句话是说不明白的,还是查查相关资料.

重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.

闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样.

21 lambda函数

其实就是一个匿名函数,为什么叫lambda?因为和后面的函数式编程有关.

推荐: 知乎

22 Python函数式编程

这个需要适当的了解一下吧,毕竟函数式编程在Python中也做了引用.

推荐: 酷壳

python中函数式编程支持:

filter 函数的功能相当于过滤器。调用一个布尔函数bool_func来迭代遍历每个seq中的元素;返回一个使bool_seq返回值为true的元素的序列。

>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6,7]

map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:

>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]

reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘:

>>> reduce(lambda x,y:x*y,range(1,4))
6

23 Python里的拷贝

引用和copy(),deepcopy()的区别

import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d
输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
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. 实时性

缺点:

  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的区别

推荐: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

31 单线程+异步I/O

  • 现代操作系统对I/O操作的改进中最为重要的就是支持异步I/O。如果充分利用操作系统提供的异步I/O支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。Nginx就是支持异步I/O的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式,这也是当下实现多任务编程的一种趋势。

  • 在Python语言中,单线程+异步I/O的编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

  • 协程通过yield关键字和 send()操作来转移执行权,协程之间不是调用者与被调用者的关系。

  • 协程的优势在于以下两点:

    1. 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。
    2. 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。

    说明:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它的长处,如果要提升CPU的利用率可以考虑"多进程+协程"的模式。

32 Django

  • 创建一个Django项目
    • mkdir oa
    • cd oa
    • python3 -m venv venv
    • source venv/bin/activate
    • django-admin startproject oa .
    • python manage.py runserver
  • 视图模板
    • templates
      • index.html
    • 项目目录/views.py
    • settings.py
      • 'DIRS': [os.path.join(BASE_DIR, 'templates')],

33 爬虫

  • 步骤

    • 设定抓取目标(种子页面)并获取网页。
    • 当服务器无法访问时,设置重试次数。
    • 在需要的时候设置用户代理(否则无法访问页面)。
    • 对获取的页面进行必要的解码操作。
    • 通过正则表达式获取页面中的链接。
    • 对链接进行进一步的处理(获取页面并重复上面的动作)。
    • 将有用的信息进行持久化(以备后续的处理)。
  • 工具

    • 下载数据 - urllib / requests / aiohttp。
    • 解析数据 - re / lxml / beautifulsoup4(bs4)/ pyquery。
    • 缓存和持久化 - pymysql / redis / sqlalchemy / peewee / pymongo。
    • 生成摘要 - hashlib。
    • 序列化和压缩 - pickle / json / zlib。
    • 调度器 - 进程 / 线程 / 协程。
  • 推荐大家阅读《Python并行编程》

编程题

1 台阶问题/斐波那契

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

fib = lambda n: n if n <= 2 else fib(n - 1) + fib(n - 2)

第二种记忆方法

def memo(func):
 cache = {}
 def wrap(*args):
 if args not in cache:
 cache[args] = func(*args)
 return cache[args]
 return wrap
@memo
def fib(i):
 if i < 2:
 return 1
 return fib(i-1) + fib(i-2)

第三种方法

def fib(n):
 a, b = 0, 1
 for _ in xrange(n):
 a, b = b, a + b
 return b

2 变态台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级......它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

fib = lambda n: n if n < 2 else 2 * fib(n - 1)

3 矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

2*n个矩形的覆盖方法等于第2*(n-1)加上第2*(n-2)的方法。

f = lambda n: 1 if n < 2 else f(n - 1) + f(n - 2)

4 杨氏矩阵查找

在一个m行n列二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

使用Step-wise线性搜索。

def get_value(l, r, c):
 return l[r][c]
def find(l, x):
 m = len(l) - 1
 n = len(l[0]) - 1
 r = 0
 c = n
 while c >= 0 and r <= m:
 value = get_value(l, r, c)
 if value == x:
 return True
 elif value > x:
 c = c - 1
 elif value < x:
 r = r + 1
 return False

5 去除列表中的重复元素

用集合

list(set(l))

用字典

l1 = ['b','c','d','b','c','a','a']
l2 = {}.fromkeys(l1).keys()
print l2

用字典并保持顺序

l1 = ['b','c','d','b','c','a','a']
l2 = list(set(l1))
l2.sort(key=l1.index)
print l2

列表推导式

l1 = ['b','c','d','b','c','a','a']
l2 = []
[l2.append(i) for i in l1 if not i in l2]

sorted排序并且用列表推导式.

l = ['b','c','d','b','c','a','a'] [single.append(i) for i in sorted(l) if i not in single] print single

6 链表成对调换

1->2->3->4转换成2->1->4->3.

class ListNode:
 def __init__(self, x):
 self.val = x
 self.next = None
class Solution:
 # @param a ListNode
 # @return a ListNode
 def swapPairs(self, head):
 if head != None and head.next != None:
 next = head.next
 head.next = self.swapPairs(next.next)
 next.next = head
 return next
 return head

7 创建字典的方法

1 直接创建

dict = {'name':'earth', 'port':'80'}

2 工厂方法

items=[('name','earth'),('port','80')]
dict2=dict(items)
dict1=dict((['name','earth'],['port','80']))

3 fromkeys()方法

dict1={}.fromkeys(('x','y'),-1)
dict={'x':-1,'y':-1}
dict2={}.fromkeys(('x','y'))
dict2={'x':None, 'y':None}

8 合并两个有序列表

知乎远程面试要求编程

尾递归

def _recursion_merge_sort2(l1, l2, tmp):
 if len(l1) == 0 or len(l2) == 0:
 tmp.extend(l1)
 tmp.extend(l2)
 return tmp
 else:
 if l1[0] < l2[0]:
 tmp.append(l1[0])
 del l1[0]
 else:
 tmp.append(l2[0])
 del l2[0]
 return _recursion_merge_sort2(l1, l2, tmp)
def recursion_merge_sort2(l1, l2):
 return _recursion_merge_sort2(l1, l2, [])

循环算法

思路:

定义一个新的空列表

比较两个列表的首个元素

小的就插入到新列表里

把已经插入新列表的元素从旧列表删除

直到两个旧列表有一个为空

再把旧列表加到新列表后面

def loop_merge_sort(l1, l2):
 tmp = []
 while len(l1) > 0 and len(l2) > 0:
 if l1[0] < l2[0]:
 tmp.append(l1[0])
 del l1[0]
 else:
 tmp.append(l2[0])
 del l2[0]
 tmp.extend(l1)
 tmp.extend(l2)
 return tmp

pop弹出

a = [1,2,3,7]
b = [3,4,5]
def merge_sortedlist(a,b):
 c = []
 while a and b:
 if a[0] >= b[0]:
 c.append(b.pop(0))
 else:
 c.append(a.pop(0))
 while a:
 c.append(a.pop(0))
 while b:
 c.append(b.pop(0))
 return c
print merge_sortedlist(a,b)
 

9 交叉链表求交点

其实思想可以按照从尾开始比较两个链表,如果相交,则从尾开始必然一致,只要从尾开始比较,直至不一致的地方即为交叉点,如图所示

# 使用a,b两个list来模拟链表,可以看出交叉点是 7这个节点
a = [1,2,3,7,9,1,5]
b = [4,5,7,9,1,5]
for i in range(1,min(len(a),len(b))):
 if i==1 and (a[-1] != b[-1]):
 print "No"
 break
 else:
 if a[-i] != b[-i]:
 print "交叉节点:",a[-i+1]
 break
 else:
 pass

另外一种比较正规的方法,构造链表类

class ListNode:
 def __init__(self, x):
 self.val = x
 self.next = None
def node(l1, l2):
 length1, lenth2 = 0, 0
 # 求两个链表长度
 while l1.next:
 l1 = l1.next
 length1 += 1
 while l2.next:
 l2 = l2.next
 length2 += 1
 # 长的链表先走
 if length1 > lenth2:
 for _ in range(length1 - length2):
 l1 = l1.next
 else:
 for _ in range(length2 - length1):
 l2 = l2.next
 while l1 and l2:
 if l1.next == l2.next:
 return l1.next
 else:
 l1 = l1.next
 l2 = l2.next

修改了一下:

#coding:utf-8
class ListNode:
 def __init__(self, x):
 self.val = x
 self.next = None
def node(l1, l2):
 length1, length2 = 0, 0
 # 求两个链表长度
 while l1.next:
 l1 = l1.next#尾节点
 length1 += 1
 while l2.next:
 l2 = l2.next#尾节点
 length2 += 1
 #如果相交
 if l1.next == l2.next:
 # 长的链表先走
 if length1 > length2:
 for _ in range(length1 - length2):
 l1 = l1.next
 return l1#返回交点
 else:
 for _ in range(length2 - length1):
 l2 = l2.next
 return l2#返回交点
 # 如果不相交
 else:
 return

思路: http://humaoli.blog.163.com/blog/static/13346651820141125102125995/

10 二分查找

#coding:utf-8
def binary_search(list,item):
 low = 0
 high = len(list)-1
 while low<=high:
 mid = (low+high)/2
 guess = list[mid]
 if guess>item:
 high = mid-1
 elif guess<item:
 low = mid+1
 else:
 return mid
 return None
mylist = [1,3,5,7,9]
print binary_search(mylist,3)

参考: http://blog.csdn.net/u013205877/article/details/76411718

11 快排

#coding:utf-8
def quicksort(list):
 if len(list)<2:
 return list
 else:
 midpivot = list[0]
 lessbeforemidpivot = [i for i in list[1:] if i<=midpivot]
 biggerafterpivot = [i for i in list[1:] if i > midpivot]
 finallylist = quicksort(lessbeforemidpivot)+[midpivot]+quicksort(biggerafterpivot)
 return finallylist
print quicksort([2,4,6,7,1,2,5])

更多排序问题可见:数据结构与算法-排序篇-Python描述

12 找零问题

#coding:utf-8
#values是硬币的面值values = [ 25, 21, 10, 5, 1]
#valuesCounts 钱币对应的种类数
#money 找出来的总钱数
#coinsUsed 对应于目前钱币总数i所使用的硬币数目
def coinChange(values,valuesCounts,money,coinsUsed):
 #遍历出从1到money所有的钱数可能
 for cents in range(1,money+1):
 minCoins = cents
 #把所有的硬币面值遍历出来和钱数做对比
 for kind in range(0,valuesCounts):
 if (values[kind] <= cents):
 temp = coinsUsed[cents - values[kind]] +1
 if (temp < minCoins):
 minCoins = temp
 coinsUsed[cents] = minCoins
 print ('面值:{0}的最少硬币使用数为:{1}'.format(cents, coinsUsed[cents]))

思路: http://blog.csdn.net/wdxin1322/article/details/9501163

方法: http://www.cnblogs.com/ChenxofHit/archive/2011/03/18/1988431.html

13 广度遍历和深度遍历二叉树

给定一个数组,构建二叉树,并且按层次打印这个二叉树

14 二叉树节点

class Node(object):
 def __init__(self, data, left=None, right=None):
 self.data = data
 self.left = left
 self.right = right
tree = Node(1, Node(3, Node(7, Node(0)), Node(6)), Node(2, Node(5), Node(4)))

15 层次遍历

def lookup(root):
 row = [root]
 while row:
    print(row)
    row = [kid for item in row for kid in (item.left, item.right) if kid]

16 深度遍历

def deep(root):
 if not root:
 return
 print root.data
 deep(root.left)
 deep(root.right)
if __name__ == '__main__':
 lookup(tree)
 deep(tree)

17 前中后序遍历

深度遍历改变顺序就OK了

#coding:utf-8
#二叉树的遍历
#简单的二叉树节点类
class Node(object):
 def __init__(self,value,left,right):
 self.value = value
 self.left = left
 self.right = right
#中序遍历:遍历左子树,访问当前节点,遍历右子树
def mid_travelsal(root):
 if root.left is None:
 mid_travelsal(root.left)
 #访问当前节点
 print(root.value)
 if root.right is not None:
 mid_travelsal(root.right)
#前序遍历:访问当前节点,遍历左子树,遍历右子树
def pre_travelsal(root):
 print (root.value)
 if root.left is not None:
 pre_travelsal(root.left)
 if root.right is not None:
 pre_travelsal(root.right)
#后续遍历:遍历左子树,遍历右子树,访问当前节点
def post_trvelsal(root):
 if root.left is not None:
 post_trvelsal(root.left)
 if root.right is not None:
 post_trvelsal(root.right)
 print (root.value)

18 求最大树深

def maxDepth(root):
 if not root:
 return 0
 return max(maxDepth(root.left), maxDepth(root.right)) + 1

19 求两棵树是否相同

def isSameTree(p, q):
 if p == None and q == None:
 return True
 elif p and q :
 return p.val == q.val and isSameTree(p.left,q.left) and isSameTree(p.right,q.right)
 else :
 return False

20 前序中序求后序

推荐: http://blog.csdn.net/hinyunsin/article/details/6315502

def rebuild(pre, center):
 if not pre:
 return
 cur = Node(pre[0])
 index = center.index(pre[0])
 cur.left = rebuild(pre[1:index + 1], center[:index])
 cur.right = rebuild(pre[index + 1:], center[index + 1:])
 return cur
def deep(root):
 if not root:
 return
 deep(root.left)
 deep(root.right)
 print root.data

21 单链表逆置

class Node(object):
 def __init__(self, data=None, next=None):
 self.data = data
 self.next = next
link = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9)))))))))
def rev(link):
 pre = link
 cur = link.next
 pre.next = None
 while cur:
 tmp = cur.next
 cur.next = pre
 pre = cur
 cur = tmp
 return pre
root = rev(link)
while root:
 print root.data
 root = root.next

思路: http://blog.csdn.net/feliciafay/article/details/6841115

方法: http://www.xuebuyuan.com/2066385.html?mobile=1

22 两个字符串是否是变位词

class Anagram:
 """
 @:param s1: The first string
 @:param s2: The second string
 @:return true or false
 """
 def Solution1(s1,s2):
 alist = list(s2)
 pos1 = 0
 stillOK = True
 while pos1 < len(s1) and stillOK:
 pos2 = 0
 found = False
 while pos2 < len(alist) and not found:
 if s1[pos1] == alist[pos2]:
 found = True
 else:
 pos2 = pos2 + 1
 if found:
 alist[pos2] = None
 else:
 stillOK = False
 pos1 = pos1 + 1
 return stillOK
 print(Solution1('abcd','dcba'))
 def Solution2(s1,s2):
 alist1 = list(s1)
 alist2 = list(s2)
 alist1.sort()
 alist2.sort()
 pos = 0
 matches = True
 while pos < len(s1) and matches:
 if alist1[pos] == alist2[pos]:
 pos = pos + 1
 else:
 matches = False
 return matches
 print(Solution2('abcde','edcbg'))
 def Solution3(s1,s2):
 c1 = [0]*26
 c2 = [0]*26
 for i in range(len(s1)):
 pos = ord(s1[i])-ord('a')
 c1[pos] = c1[pos] + 1
 for i in range(len(s2)):
 pos = ord(s2[i])-ord('a')
 c2[pos] = c2[pos] + 1
 j = 0
 stillOK = True
 while j<26 and stillOK:
 if c1[j] == c2[j]:
 j = j + 1
 else:
 stillOK = False
 return stillOK
 print(Solution3('apple','pleap'))

23 动态规划问题

可参考:动态规划(DP)的整理-Python描述

24 单例模式

方法一:可以使用__new__方法

在__new__方法中把类实例绑定到类变量_instance上,如果cls._instance为None表示该类还没有实例化过,实例化该类并返回。如果cls_instance不为None表示该类已实例化,直接返回cls_instance

class SingleTon(object):
 def __new__(cls,*args,**kwargs):
 if not hasattr(cls,'_instance'):
 cls._instance = object.__new__(cls,*args,**kwargs)
 return cls._instance
class TestClass(SingleTon):
 a = 1
test1 = TestClass()
test2 = TestClass()
print (test1.a,test2.a)
test1.a=2
print (test1.a,test2.a)
print (id(test1),id(test2))

方法二:使用装饰器,建立过实例的就放到instances里面,下次建立的时候先检查里面有没有

def SingleTon(cls,*args,**kwargs):
 instances = {}
 print (instances)
 def _singleton():
 if cls not in instances:
 instances[cls] = cls(*args,**kwargs)
 print (instances)
 return instances[cls]
 return _singleton
@SingleTon
class LastClass(object):
 a = 1
test1 = LastClass()
print (test1.a)
test2 = LastClass()
print (test2.a)

方法三:使用__metaclass__(元类)关于元类看看这个吧:http://blog.jobbole.com/21351/

class SignalTon(type):
 def __init__(cls,name,bases,dict):
 super(SignalTon, cls).__init__(name,bases,dict)
 cls._instance = None
 def __call__(cls, *args, **kwargs):
 if cls._instance is None:
 cls._instance = super(SignalTon,cls).__call__(*args,**kwargs)
 return cls._instance
class TestClass(object):
 __metaclass__ = SignalTon
test1 = TestClass()
test2 = TestClass()
test1.a = 2
print (test1.a,test2.a)
print (id(test1),id(test2))

方法四:共享属性

所谓单例就是所有的引用(实例,对象)拥有相同的属性和方法,同一个类的实例天生都会有相同的方法,那我们只需要保证同一个类所产生的实例都具有相同的属性。所有实例共享属性最简单直接的方法就是共享__dict__属性指向。

class SingleTon(object):
 _state = {}
 def __new__(cls, *args, **kwargs):
 obj = object.__new__(cls,*args,**kwargs)
 obj.__dict__ = cls._state
 return obj
class TestClass(SingleTon):
 a = 1
test1 = TestClass()
test2 = TestClass()
print (test1.a,test2.a)
test1.a = 2
print (test1.a,test2.a)
print (id(test1),id(test2))

方法五:使用同一个模版,写在mysingleton.py中

class My_Singleton(object):
 def foo(self):
 pass
my_singleton = My_Singleton()
#写在要使用这个实例的py文件里面,在不同的引用的地方都引用相同的实例,以此实现单例模式
from mysingleton import my_singleton
my_singleton.foo()

25 super函数的原理

#阅读下面的代码,它的输出结果是什么?
class A(object):
 def __init__(self):
 print ("enter A")
 super(A, self).__init__() # new
 print ("leave A")
class B(object):
 def __init__(self):
 print ("enter B")
 super(B, self).__init__() # new
 print ("leave B")
class C(A):
 def __init__(self):
 print ("enter C")
 super(C, self).__init__()
 print ("leave C")
class D(A):
 def __init__(self):
 print ("enter D")
 super(D, self).__init__()
 print ("leave D")
class E(B, C):
 def __init__(self):
 print ("enter E")
 super(E, self).__init__() # change
 print ("leave E")
class F(E, D):
 def __init__(self):
 print ("enter F")
 super(F, self).__init__() # change
 print ("leave F")
#输出
enter F
enter E
enter B
enter C
enter D
enter A
leave A
leave D
leave C
leave B
leave E
leave F

About

总结Python部分的面试常见问题

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

AltStyle によって変換されたページ (->オリジナル) /