Git Product home page Git Product logo

interview_python's Introduction

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)

类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用

类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;

静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系

更多关于这个问题:

  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
dir()   #  获得当前模块的属性列表

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

"{0} {1}".format("hello", "world") # 设置指定位置 print("网站名:{name}, 地址 {url}".format(name="菜鸟教程", url="www.runoob.com"))

通过字典设置参数 site = {"name": "菜鸟教程", "url": "www.runoob.com"} print("网站名:{name}, 地址 {url}".format(**site))

通过列表索引设置参数 my_list = ['菜鸟教程', 'www.runoob.com'] print("网站名:{0[0]}, 地址 {0[1]}".format(my_list)) # "0" 是必须的

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.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)
3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。

for 循环机制
for循环的本质:循环所有对象,全都是使用迭代器协议。
for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了,
列表,字符串,元组,字典,集合,文件对象等本质上来说都不是可迭代对象,在使用for循环的时候内部是先调用他们内部的iter方法,使他们变成了可迭代对象,然后在使用可迭代对象的next方法依次循环元素,当元素循环完时,会触发StopIteration异常,for循环会捕捉到这种异常,终止迭代

访问方式常见的有下标方式访问、迭代器协议访问、for循环访问

迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退
不像列表把所有元素一次性加载到内存,迭代器是以一种延迟计算(lazy evaluation)方式返回元素,不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合
迭代器有两个基本的方法 next方法:返回迭代器的下一个元素 __ 方法:返回迭代器对象本身
1.访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容 2.不能随机访问集合中的某个值 ,只能从头到尾依次访问 3.访问到一半时不能往回退 4.便于循环比较大的数据集合,节省内存

生成器:语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值(只要一个函数内出现了yield,那它就是一个生成器函数,执行这个函数就得到一个生成器)
自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常
状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行
生成器的唯一注意事项就是:生成器只能遍历一次。

区别: 生成器能做到迭代器能做的所有事,而且因为自动创建iter()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常。

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

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

31 yield

yield就是保存当前程序执行状态。你用for循环的时候,每次取一个元素的时候就会计算一次。用yield的函数叫generator,和iterator一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间,generator每次计算需要上一次计算结果,所以用yield,否则一return,上次计算结果就没了

首先,如果你还没有对yield有个初步分认识,那么你先把yield看做“return”,这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了。看做return之后再把它看做一个是生成器(generator)的一部分(带yield的函数才是真正的迭代器),好了,如果你对这些不明白的话,那先把yield看做return,然后直接看下面的程序,你就会明白yield的全部意思了:

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

就这么简单的几行代码就让你明白什么是yield,代码的输出这个:

starting...
4
********************
res: None
4

1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,

4.程序执行print("*"20),输出20个

5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,

6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从foo函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))

再看一个这个生成器的send函数的例子,这个例子就把上面那个例子的最后一行换掉了,输出结果:

starting...
4
********************
res: 7
4

先大致说一下send函数的概念:此时你应该注意到上面那个的紫色的字,还有上面那个res的值为什么是None,这个变成了7,到底为什么,这是因为,send是发送一个参数给res的,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

5.程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量

6.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入while循环

7.程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次暂停,直到再次调用next方法或send方法。

第一次调用__next__函数的时候,我们从fun的起点开始,然后在yield处结束,需要注意的是,赋值语句不会调用,此处yield i和含义和return差不多。

但是第二次调用__next__函数的时候,就会直接从上一个yield的结束处开始,也就是先执行赋值语句,然后输出字符串,进入下一个循环,直到下一个yield或者生成器结束

第二次调用的时候没有选择使用__next__函数,而是使用了一个sent()函数。这里就需要注意,sent()函数的用法和__next__函数不太一样。sent()函数只能从yield之后开始,到下一个yield结束。这也就意味着第一次调用必须使用__next__函数。

sent()函数最重要的作用在于它可以给yield对应的赋值语句赋值

这就结束了,说一下,为什么用这个生成器,是因为如果用List的话,会占用更大的空间,比如说取0,1,2,3,4,5,6............1000

你可能会这样:

for n in range(1000):
    a=n

这个时候range(1000)就默认生成一个含有1000个数的list了,所以很占内存。

这个时候你可以用刚才的yield组合成生成器进行实现,也可以用xrange(1000)这个生成器实现

yield组合:

def foo(num):
    print("starting...")
    while num<10:
        num=num+1
        yield num
for n in foo(0):
    print(n)

输出:

starting...
1
2
3
4
5
6
7
8
9
10

 xrange(1000):

for n in xrange(1000):
    a=n

 其中要注意的是python3时已经没有xrange()了,在python3中,range()就是xrange()了,你可以在python3中查看range()的类型,它已经是个<class 'range'>了,而不是一个list了,毕竟这个是需要优化的。

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和装饰器

装饰器本质上是一个callable object,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。比如:插入日志,性能测试,事务处理,缓存。权限的校验等场景,有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。

(扩充)这个AOP一听起来有点懵,同学面阿里的时候就被问懵了...

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

AOP:简言之、这种在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程**就是面向切面的编程。 我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。 优点是:这样的做法,对原有代码毫无入侵性 装饰器:装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。 概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。 要想明白装饰器首先要明白一个概念即函数即是对象。 函数就是对象.因此,对象可以赋值给一个变量;以在其他函数里定义;函数可以返回另一个函数;函数作为参数传递

1.1. 需求是怎么来的? 装饰器的定义很是抽象,我们来看一个小例子。

def foo():   
    print 'in foo()'
     
foo()

这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

import time
def foo():
    start = time.clock()
    print 'in foo()'
    end = time.clock()
    print "used :" , end - start
 
foo()

很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

1.2. 以不变应万变,是变也 还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

import time
def foo():
    print 'in foo()'
 
def timeit(func):
    start = time.clock()
    func()
    end =time.clock()
    print 'used:', end - start
 
timeit(foo)

看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。  

1.3. 最大限度地少改动! 既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

import time
  
def foo():
    print 'in foo()'
  
# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):
      
    # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
      
    # 将包装后的函数返回
    return wrapper
  
foo = timeit(foo)
foo()

这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)

  1. Python的额外支持 2.1. 语法糖 上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。
import time
 
def timeit(func):
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
 
@timeit
def foo():
    print 'in foo()'
 
foo()

重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。

2.2. 内置的装饰器 内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。

class Rabbit(object):
      
    def __init__(self, name):
        self._name = name
      
    @staticmethod
    def newRabbit(name):
        return Rabbit(name)
      
    @classmethod
    def newRabbit2(cls):
        return Rabbit('')
      
    @property
    def name(self):
        return self._name

这里定义的属性是一个只读属性,如果需要可写,则需要再定义一个setter:

@name.setter
def name(self, name):
    self._name = name

2.3. functools模块 functools模块提供了两个装饰器。这个模块是Python 2.5后新增的,一般来说大家用的应该都高于这个版本。但我平时的工作环境是2.4 T-T

2.3.1. wraps(wrapped[, assigned][, updated]): 这是一个很有用的装饰器。看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字wrapper,如果你希望使用反射,可能会导致意外的结果。这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留。

import time
import functools
  
def timeit(func):
    @functools.wraps(func)
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
  
@timeit
def foo():
    print 'in foo()'
  
foo()
print foo.__name__
foo

首先注意第5行,如果注释这一行,foo.__name__将是'wrapper'。另外相信你也注意到了,这个装饰器竟然带有一个参数。实际上,他还有另外两个可选的参数,assigned中的属性名将使用赋值的方式替换,而updated中的属性名将使用update的方式合并,你可以通过查看functools的源代码获得它们的默认值。对于这个装饰器,相当于wrapper = functools.wraps(func)(wrapper)。

2.3.2. total_ordering(cls): 这个装饰器在特定的场合有一定用处,但是它是在Python 2.7后新增的。它的作用是为实现了至少__lt__、legt、__ge__其中一个的类加上其他的比较方法,这是一个类装饰器。如果觉得不好理解,不妨仔细看看这个装饰器的源代码:

53  def total_ordering(cls):
54      """Class decorator that fills in missing ordering methods"""
55      convert = {
56          '__lt__': [('__gt__', lambda self, other: other < self),
57                     ('__le__', lambda self, other: not other < self),
58                     ('__ge__', lambda self, other: not self < other)],
59          '__le__': [('__ge__', lambda self, other: other <= self),
60                     ('__lt__', lambda self, other: not other <= self),
61                     ('__gt__', lambda self, other: not self <= other)],
62          '__gt__': [('__lt__', lambda self, other: other > self),
63                     ('__ge__', lambda self, other: not other > self),
64                     ('__le__', lambda self, other: not self > other)],
65          '__ge__': [('__le__', lambda self, other: other >= self),
66                     ('__gt__', lambda self, other: not other >= self),
67                     ('__lt__', lambda self, other: not self >= other)]
68      }
69      roots = set(dir(cls)) & set(convert)
70      if not roots:
71          raise ValueError('must define at least one ordering operation: < > <= >=')
72      root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
73      for opname, opfunc in convert[root]:
74          if opname not in roots:
75              opfunc.__name__ = opname
76              opfunc.__doc__ = getattr(int, opname).__doc__
77              setattr(cls, opname, opfunc)
78      return cls
# 装饰器就是把其他函数作为参数的函数
def my_new_decorator(a_function_to_decorate):

    # 在函数里面,装饰器在运行中定义函数: 包装.
    # 这个函数将被包装在原始函数的外面,所以可以在原始函数之前和之后执行其他代码..
    def the_wrapper_function():

        # 把要在原始函数被调用前的代码放在这里
        print "Before the function runs"

        # 调用原始函数(用括号)
        a_function_to_decorate()

        # 把要在原始函数调用后的代码放在这里
        print "After the function runs"

    # 在这里"a_function_to_decorate" 函数永远不会被执行
    # 在这里返回刚才包装过的函数
    # 在包装函数里包含要在原始函数前后执行的代码.
    return the_wrapper_function

# 加入你建了个函数,不想修改了
def a_stand_alone_function():
    print "I am a stand alone function, don't you dare modify me"

a_stand_alone_function()
#输出: I am a stand alone function, don't you dare modify me

# 现在,你可以装饰它来增加它的功能
# 把它传递给装饰器,它就会返回一个被包装过的函数.

a_function_decorated = my_new_decorator(a_stand_alone_function)
# 执行
a_function_decorated()
#输出s:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

真实装饰器
@my_new_decorator
def another_stand_alone_function():
    print "Leave me alone"

another_stand_alone_function()
#输出:
#Before the function runs
#Leave me alone
#After the function runs

从这里可以看出@decorator就是下面的简写:
another_stand_alone_function = my_new_decorator(another_stand_alone_function)

在装饰器函数里传入参数
def a_decorator_passing_arguments(function_to_decorate):
    def a_wrapper_accepting_arguments(arg1, arg2):
        print "I got args! Look:", arg1, arg2
        function_to_decorate(arg1, arg2)
    return a_wrapper_accepting_arguments

# 当你调用装饰器返回的函数时,也就调用了包装器,把参数传入包装器里,
# 它将把参数传递给被装饰的函数里.

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
    print "My name is", first_name, last_name

print_full_name("Peter", "Venkman")
# 输出:
#I got args! Look: Peter Venkman
#My name is Peter Venkman

把参数传给装饰器
# 装饰器就是一个'平常不过'的函数
def my_decorator(func):
    print "I am an ordinary function"
    def wrapper():
        print "I am function returned by the decorator"
        func()
    return wrapper

# 因此你可以不用"@"也可以调用他

def lazy_function():
    print "zzzzzzzz"

decorated_function = my_decorator(lazy_function)
#输出: I am an ordinary function

# 之所以输出 "I am an ordinary function"是因为你调用了函数,
# 并非什么魔法.

@my_decorator
def lazy_function():
    print "zzzzzzzz"

#输出: I am an ordinary function

这里调用decorated_function()才会输出装饰器里面的方法,建一个装饰器.它只是一个新函数,去掉中间变量他就会变为真正的装饰器,那么如何去掉中间变量
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):

    print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2

    def my_decorator(func):
        # 这里传递参数的能力是借鉴了 closures.
        # 如果对closures感到困惑可以看看下面这个:
        # http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
        print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2

        # 不要忘了装饰器参数和函数参数!
        def wrapped(function_arg1, function_arg2) :
            print ("I am the wrapper around the decorated function.\n"
                  "I can access all the variables\n"
                  "\t- from the decorator: {0} {1}\n"
                  "\t- from the function call: {2} {3}\n"
                  "Then I can pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,
                          function_arg1, function_arg2))
            return func(function_arg1, function_arg2)

        return wrapped

    return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("I am the decorated function and only knows about my arguments: {0}"
           " {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments("Rajesh", "Howard")
#输出:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
#   - from the decorator: Leonard Sheldon
#   - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

看到了吗?我们用一个函数调用"@“

装饰器的知识点

装饰器本质上是一个callable object ,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

import time
from functools import wraps

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.clock()
        ret = func(*args, **kwargs)
        end = time.clock()
        print('used:',end-start)
        return ret
    
    return wrapper
@timeit
def foo():
    print('in foo()'foo())

装饰器使函数调用变慢了.一定要记住.

装饰器不能被取消(有些人把装饰器做成可以移除的但是没有人会用)所以一旦一个函数被装饰了.所有的代码都会被装饰.

内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。 Django用装饰器管理缓存和试图的权限. Twisted用来修改异步函数的调用.

这个问题比较大,推荐: 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 自然就不需要函数重载了。

python参数

确定参数:平时最常用的必传确定数量的参数即为确定参数

缺省参数:在调用函数时可以传也可以省去的参数,如果不传将使用默认值

可变参数:可变长度参数

关键字参数:长度可变,但是需要以kv对形式传参

# 1.缺省参数 即有默认值的参数 必须保证带有默认值的缺省参数,在参数列表末尾,例如:
def test1(a, b = 3):
	print(a, b)

test1(0)# 打印结果:0 3
test1(0,1)# 打印结果: 0 1

# 2.变长参数 即参数的个数没有确定长度 例如:
def test2(a, b, *args):
	print(a, b, args)

test2(1, 2, 3, 4, 5, 6, 7) # 打印结果:1 2 (3,4,5,6,7) 也就是除去 a,b之外所有的参数都是args中的元素,并以元祖形式存在

# 3.关键字参数 即参数以 key=value的形式给出 例如
def test3(a, b, **kwargs):
	print(a, b, kwargs)

test3(1, 2, 'name'='pengshuyi', 'age'=20)# 打印结果:1 2 {'name':'pengshuyi', 'age':20} kv对以字典形式存在

# 4.各类型参数

14 新式类和旧式类

a. 在python里凡是继承了object的类,都是新式类

b. Python3里只有新式类

c. Python2里面继承object的是新式类,没有写父类的是经典类

d. 经典类目前在Python里基本没有应用

(扩充)这个面试官问了,我说了老半天,不知道他问的真正意图是什么.

stackoverflow

这篇文章很好的介绍了新式类的特性: http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html

新式类很早在2.2就出现了,所以旧式类完全是兼容的问题,Python3里的类全部都是新式类.这里有一个MRO问题可以了解下(新式类继承是根据C3算法,旧式类是深度优先),<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()被绕过

C3:

class A:
     pass
class B(A):
     pass
class C(A):
     pass
class D(B, C):
     pass
class E(C, A):
     pass
class F(D, E):
     pass
class G(E):
     pass
class H(G, F):
     pass
#首先。我们要确定从H开始找。也就是说。创建的是H的对象。
# 如果从H找。那找到H+H的父类的C3,我们设C3算法是L(X),即给出X类,找到X的MRO
  L(H) = H + L(G) + L(F)
#  继续从代码中找G和F的⽗类往⾥⾯带
  L(G) = G + L(E)
  L(F) = F + L(D)+ L(E)
#  继续找E 和 D
  L(E) = E + L(C) + L(A)
  L(D) = D + L(B) + L(C)
#  继续找B和C
  L(B) = B + L(A)
  L(C) = C + L(A)
     pass

最后就剩下⼀个A了. 也就不⽤再找了. 接下来. 把L(A) 往⾥带. 再推回去. 但要记住. 这⾥的+ 表⽰的是merge. merge的原则是⽤每个元组的头⼀项和后⾯元组的除头⼀项外的其他元素进⾏比较, 看是否存在. 如果存在. 就从下⼀个元组的头⼀项继续找. 如果找不到. 就拿出来.作为merge的结果的⼀项. 以此类推. 直到元组之间的元素都相同. 也就不⽤再找了.

L(B) =(B,) + (A,) -> (B, A)
  L(C) =(C,) + (A,) -> (C, A)
继续带. 
  L(E) = (E,) + (C, A) + (A) -> E, C, A
  L(D) = (D,) + (B, A) + (C, A) -> D, B, A
继续带. 
  L(G) = (G,) + (E, C, A) -> G, E, C, A
  L(F) = (F,) + (D, B, A) + (E, C, A) -> F, D, B, E, C, A
加油,最后了
  L(H) = (H, ) + (G, E, C, A) + ( F, D, B, E, C, A) -> H, G, F, D, B, E, C, A

算完了. 最终结果 HGFDBECA. 那这个算完了. 如何验证呢? 其实python早就给你准备好了. 我们可以使⽤类名.__mro__获取到类的MRO信息.

那C3到底怎么看更容易呢? 其实很简单. C3是把我们多个类产⽣的共同继承留到最后去找. 所以. 我们也可以从图上来看到相关的规律. 这个要⼤家⾃⼰多写多画图就能感觉到了. 但是如果没有所谓的共同继承关系. 那⼏乎就当成是深度遍历就可以了.

15 __new____init__的区别

这个__new__确实很少见到,先做了解吧.

  1. __new__是一个静态方法,而__init__是一个实例方法.
  2. __new__方法会返回一个创建的实例,而__init__什么都不返回.
  3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
  4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.

stackoverflow

ps: __metaclass__是创建类时起作用.所以我们可以分别使用__metaclass__,__new____init__来分别在类创建,实例创建和实例初始化的时候做一些小手脚.

16 单例模式

单例模式应用的场景一般发现在以下条件下:

资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。

控制资源的情况下,方便资源之间的互相通信。如线程池等,1,网站的计数器 2,应用配置 3.多线程池 4数据库配置 数据库连接池 5.应用程序的日志应用...

​ 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

__new__()__init__()之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例 这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的.

我们在使用class创建类的时候, 只会创建一个类对象, 但是, 当我们实例化这个类对象的时候, 一个类对象, 可以实例化出很多不同的对象, 而我们每次实例化出来一个对象, 就会在内存中重新分配一块空间, 而今天介绍的单例模式, 就是为了解决上述问题, 使得由一个类对象所实例化出来的全部对象都指向同一块内存空间.

class A:
    pass

a = A()
print('a对象所在的内存地址是 %d, A类所在的内存地址是 %d' % (id(a), id(A)))
b = A()
print('b对象所在的内存地址是 %d, A类所在的内存地址是 %d' % (id(b), id(A)))
c = A()
print('c对象所在的内存地址是 %d, A类所在的内存地址是 %d' % (id(c), id(A)))

要想弄明白为什么每个对象被实例化出来之后, 都会重新被分配出一块新的内存地址, 就要清楚一个python中的内置函数__new__(), 它跟__init__()一样, 都是对象在被创建出来的时候, 就自动执行的一个函数, init()函数, 是为了给函数初始化属性值的, 而__new__()这个函数, 就是为了给对象在被实例化的时候, 分配一块内存地址, 因此, 我们可以重写__new__()这个方法, 让他在第一次实例化一个对象之后, 分配一块地址, 在此后的所有实例化的其他对象时, 都不再分配新的地址, 而继续使用第一个对象所被分配的地址, 因此, 我们可以在类对象里, 定义一个类属性, 初始值设为None, 如果这个值是None就调用父类的__new__()方法, 为其分配地址, 并返回这个地址(__new__方法一定要返回一个地址)

New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实例

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):  #理解为方法的继承,对__new__方法也是同样的继承,没有重写,就是使用基类的方法
    a = 1
m1 = MyClass()
m2 = MyClass()
print m1 is m2

2 共享属性(有问题?属性一样,内存地址不一样啊)

1) 内置的数据类型没有__dict__属性

  2) 每个类有自己的__dict__属性,就算存着继承关系,父类的__dict__ 并不会影响子类的__dict__

  3) 对象也有自己的__dict__属性, 存储self.xxx 信息,父子类对象公用__dict__

创建实例时把所有实例的__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): # 装饰器的参数是类
    instances = {}
    def getinstance(*args, **kw):
        if cls not in instances:
            instances[cls] = cls(*args, **kw) # 创建实例
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  a = 2

4 import方法

使用模块:python的模块本身就是天然的单例模式,因为模块在第一次导入是会生成.pyc文件,第二次导入时会直接加载.pyc文件,而不会再次执行模块代码;

# 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如何实现单例模式?请写出两种实现方式?

第一种方法:使用装饰器

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper
    
    
@singleton
class Foo(object):
    pass
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)  # True

第二种方法:使用基类 New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实例

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance
    
    
class Foo(Singleton):
    pass

foo1 = Foo()
foo2 = Foo()

print(foo1 is foo2)  # True

第三种方法:元类,元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在调用call时候保证始终只创建一个实例即可,type是python的元类

class Singleton(type):
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instance


# Python2
class Foo(object):
    __metaclass__ = Singleton

# Python3
class Foo(metaclass=Singleton):
    pass

foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)  # True

79.对设计模式的理解,简述你了解的设计模式?

设计模式是经过总结,优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码,反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。 常见的是工厂模式和单例模式

82.用一行代码生成[1,4,9,16,25,36,49,64,81,100]

print([x*x for x in range(1, 11)])

87.X是什么类型?

X= (i for i in range(10))
X是 generator类型

88.请用一行代码 实现将1-N 的整数列表以3为单位分组

N =100
print ([[x for x in range(1,100)] [i:i+3] for i in range(0,100,3)])

17 Python中的作用域

Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。

当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:

本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)

Python中变量的作用域?(变量查找顺序)

函数作用域的LEGB顺序

1.什么是LEGB?

L: local 函数内部作用域

E: enclosing 函数内部与内嵌函数之间

G: global 全局作用域

B: build-in 内置作用

python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的

函数定义了本地作用域,而模块定义的是全局作用域。

如果想要在函数内定义全局作用域,需要加上global修饰符。

变量名解析:LEGB原则

当在函数中使用未认证的变量名时,Python搜索4个作用域[本地作用域(L)(函数内部声明但没有使用global的变量),之后是上一层结构中def或者lambda的本地作用域(E),之后是全局作用域(G)(函数中使用global声明的变量或在模块层声明的变量),最后是内置作用域(B)(即python的内置类和函数等)]并且在第一处能够找到这个变量名的地方停下来。如果变量名在整个的搜索过程中 都没有找到,Python就会报错。

补:上面的变量规则只适用于简单对象,当出现引用对象的属性时,则有另一套搜索规则:属性引用搜索一个或多个对象,而不是作用域,并且有可能涉及到所谓的"继承"

18 GIL线程全局锁

线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。

Python 最难的问题

解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能).

19 协程

简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.

Python里最常见的yield就是协程的**!可以查看第九个问题.

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

与线程相比,协程更轻量。一个Python线程大概占用8M内存,而一个协程只占用1KB不到内存。协程更适用于IO密集型的应用。

send方法

yield表达式有一个返回值,send方法的作用就是控制这个返回值,send的参数就是yield表达式的返回值。我们来看一下官方文档上关于send的定义

generate.send(value):生成器的send(value)方法会将value值“发送”给生成器中的方法。value参数变成当前yield表达式的值。send()方法会返回生成器生成的下一个yield值或者StopIteration异常(如果生成器没有生成下一个yield值就退出了)。当通过调用send()启动生成器时,value值必须为None,因为当前还没有yield表达式可以接收参数。

python yield 与 协程的实现

我们都知道函数(子例程)的控制权要等到return 后才会交给调用者,函数中的变量随着控制权结束后也就消失了,需要等到下次调用重新开始,然而yield 就是这么神奇可以执行到yield 关键字处然后将控制权交给调用者,然后在次进入函数从上次结束的地方继续执行不断循环

如何使用yield 实现协程(来自廖雪峰老师的例子)

import time

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'

def produce(c):
    c.__next__()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print("send.......")
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()
    
c = consumer()
produce(c)

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

首先调用c.next()启动生成器;

然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

consumer通过yield拿到消息,处理,又通过yield把结果传回;

produce拿到consumer处理的结果,继续生产下一条消息;

produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

20 闭包

在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。

闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。

当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:

  1. 必须有一个内嵌函数
  2. 内嵌函数必须引用外部函数中的变量
  3. 外部函数的返回值必须是内嵌函数

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

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

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

简单说,闭包就是根据不同的配置信息得到不同的结果, 装饰器就是一种闭包, 闭包有效的减少了函数所需定义的参数数目。

闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。

不同的语言实现闭包的方式不同。Python以函数对象为基础,为闭包这一语法结构提供支持的 (我们在特殊方法与多范式中,已经多次看到Python使用对象来实现一些特殊的语法)。Python一切皆对象,函数这一语法结构也是一个对象。在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。

def line_conf(a, b):  
    def line(x):  
        return a*x + b  
    return line  

  line1 = line_conf(1, 1)  
line2 = line_conf(4, 5)  
print(line1(5), line2(5))  
# (6, 25)  

例子中, 如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。

这个例子中,函数line与环境变量a,b构 成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

闭包的应用场景:闭包与并行运算 闭包有效的减少了函数所需定义的参数数目。这 对于并行运算来说有重要的意义。在并行运算的环境下,我们可以让每台电脑负责一个函数,然后将一台电脑的输出和下一台电脑的输入串联起来。最终,我们像流 水线一样工作,从串联的电脑集群一端输入数据,从另一端输出数据。这样的情境最适合只有一个参数输入的函数。闭包就可以实现这一目的。

并行运算正称为一个热点。这也是函数式编程又 热起来的一个重要原因。函数式编程早在1950年代就已经存在,但应用并不广泛。然而,我们上面描述的流水线式的工作并行集群过程,正适合函数式编程。由 于函数式编程这一天然优势,越来越多的语言也开始加入对函数式编程范式的支持。

21 lambda函数

lambda函数是匿名函数,使用lambda函数能创建小型匿名函数,这种函数得名于省略了用def声明函数的标准步骤

lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数

1.lambda函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下

2.匿名函数,一般用来给filter,map这样的函数式编程服务

3.作为回调函数,传递给某些应用,比如消息处理

简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。

先举一个普通的 Python 例子:将一个 list 里的每个元素都平方:map( lambda x: x*x, [y for y in range(10)] )这个写法要好过

def sq(x):
    return x * x

map(sq, [y for y in range(10)]),因为后者多定义了一个(污染环境的)函数,尤其如果这个函数只会使用一次的话。而且第一种写法实际上更易读,因为那个映射到列表上的函数具体是要做什么,非常一目了然。如果你仔细观察自己的代码,会发现这种场景其实很常见:你在某处就真的只需要一个能做一件事情的函数而已,连它叫什么名字都无关紧要。Lambda 表达式就可以用来做这件事。

进一步讲,匿名函数本质上就是一个函数,它所抽象出来的东西是一组运算。这是什么意思呢?类比a = [1, 2, 3]和f = lambda x : x + 1,你会发现,等号右边的东西完全可以脱离等号左边的东西而存在,等号左边的名字只是右边之实体的标识符。如果你能习惯 [1, 2, 3] 单独存在,那么 lambda x : x + 1 也能单独存在其实也就不难理解了,它的意义就是给「某个数加一」这一运算本身。

现在回头来看 map() 函数,它可以将一个函数映射到一个可枚举类型上面。沿用上面给出的 a 和 f,可以写:map(f, a)也就是将函数 f 依次套用在 a 的每一个元素上面,获得结果 [2, 3, 4]。现在用 lambda 表达式来替换 f,就变成:map( lambda x : x + 1, [1, 2, 3] )会不会觉得现在很一目了然了?尤其是类比

a = [1, 2, 3]
r = []
for each in a:
    r.append(each+1)
这样的写法时,你会发现自己如果能将「遍历列表,给遇到的每个元素都做某种运算」的过程从一个循环里抽象出来成为一个函数 map,然后用 lambda 表达式将这种运算作为参数传给 map 的话,考虑事情的思维层级会高出一些来,需要顾及的细节也少了一点。Python 之中,类似能用到 lambda 表达式的「高级」函数还有 reduce、filter 等等,很多语言也都有这样的工具(不过这些特性最好不要在 Python 中用太多)。

这种能够接受一个函数作为参数的函数叫做「高阶函数」(higher-order function),是来自函数式编程(functional programming)的**。和其他很多语言相比,Python 的 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']]

我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

而浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。浅拷贝是将对象的引用复制给另一个对象

浅拷贝只拷贝一层, 深拷贝拷贝全部,深拷贝是将对象本身复制给另一个对象

第一:非容器类型(不可变对象, 比如数字,字符串和其他原子类型的对象,例如代码,类型和range对象等)没有拷贝一说,浅拷贝是完全用切片操作来完成的。

第二:如果元组变量只包含原子类型对象,那么深拷贝将不会进行。

构造方法或切片 [:] 做的是浅拷贝(即拷贝了最外层容器,副本中的元素是原容器中元素的引用)。

24 Python垃圾回收机制

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。

1 引用计数

python创建新对象都是在内存上开辟一个块, 每个对象只存有一份数据, 赋值和复制都是创建了新的引用, 使用的是对象和引用分离策略

在Python中,每个对象都有存有指向该对象的引用总数,即引用计数, 如果引用计数为0, 那这个对象就会被python垃圾回收机制回收

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。

引用计数也是一种垃圾收集机制,而且也是一种最直观、最简单的垃圾收集技术。当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1,如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了。

优点:

  1. 简单
  2. 实时性

缺点:

  1. 维护引用计数消耗资源
  2. 循环引用

2 标记-清除机制

基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

同时为了保证效率, Python只会在垃圾达到一定阈值时,垃圾回收才会启动。

3 分代技术

这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾

分代回收的整体**是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。

Python默认定义了三代对象集合,索引数越大,对象存活时间越长。

举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

Python的内存管理机制及调优手段?

内存管理机制: 引用计数、垃圾回收、内存池

引用计数:引用计数是一种非常高效的内存管理手段,当一个Python对象被引用时其引用计数增加1,

当其不再被一个变量引用时则计数减1,当引用计数等于0时对象被删除。弱引用不会增加引用计数

垃圾回收(见上)

调优手段

1.手动垃圾回收

2.调高垃圾回收阈值

3.避免循环引用

25 Python的List

推荐: http://www.jianshu.com/p/J4U6rR

26 Python的is

python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。

is是对比地址,比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同

==是对比值,比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了。默认会调用对象的eq()方法

is 运算符比 == 效率高,在变量和None进行比较时,应该使用 is。

is 与 == 相比有一个比较大的优势,就是计算速度快,因为它不能重载,不用进行特殊的函数调用,少了函数调用的开销而直接比较两个整数 id。而 a == b 则是等同于a.eq(b)。继承自 object 的 eq 方法比较两个对象的id,结果与 is 一样。但是多数Python的对象会覆盖object的 __eq__方法,而定义内容的相关比较,所以比较的是对象属性的值。

在变量和单例值之间比较时,应该使用 is。目前,最常使用 is 的地方是判断对象是不是 None。下面是推荐的写法:

a is None 判断不是None的推荐写法是:

a is not None

27 read,readline和readlines

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

28 Python2和3的区别

编码/数据类型 3没有long 增加了bytes xrange / dict的.keys()、.items 和.values()方法返回迭代器 / 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方法浅见

重写是继承机制中的重要内容,对于构造方法尤为重要。构造方法用来初始化新建对象的状态,大多数子类不仅要有自己的初始化代码,还要拥有超类的初始化代码。如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象可能不会被正确的初始化–Python基础教程

经典类和新式类 经典类是python2.2之前的东西,但是在2.7还在兼容,但是在3之后的版本就只承认新式类了 新式类在python2.2之后的版本中都可以使用 为什么不用经典类 原因在于经典类在类多重继承的时候是采用从左到右深度优先原则匹配方法的,而新式类是采用C3算法(不同于广度优先)进行匹配的,经典类最容易出现的就是子类越过父类的方法,以下两个例子

# 当前,我们先定义一个大的父类,用Bird来创建,里面有个构造方法和定义了两个方法,分别是eat,sing方法,然后创建了一个Sparrow的子类,继承自父类Bird,自己也拥有构造方法,首先进行测试
class Bird:
    def __init__(self):
        print '我是父类的初始化'
        self.cansing = True
    def eat(self):
        print 'i can eat'

    def sing(self):
        if self.cansing:
            print 'i can sing'

class Sparrow(Bird):
    def __init__(self):
        print '我就是要重写父类的init'

    def jump(self):
        print 'i can jump'

S = Sparrow()
S.jump()
S.eat()
S.sing()

然后很喜闻乐见的报错了,因为子类重写了父类的构造方法,我的理解是被覆盖了而不是像append那样接上,所以结果如图,问题的原因在于Sparrow这个类已经重写了其父类的init,导致cansing的方法被绕过

我就是要重写父类的init

i can jump
i can eat
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-f4eaf99ef216> in <module>()
     21 S.jump()
     22 S.eat()
---> 23 S.sing()

<ipython-input-9-f4eaf99ef216> in sing(self)
      7 
      8     def sing(self):
----> 9         if self.cansing:
     10             print 'i can sing'
     11 

AttributeError: Sparrow instance has no attribute 'cansing'

我们再来看一个例子,关于经典类的深度优先查找的规则,看完你就知道为什么py3要使用新式类了

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()被绕过

补救经典类方法 方法一:按照类名访问 调用未绑定的超类构造方法,这个方法在python2.2使用较多,之后随着super出现后,被取缔,原因也有很多,这个具体自己百度吧。

class Bird:
    def __init__(self):
        print '我是父类的初始化'
        self.cansing = True
    def eat(self):
        print 'i can eat'

    def sing(self):
        if self.cansing:
            print 'i can sing'

class Sparrow(Bird):
    def __init__(self):
        Bird.__init__(self)
        print '我就是要重写父类的init'

    def jump(self):
        print 'i can jump'

S = Sparrow()
S.jump()
S.eat()
S.sing()

我是父类的初始化 我就是要重写父类的init i can jump i can eat i can sing

​ 这个方法个人来看并不推荐,因为如果要修改很多很多子类,若是父类名字一变,修改量太大,而且,对父类的访问着实很多次。

方法二:使用super 另一种方式就是使用super,记得这是个只能在新式类中才起作用,所以代码块前需要加

metaclass = type 1 完整代码块如下:

__metaclass__ = type
class Bird:
    def __init__(self):
        print '我是父类的初始化'
        self.cansing = True
    def eat(self):
        print 'i can eat'

    def sing(self):
        if self.cansing:
            print 'i can sing'

class Sparrow(Bird):
    def __init__(self):
        super(Sparrow,self).__init__()
        print '我就是要重写父类的init'

    def jump(self):
        print 'i can jump'

S = Sparrow()
S.jump()
S.eat()
S.sing()

​ 效果和上述采用未绑定的超类方法一样,但是更加直观。下面盗用一下为什么super方法那么超级: 即使类已经继承多个超类,它也只需要使用一次super函数

注意:建议就是要么一直用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.

xrange函数说明:语法上和range完全相同,所不同的是生成的不是一个数组,而是一个生成器。

如果循环时用range,由上面结果我们也可以看出,上来就会生成一个数组。若是元素个数少还可以接受,但是如果元素个数非常多,那岂不是要开辟很大的内存来存放这个数组?这对让内存空间亚历山大呀。

如果使用xrange,xrange返回的是一个生成器,一边循环一边计算,每次只返回一个值,这样就不必开辟这么大的内存空间了。

因此,在循环里尽量使用xrange吧,随着元素个数增多,xrange性能要比range好的多。

xrange函数在Python3中已经取消,在python3中,range()这种实现被移除了,保留了xrange()的实现,且将xrange()重新命名成range()。所以Python3不能使用xrange,只能使用range

http://stackoverflow.com/questions/94935/what-is-the-difference-between-range-and-xrange-functions-in-python-2-x

16.python中内置的数据结构有几种?

a. 整型 int、 长整型 long、浮点型 float、 复数 complex

b. 字符串 str、 列表 list、 元祖 tuple

c. 字典 dict 、 集合 set

d. Python3 中没有 long,只有无限精度的 int

23.可变类型和不可变类型

不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。

可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上其所指的值直接发生改变,并没有发生复制行为,也没有开辟出新的地址,通俗点说就是原地改变。

1,可变类型有list,dict.不可变类型有string,number,tuple.

2,当进行修改操作时,可变类型传递的是内存中的地址,也就是说,直接修改内存中的值,并没有开辟新的内存。

3,不可变类型被改变时,并没有改变原内存地址中的值,而是开辟一块新的内存,将原地址中的值复制过去,对这块新开辟的内存中的值进行操作。

Pyhton中,数值类型(int 和float),字符串str、元祖tuple都是不可变类型。而列表list、字典dict、集合set是可变类型

31.异常

如果我们没有对异常进行任何预防,那么在程序执行的过程中发生异常,就会中断程序,调用python默认的异常处理器,并在终端输出异常信息。

try...except...finally语句:当try语句执行时发生异常,回到try语句层,寻找后面是否有except语句。

找到except语句后,会调用这个自定义的异常处理器。except将异常处理完毕后,程序继续往下执行。finally语句表示,无论异常发生与否,finally中的语句都要执行。

assert语句:判断assert后面紧跟的语句是True还是False,如果是True则继续执行print,如果是False则中断程序,调用默认的异常处理器,同时输出assert语句逗号后面的提示信息。

with语句:如果with语句或语句块中发生异常,会调用默认的异常处理器处理,但文件还是会正常关闭。

有用过with statement吗?它的好处是什么?具体如何实现?

with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。

如果不用with语句,代码如下:

file = open("/tmp/foo.txt")
data = file.read()
file.close()

这里有两个问题:

一是可能忘记关闭文件句柄; 二是文件读取数据发生异常,没有进行任何处理。

with open("/tmp/foo.txt") as file:
    data = file.read()

with如何工作?

紧跟with后面的语句被求值后,返回对象的 enter() 方法被调用,这个方法的返回值将被赋值给as后面的变量。 当with后面的代码块全部被执行完之后,将调用前面返回对象的 exit()方法。

Python基础

文件操作

1.有一个jsonline格式的文件file.txt大小约为10K

def get_lines():
    with open('file.txt','rb') as f:
        return f.readlines()

if __name__ == '__main__':
    for e in get_lines():
        process(e) # 处理每一行数据

现在要处理一个大小为10G的文件,但是内存只有4G,如果在只修改get_lines 函数而其他代码保持不变的情况下,应该如何实现?需要考虑的问题都有那些?

def get_lines():
    with open('file.txt','rb') as f:
        for i in f:
            yield i

Pandaaaa906提供的方法

from mmap import mmap


def get_lines(fp):
    with open(fp,"r+") as f:
        m = mmap(f.fileno(), 0)
        tmp = 0
        for i, char in enumerate(m):
            if char==b"\n":
                yield m[tmp:i+1].decode()
                tmp = i+1

if __name__=="__main__":
    for i in get_lines("fp_some_huge_file"):
        print(i)

要考虑的问题有:内存只有4G无法一次性读入10G文件,需要分批读入分批读入数据要记录每次读入数据的位置。分批每次读取数据的大小,太小会在读取操作花费过多时间。 https://stackoverflow.com/questions/30294146/python-fastest-way-to-process-large-file

2.补充缺失的代码

def print_directory_contents(sPath):
"""
这个函数接收文件夹的名称作为输入参数
返回该文件夹中文件的路径
以及其包含文件夹中文件的路径
"""
import os
for s_child in os.listdir(s_path):
    s_child_path = os.path.join(s_path, s_child)
    if os.path.isdir(s_child_path):
        print_directory_contents(s_child_path)
    else:
        print(s_child_path)

模块与包

3.输入日期, 判断这一天是这一年的第几天?

import datetime
def dayofyear():
    year = input("请输入年份: ")
    month = input("请输入月份: ")
    day = input("请输入天: ")
    date1 = datetime.date(year=int(year),month=int(month),day=int(day))
    date2 = datetime.date(year=int(year),month=1,day=1)
    return (date1-date2).days+1

4.打乱一个排好序的list对象alist?

import random
alist = [1,2,3,4,5]
random.shuffle(alist)
print(alist)

数据类型

5.现有字典 d= {'a':24,'g':52,'i':12,'k':33}请按value值进行排序?

sorted(d.items(),key=lambda x:x[1])

6.字典推导式

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

7.请反转字符串 "aStr"?

print("aStr"[::-1])

8.将字符串 "k:1 |k1:2|k2:3|k3:4",处理成字典 {k:1,k1:2,...}

str1 = "k:1|k1:2|k2:3|k3:4"
def str2dict(str1):
    dict1 = {}
    for iterms in str1.split('|'):
        key,value = iterms.split(':')
        dict1[key] = value
    return dict1
#字典推导式
d = {k:int(v) for t in str1.split("|") for k, v in (t.split(":"), )}

9.请按alist中元素的age由大到小排序

alist = [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]
def sort_by_age(list1):
    return sorted(alist,key=lambda x:x['age'],reverse=True)

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

list = ['a','b','c','d','e']
print(list[10:])

代码将输出[],不会产生IndexError错误,就像所期望的那样,尝试用超出成员的个数的index来获取某个列表的成员。例如,尝试获取list[10]和之后的成员,会导致IndexError。然而,尝试获取列表的切片,开始的index超过了成员个数不会产生IndexError,而是仅仅返回一个空列表。这成为特别让人恶心的疑难杂症,因为运行的时候没有错误产生,导致Bug很难被追踪到。

11.写一个列表生成式,产生一个公差为11的等差数列

print([x*11 for x in range(10)])

12.给定两个列表,怎么找出他们相同的元素和不同的元素?

list1 = [1,2,3]
list2 = [3,4,5]
set1 = set(list1)
set2 = set(list2)
print(set1 & set2)
print(set1 ^ set2)

13.请写出一段python代码实现删除list里面的重复元素?

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

用list类的sort方法:

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

也可以这样写:

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

也可以用遍历:

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

14.给定两个list A,B ,请用找出A,B中相同与不同的元素

A,B 中相同元素print(set(A)&set(B))
A,B 中不同元素:  print(set(A)^set(B))

企业面试题

18.反转一个整数,例如-123 --> -321

class Solution(object):
    def reverse(self,x):
        if -10<x<10:
            return x
        str_x = str(x)
        if str_x[0] !="-":
            str_x = str_x[::-1]
            x = int(str_x)
        else:
            str_x = str_x[1:][::-1]
            x = int(str_x)
            x = -x
        return x if -2147483648<x<2147483647 else 0
if __name__ == '__main__':
    s = Solution()
    reverse_int = s.reverse(-120)
    print(reverse_int)

19.设计实现遍历目录与子目录,抓取.pyc文件

第一种方法:

import os

def get_files(dir,suffix):
    res = []
    for root,dirs,files in os.walk(dir):
        for filename in files:
            name,suf = os.path.splitext(filename)
            if suf == suffix:
                res.append(os.path.join(root,filename))

    print(res)

get_files("./",'.pyc')

第二种方法:

import os

def pick(obj):
    if ob.endswith(".pyc"):
        print(obj)
    
def scan_path(ph):
    file_list = os.listdir(ph)
    for obj in file_list:
        if os.path.isfile(obj):
    pick(obj)
        elif os.path.isdir(obj):
            scan_path(obj)
    
if __name__=='__main__':
    path = input('输入目录')
    scan_path(path)

第三种方法

from glob import iglob


def func(fp, postfix):
    for i in iglob(f"{fp}/**/*{postfix}", recursive=True):
        print(i)

if __name__ == "__main__":
    postfix = ".pyc"
    func("K:\Python_script", postfix)

20.一行代码实现1-100之和

count = sum(range(0,101))
print(count)

21.Python-遍历列表时删除元素的正确做法

遍历在新在列表操作,删除时在原来的列表操作

a = [1,2,3,4,5,6,7,8]
print(id(a))
print(id(a[:]))
for i in a[:]:
    if i>5:
        pass
    else:
        a.remove(i)
    print(a)
print('-----------')
print(id(a))
#filter
a=[1,2,3,4,5,6,7,8]
b = filter(lambda x: x>5,a)
print(list(b))

列表解析

a=[1,2,3,4,5,6,7,8]
b = [i for i in a if i>5]
print(b)

倒序删除 因为列表总是‘向前移’,所以可以倒序遍历,即使后面的元素被修改了,还没有被遍历的元素和其坐标还是保持不变的

a=[1,2,3,4,5,6,7,8]
print(id(a))
for i in range(len(a)-1,-1,-1):
    if a[i]>5:
        pass
    else:
        a.remove(a[i])
print(id(a))
print('-----------')
print(a)

22.字符串的操作题目

全字母短句 PANGRAM 是包含所有英文字母的句子,比如:A QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 定义并实现一个方法 get_missing_letter, 传入一个字符串采纳数,返回参数字符串变成一个 PANGRAM 中所缺失的字符。应该忽略传入字符串参数中的大小写,返回应该都是小写字符并按字母顺序排序(请忽略所有非 ACSII 字符)

下面示例是用来解释,双引号不需要考虑:

(0)输入: "A quick brown for jumps over the lazy dog"

返回: ""

(1)输入: "A slow yellow fox crawls under the proactive dog"

返回: "bjkmqz"

(2)输入: "Lions, and tigers, and bears, oh my!"

返回: "cfjkpquvwxz"

(3)输入: ""

返回:"abcdefghijklmnopqrstuvwxyz"

def get_missing_letter(a):
    s1 = set("abcdefghijklmnopqrstuvwxyz")
    s2 = set(a.lower())
    ret = "".join(sorted(s1-s2))
    return ret
    
print(get_missing_letter("python"))

# other ways to generate letters
# range("a", "z")
# 方法一:
import string
letters = string.ascii_lowercase
# 方法二:
letters = "".join(map(chr, range(ord('a'), ord('z') + 1)))

25.求出列表所有奇数并构造新列表

a = [1,2,3,4,5,6,7,8,9,10]
res = [ i for i in a if i%2==1]
print(res)

26.用一行python代码写出1+2+3+10248

from functools import reduce
#1.使用sum内置求和函数
num = sum([1,2,3,10248])
print(num)
#2.reduce 函数
num1 = reduce(lambda x,y :x+y,[1,2,3,10248])
print(num1)

28.字符串 "123" 转换成 123,不使用内置api,例如 int()

方法一: 利用 str 函数

def atoi(s):
    num = 0
    for v in s:
        for j in range(10):
            if v == str(j):
                num = num * 10 + j
    return num

方法二: 利用 ord 函数

def atoi(s):
    num = 0
    for v in s:
        num = num * 10 + ord(v) - ord('0')
    return num

方法三: 利用 eval 函数

def atoi(s):
    num = 0
    for v in s:
        t = "%s * 1" % v
        n = eval(t)
        num = num * 10 + n
    return num

方法四: 结合方法二,使用 reduce,一行解决

from functools import reduce
def atoi(s):
    return reduce(lambda num, v: num * 10 + ord(v) - ord('0'), s, 0)

29.Given an array of integers

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。示例:给定nums = [2,7,11,15],target=9 因为 nums[0]+nums[1] = 2+7 =9,所以返回[0,1]

class Solution:
    def twoSum(self,nums,target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        d = {}
        size = 0
        while size < len(nums):
            if target-nums[size] in d:
                if d[target-nums[size]] <size:
                    return [d[target-nums[size]],size]
                else:
                    d[nums[size]] = size
                size = size +1
solution = Solution()
list = [2,7,11,15]
target = 9
nums = solution.twoSum(list,target)
print(nums)

给列表中的字典排序:假设有如下list对象,alist=[{"name":"a","age":20},{"name":"b","age":30},{"name":"c","age":25}],将alist中的元素按照age从大到小排序 alist=[{"name":"a","age":20},{"name":"b","age":30},{"name":"c","age":25}]

alist_sort = sorted(alist,key=lambda e: e.__getitem__('age'),reverse=True)

30.python代码实现删除一个list里面的重复元素

def distFunc1(a):
    """使用集合去重"""
    a = list(set(a))
    print(a)

def distFunc2(a):
    """将一个列表的数据取出放到另一个列表中,中间作判断"""
    list = []
    for i in a:
        if i not in list:
            list.append(i)
    #如果需要排序的话用sort
    list.sort()
    print(list)

def distFunc3(a):
    """使用字典"""
    b = {}
    b = b.fromkeys(a)
    c = list(b.keys())
    print(c)

if __name__ == "__main__":
    a = [1,2,4,2,4,5,7,10,5,5,7,8,9,0,3]
    distFunc1(a)
    distFunc2(a)
    distFunc3(a)
  

31.统计一个文本中单词频次最高的10个单词?

import re

# 方法一
def test(filepath):
    
    distone = {}

    with open(filepath) as f:
        for line in f:
            line = re.sub("\W+", " ", line)
            lineone = line.split()
            for keyone in lineone:
                if not distone.get(keyone):
                    distone[keyone] = 1
                else:
                    distone[keyone] += 1
    num_ten = sorted(distone.items(), key=lambda x:x[1], reverse=True)[:10]
    num_ten =[x[0] for x in num_ten]
    return num_ten
    
 
# 方法二 
# 使用 built-in 的 Counter 里面的 most_common
import re
from collections import Counter


def test2(filepath):
    with open(filepath) as f:
        return list(map(lambda c: c[0], Counter(re.sub("\W+", " ", f.read()).split()).most_common(10)))

32.请写出一个函数满足以下条件

该函数的输入是一个仅包含数字的list,输出一个新的list,其中每一个元素要满足以下条件:

1、该元素是偶数

2、该元素在原list中是在偶数的位置(index是偶数)

def num_list(num):
    return [i for i in num if i %2 ==0 and num.index(i)%2==0]

num = [0,1,2,3,4,5,6,7,8,9,10]
result = num_list(num)
print(result)

33.使用单一的列表生成式来产生一个新的列表

该列表只包含满足以下条件的值,元素为原始列表中偶数切片

list_data = [1,2,5,8,10,3,18,6,20]
res = [x for x in list_data[::2] if x %2 ==0]
print(res)

34.用一行代码生成[1,4,9,16,25,36,49,64,81,100]

[x * x for x in range(1,11)]

35.输入某年某月某日,判断这一天是这一年的第几天?

import datetime

y = int(input("请输入4位数字的年份:"))
m = int(input("请输入月份:"))
d = int(input("请输入是哪一天"))

targetDay = datetime.date(y,m,d)
dayCount = targetDay - datetime.date(targetDay.year -1,12,31)
print("%s是 %s年的第%s天。"%(targetDay,y,dayCount.days))

36.两个有序列表,l1,l2,对这两个列表进行合并不可使用extend

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]
    while len(l1)>0:
        tmp.append(l1[0])
        del l1[0]
    while len(l2)>0:
        tmp.append(l2[0])
        del l2[0]
    return tmp

37.给定一个任意长度数组,实现一个函数

让所有奇数都在偶数前面,而且奇数升序排列,偶数降序排序,如字符串'1982376455',变成'1355798642'

# 方法一
def func1(l):
    if isinstance(l, str):
        l = [int(i) for i in l]
    l.sort(reverse=True)
    for i in range(len(l)):
        if l[i] % 2 > 0:
            l.insert(0, l.pop(i))
    print(''.join(str(e) for e in l))

# 方法二
def func2(l):
    print("".join(sorted(l, key=lambda x: int(x) % 2 == 0 and 20 - int(x) or int(x))))

38.写一个函数找出一个整数数组中,第二大的数

def find_second_large_num(num_list):
    """
    找出数组第2大的数字
    """
    # 方法一
    # 直接排序,输出倒数第二个数即可
    tmp_list = sorted(num_list)
    print("方法一\nSecond_large_num is :", tmp_list[-2])
    
    # 方法二
    # 设置两个标志位一个存储最大数一个存储次大数
    # two 存储次大值,one 存储最大值,遍历一次数组即可,先判断是否大于 one,若大于将 one 的值给 two,将 num_list[i] 的值给 one,否则比较是否大于two,若大于直接将 num_list[i] 的值给two,否则pass
    one = num_list[0]
    two = num_list[0]
    for i in range(1, len(num_list)):
        if num_list[i] > one:
            two = one
            one = num_list[i]
        elif num_list[i] > two:
            two = num_list[i]
    print("方法二\nSecond_large_num is :", two)
    
    # 方法三
    # 用 reduce 与逻辑符号 (and, or)
    # 基本思路与方法二一样,但是不需要用 if 进行判断。
    from functools import reduce
    num = reduce(lambda ot, x: ot[1] < x and (ot[1], x) or ot[0] < x and (x, ot[1]) or ot, num_list, (0, 0))[0]
    print("方法三\nSecond_large_num is :", num)
    
    
if __name__ == '__main___':
    num_list = [34, 11, 23, 56, 78, 0, 9, 12, 3, 7, 5]
    find_second_large_num(num_list)

39.阅读一下代码他们的输出结果是什么?

def multi():
    return [lambda x : i*x for i in range(4)]
print([m(3) for m in multi()])

正确答案是[9,9,9,9],而不是[0,3,6,9]产生的原因是Python的闭包的后期绑定导致的,这意味着在闭包中的变量是在内部函数被调用的时候被查找的,因为,最后函数被调用的时候,for循环已经完成, i 的值最后是3,因此每一个返回值的i都是3,所以最后的结果是[9,9,9,9]

40.统计一段字符串中字符出现的次数

# 方法一
def count_str(str_data):
    """定义一个字符出现次数的函数"""
    dict_str = {} 
    for i in str_data:
        dict_str[i] = dict_str.get(i, 0) + 1
    return dict_str
dict_str = count_str("AAABBCCAC")
str_count_data = ""
for k, v in dict_str.items():
    str_count_data += k + str(v)
print(str_count_data)

# 方法二
from collections import Counter

print("".join(map(lambda x: x[0] + str(x[1]), Counter("AAABBCCAC").most_common())))

41.super函数的具体用法和场景

https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html

Python高级

元类

42.Python中类方法、类实例方法、静态方法有何区别?

类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用

类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;

静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系

43.遍历一个object的所有属性,并print每一个属性名?

class Car:
    def __init__(self,name,loss): # loss [价格,油耗,公里数]
        self.name = name
        self.loss = loss
    
    def getName(self):
        return self.name
    
    def getPrice(self):
        # 获取汽车价格
        return self.loss[0]
    
    def getLoss(self):
        # 获取汽车损耗值
        return self.loss[1] * self.loss[2]

Bmw = Car("宝马",[60,9,500]) # 实例化一个宝马车对象
print(getattr(Bmw,"name")) # 使用getattr()传入对象名字,属性值。
print(dir(Bmw)) # 获Bmw所有的属性和方法

44.写一个类,并让它尽可能多的支持操作符?

class Array:
    __list = []
    
    def __init__(self):
        print "constructor"
    
    def __del__(self):
        print "destruct"
    
    def __str__(self):
        return "this self-defined array class"

    def __getitem__(self,key):
        return self.__list[key]
    
    def __len__(self):
        return len(self.__list)

    def Add(self,value):
        self.__list.append(value)
    
    def Remove(self,index):
        del self.__list[index]
    
    def DisplayItems(self):
        print "show all items---"
        for item in self.__list:
            print item
    
        

45.介绍Cython,Pypy Cpython Numba各有什么缺点

Cython

46.请描述抽象类和接口类的区别和联系

1.抽象类: 规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。可以将抽象类理解为毛坯房,门窗,墙面的样式由你自己来定,所以抽象类与作为基类的普通类的区别在于约束性更强

2.接口类:与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本区别在于用途:与不同个体间沟通的规则,你要进宿舍需要有钥匙,这个钥匙就是你与宿舍的接口,你的舍友也有这个接口,所以他也能进入宿舍,你用手机通话,那么手机就是你与他人交流的接口

3.区别和关联:

1.接口是抽象类的变体,接口中所有的方法都是抽象的,而抽象类中可以有非抽象方法,抽象类是声明方法的存在而不去实现它的类

2.接口可以继承,抽象类不行

3.接口定义方法,没有实现的代码,而抽象类可以实现部分方法

4.接口中基本数据类型为static而抽象类不是

47.Python中如何动态获取和设置对象的属性?

if hasattr(Parent, 'x'):
    print(getattr(Parent, 'x'))
    setattr(Parent, 'x',3)
print(getattr(Parent,'x'))

48.哪些操作会导致Python内存溢出,怎么处理?

调试主要是用gc.set_debug(gc.DEBUG_UNCOLLECTABLE)等方法,查看有哪些变量没有被回收。

在Django 中project的setting.py里添加代码打印日志调节。

内存溢出的原因可能有两个:

对象被更长生命周期的对象所引用,不得释放。之前好像是views.py里的处理request函数中没有释放其中的变量引起了,使用了del后确实降了内存。

重写了对象的del函数,不过这个自从Python的3.4版本后不管是否重新都会被回收。

内存溢出原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

2.集合类中有对对象的引用,使用完后未清空,产生了堆积,使得JVM不能回收;

3.代码中存在死循环或循环产生过多重复的对象实体;

4.使用的第三方软件中的BUG;

5.启动参数内存值设定的过小

内存溢出的解决方案: 第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

49.关于Python内存管理,下列说法错误的是 B

A,变量不必事先声明 B,变量无须先创建和赋值而直接使用

C,变量无须指定类型 D,可以使用del释放资源

51.内存泄露是什么?如何避免?

内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

__del__()函数的对象间的循环引用是导致内存泄露的主凶。不使用一个对象时使用: del object 来删除一个对象的引用计数就可以有效防止内存泄露问题。

通过Python扩展模块gc 来查看不能回收的对象的详细信息。

可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为0来判断是否内存泄露

函数

52.python常见的列表推导式?

[表达式 for 变量 in 列表] 或者 [表达式 for 变量 in 列表 if 条件]

53.简述read、readline、readlines的区别?

read 读取整个文件

readline 读取下一行

readlines 读取整个文件到一个迭代器以供我们遍历

54.什么是Hash(散列函数)?

散列函数(英语:Hash function)又称散列算法哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表

55.python函数重载机制?

函数重载主要是为了解决两个问题。 1。可变参数类型。 2。可变参数个数。

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。

56.写一个函数找出一个整数数组中,第二大的数

57.手写一个判断时间的装饰器

import datetime


class TimeException(Exception):
    def __init__(self, exception_info):
        super().__init__()
        self.info = exception_info

    def __str__(self):
        return self.info


def timecheck(func):
    def wrapper(*args, **kwargs):
        if datetime.datetime.now().year == 2019:
            func(*args, **kwargs)
        else:
            raise TimeException("函数已过时")

    return wrapper


@timecheck
def test(name):
    print("Hello {}, 2019 Happy".format(name))


if __name__ == "__main__":
    test("backbp")

58.使用Python内置的filter()方法来过滤?

list(filter(lambda x: x % 2 == 0, range(10)))

59.编写函数的4个原则

1.函数设计要尽量短小

2.函数声明要做到合理、简单、易于使用

3.函数参数设计应该考虑向下兼容

4.一个函数只做一件事情,尽量保证函数语句粒度的一致性

60.函数调用参数的传递方式是值传递还是引用传递?

Python的参数传递有:位置参数、默认参数、可变参数、关键字参数。

函数的传值到底是值传递还是引用传递、要分情况:

不可变参数用值传递:像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象。

可变参数是引用传递:比如像列表,字典这样的对象是通过引用传递、和C语言里面的用指针传递数组很相似,可变对象能在函数内部改变。

61.如何在function里面设置一个全局变量

globals() # 返回包含当前作用余全局变量的字典。
global 变量 设置使用全局变量

62.对缺省参数的理解 ?

缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。

*args是不定长参数,它可以表示输入参数是不确定的,可以是任意多个。

**kwargs是关键字参数,赋值的时候是以键值对的方式,参数可以是任意多对在定义函数的时候

不确定会有多少参数会传入时,就可以使用两个参数

63.Mysql怎么限制IP访问?

64.带参数的装饰器?

带定长参数的装饰器

def new_func(func):
    def wrappedfun(username, passwd):
        if username == 'root' and passwd == '123456789':
            print('通过认证')
            print('开始执行附加功能')
            return func()
       	else:
            print('用户名或密码错误')
            return
    return wrappedfun

@new_func
def origin():
    print('开始执行函数')
origin('root','123456789')

带不定长参数的装饰器

def new_func(func):
    def wrappedfun(*parts):
        if parts:
            counts = len(parts)
            print('本系统包含 ', end='')
            for part in parts:
                print(part, ' ',end='')
            print('等', counts, '部分')
            return func()
        else:
            print('用户名或密码错误')
            return func()
   return wrappedfun

65.为什么函数名字可以当做参数用?

Python中一切皆对象,函数名是函数在内存中的空间,也是一个对象

66.Python中pass语句的作用是什么?

在编写代码时只写框架思路,具体实现还未编写就可以用pass进行占位,是程序不报错,不会进行任何操作。

67.有这样一段代码,print c会输出什么,为什么?

a = 10
b = 20
c = [a]
a = 15

答:10对于字符串,数字,传递是相应的值

68.交换两个变量的值?

a, b = b, a

69.map函数和reduce函数?

map(lambda x: x * x, [1, 2, 3, 4])   # 使用 lambda
# [1, 4, 9, 16]
reduce(lambda x, y: x * y, [1, 2, 3, 4])  # 相当于 ((1 * 2) * 3) * 4
# 24

70.回调函数,如何通信的?

回调函数是把函数的指针(地址)作为参数传递给另一个函数,将整个函数当作一个对象,赋值给调用的函数。

71.Python主要的内置数据类型都有哪些? print dir( ‘a ’) 的输出?

内建类型:布尔类型,数字,字符串,列表,元组,字典,集合

输出字符串'a'的内建方法

72.map(lambda x:xx,[y for y in range(3)])的输出?

[0, 1, 4]

73.hasattr() getattr() setattr() 函数使用详解?

hasattr(object,name)函数:

判断一个对象里面是否有name属性或者name方法,返回bool值,有name属性(方法)返回True,否则返回False。

class function_demo(object):
    name = 'demo'
    def run(self):
        return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, "name") # 判断对象是否有name属性,True
res = hasattr(functiondemo, "run") # 判断对象是否有run方法,True
res = hasattr(functiondemo, "age") # 判断对象是否有age属性,False
print(res)

getattr(object, name[,default])函数:

获取对象object的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号().

functiondemo = function_demo()
getattr(functiondemo, "name")# 获取name属性,存在就打印出来 --- demo
getattr(functiondemo, "run") # 获取run 方法,存在打印出方法的内存地址
getattr(functiondemo, "age") # 获取不存在的属性,报错
getattr(functiondemo, "age", 18)# 获取不存在的属性,返回一个默认值

setattr(object, name, values)函数:

给对象的属性赋值,若属性不存在,先创建再赋值

class function_demo(object):
    name = "demo"
    def run(self):
        return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, "age") # 判断age属性是否存在,False
print(res)
setattr(functiondemo, "age", 18) # 对age属性进行赋值,无返回值
res1 = hasattr(functiondemo, "age") # 再次判断属性是否存在,True

综合使用

class function_demo(object):
    name = "demo"
    def run(self):
        return "hello function"
functiondemo = function_demo()
res = hasattr(functiondemo, "addr") # 先判断是否存在
if res:
    addr = getattr(functiondemo, "addr")
    print(addr)
else:
    addr = getattr(functiondemo, "addr", setattr(functiondemo, "addr", "北京首都"))
    print(addr)

74.一句话解决阶乘函数?

reduce(lambda x,y : x*y,range(1,n+1))

76.递归函数停止的条件?

递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是return,,返回终止递归。

终止的条件:判断递归的次数是否达到某一限定值

2.判断运算的结果是否达到某个范围等,根据设计的目的来选择

77.下面这段代码的输出结果将是什么?请解释。

def multipliers():
    return [lambda x: i *x for i in range(4)]
	print([m(2) for m in multipliers()])

上面代码的输出结果是[6,6,6,6],不是我们想的[0,2,4,6]

你如何修改上面的multipliers的定义产生想要的结果?

上述问题产生的原因是python闭包的延迟绑定。这意味着内部函数被调用时,参数的值在闭包内进行查找。因此,当任何由multipliers()返回的函数被调用时,i的值将在附近的范围进行查找。那时,不管返回的函数是否被调用,for循环已经完成,i被赋予了最终的值3.

def multipliers():
    for i in range(4):
        yield lambda x: i *x
def multipliers():
    return [lambda x,i = i: i*x for i in range(4)]

91.Python的魔法方法

魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python所调用,你可以定义自己想要的行为,而这一切都是自动发生的,它们经常是两个下划线包围来命名的(比如__init___,__len__),Python的魔法方法是非常强大的所以了解其使用方法也变得尤为重要!

__init__构造器,当一个实例被创建的时候初始化的方法,但是它并不是实例化调用的第一个方法。

__new__才是实例化对象调用的第一个方法,它只取下cls参数,并把其他参数传给__init___.

___new__很少使用,但是也有它适合的场景,尤其是当类继承自一个像元祖或者字符串这样不经常改变的类型的时候。

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

__getitem__定义获取容器中指定元素的行为,相当于self[key]

__getattr__定义当用户试图访问一个不存在属性的时候的行为。

__setattr__定义当一个属性被设置的时候的行为

__getattribute___定义当一个属性被访问的时候的行为

92.面向对象中怎么实现只读属性?

将对象私有化,通过共有方法提供一个读取数据的接口

class person:
    def __init__(self, x):
        self.__age = 10
    def age(self):
        return self.__age
t = person(22)
# t.__age =100
print(t.age())

最好的方法

class MyCls(object):
    __weight = 50
    
    @property
    def weight(self):
        return self.__weight
   

93.谈谈你对面向对象的理解?

面向对象是相当于面向过程而言的,面向过程语言是一种基于功能分析的,以算法为中心的程序设计方法,而面向对象是一种基于结构分析的,以数据为中心的程序设计**。在面向对象语言中有一个很重要的东西,叫做类。面向对象有三大特性:封装、继承、多态。

正则表达式

94.请写出一段代码用正则匹配出ip?

95.a = “abbbccc”,用正则匹配为abccc,不管有多少b,就出现一次?

96.Python字符串查找和替换?

97.用Python匹配HTML g tag的时候,<.> 和 <.*?> 有什么区别

98.正则表达式贪婪与非贪婪模式的区别?

99.写出开头匹配字母和下划线,末尾是数字的正则表达式?

100.正则表达式操作

101.请匹配出变量A 中的json字符串。

102.怎么过滤评论中的表情?

103.简述Python里面search和match的区别

match()函数只检测字符串开头位置是否匹配,匹配成功才会返回结果,否则返回None

import re
print(re.match("func", "function"))
# 打印结果 <_sre.SRE_Match object; span=(0, 4), match='func'>

print(re.match("func", "function").span())
# 打印结果  (0, 4)

print(re.match("func1", "function"))
# 打印结果 None

注意print(re.match("func1", "function").span())会报错因为取不到span
   

search()函数会在整个字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

import re
print(re.search("tion", "function"))
# 打印结果 <_sre.SRE_Match object; span=(4, 8), match='tion'>

print(re.search("tion", "function").span())
# 打印结果  (4, 8)

print(re.search("tion1", "function"))
# 打印结果 None

注意print(re.search("tion1", "function").span())会报错因为取不到tion1
   

系统编程

106.进程总结

进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间片、pid。 创建进程: 首先要导入multiprocessing中的Process: 创建一个Process对象; 创建Process对象时,可以传递参数;

p = Process(target=XXX,args=(tuple,),kwargs={key:value})
target = XXX 指定的任务函数不用加(),
args=(tuple,)kwargs={key:value}给任务函数传递的参数

使用start()启动进程 结束进程 给子进程指定函数传递参数Demo

import os
from mulitprocessing import Process
import time

def pro_func(name,age,**kwargs):
    for i in range(5):
        print("子进程正在运行中,name=%s,age=%d,pid=%d"%(name,age,os.getpid()))
        print(kwargs)
        time.sleep(0.2)
if __name__ =="__main__":
    #创建Process对象
    p = Process(target=pro_func,args=('小明',18),kwargs={'m':20})
    #启动进程
    p.start()
    time.sleep(1)
    #1秒钟之后,立刻结束子进程
    p.terminate()
    p.join()

注意:进程间不共享全局变量

进程之间的通信-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"异常:

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 os,time,random
#写数据进程执行的代码:
def write(q):
    for value in ['A','B','C']:
        print("Put %s to queue...",%value)
        q.put(value)
        time.sleep(random.random())
#读数据进程执行的代码
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print("Get %s from queue.",%value)
            time.sleep(random.random())
        else:
            break
if __name__=='__main__':
    #父进程创建Queue,并传给各个子进程
    q = Queue()
    pw = Process(target=write,args=(q,))
    pr = Process(target=read,args=(q,))
    #启动子进程pw ,写入:
    pw.start()
    #等待pw结束
    pw.join()
    #启动子进程pr,读取:
    pr.start()
    pr.join()
    #pr 进程里是死循环,无法等待其结束,只能强行终止:
    print('')
    print('所有数据都写入并且读完')
进程池Pool
#coding:utf-8
from multiprocessing import Pool
import os,time,random

def worker(msg):
    t_start = time.time()
    print("%s 开始执行,进程号为%d"%(msg,os.getpid()))
    # random.random()随机生成0-1之间的浮点数
    time.sleep(random.random()*2)
    t_stop = time.time()
    print(msg,"执行完毕耗时%0.2f%t_stop-t_start))

po = Pool(3)#定义一个进程池,最大进程数3
for i in range(0,10):
    po.apply_async(worker,(i,))
print("---start----")
po.close()
po.join()
print("----end----")

进程池中使用Queue

如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会得到如下的错误信息:

RuntimeError: Queue objects should only be shared between processs through inheritance

from multiprocessing import Manager,Pool
import os,time,random
def reader(q):
    print("reader 启动(%s),父进程为(%s)"%(os.getpid(),os.getpid()))
    for i in range(q.qsize()):
        print("reader 从Queue获取到消息:%s"%q.get(True))

def writer(q):
    print("writer 启动(%s),父进程为(%s)"%(os.getpid(),os.getpid()))
    for i ini "itcast":
        q.put(i)
if __name__ == "__main__":
    print("(%s)start"%os.getpid())
    q = Manager().Queue()#使用Manager中的Queue
    po = Pool()
    po.apply_async(wrtier,(q,))
    time.sleep(1)
    po.apply_async(reader,(q,))
    po.close()
    po.join()
    print("(%s)End"%os.getpid())

107.谈谈你对多进程,多线程,以及协程的理解,项目是否用?

这个问题被问的概念相当之大, 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。

线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。

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

108.Python异常使用场景有那些?

异步的使用场景:

1、 不涉及共享资源,获对共享资源只读,即非互斥操作

2、 没有时序上的严格关系

3、 不需要原子操作,或可以通过其他方式控制原子性

4、 常用于IO操作等耗时操作,因为比较影响客户体验和使用性能

5、 不影响主线程逻辑

109.多线程共同操作同一个数据互斥锁同步?

import threading
import time
class MyThread(threading.Thread):
    def run(self):
        global num
        time.sleep(1)
    
        if mutex.acquire(1):
            num +=1
            msg = self.name + 'set num to ' +str(num)
            print msg
            mutex.release()
num = 0
mutex = threading.Lock()
def test():
    for i in range(5):
        t = MyThread()
        t.start()
if __name__=="__main__":
    test()

110.什么是多线程竞争?

线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全

那么怎么解决多线程竞争问题?---锁

锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。

锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了

锁的致命问题: 死锁

111.请介绍一下Python的线程同步?

一、 setDaemon(False) 当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行的最小单位,当设置多线程时,主线程会创建多个子线程,在Python中,默认情况下就是setDaemon(False),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。

例子

import threading 
import time

def thread():
    time.sleep(2)
    print('---子线程结束---')

def main():
    t1 = threading.Thread(target=thread)
    t1.start()
    print('---主线程--结束')

if __name__ =='__main__':
    main()
#执行结果
---主线程--结束
---子线程结束---

二、 setDaemon(True) 当我们使用setDaemon(True)时,这是子线程为守护线程,主线程一旦执行结束,则全部子线程被强制终止

例子

import threading
import time
def thread():
    time.sleep(2)
    print(’---子线程结束---')
def main():
    t1 = threading.Thread(target=thread)
    t1.setDaemon(True)#设置子线程守护主线程
    t1.start()
    print('---主线程结束---')

if __name__ =='__main__':
    main()
#执行结果
---主线程结束--- #只有主线程结束,子线程来不及执行就被强制结束

三、 join(线程同步) join 所完成的工作就是线程同步,即主线程任务结束以后,进入堵塞状态,一直等待所有的子线程结束以后,主线程再终止。

当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序,所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和,简单的来说,就是给每个子线程一个timeou的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。

没有设置守护线程时,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。

例子

import threading
import time

def thread():
    time.sleep(2)
    print('---子线程结束---')

def main():
    t1 = threading.Thread(target=thread)
    t1.setDaemon(True)
    t1.start()
    t1.join(timeout=1)#1 线程同步,主线程堵塞1s 然后主线程结束,子线程继续执行
                        #2 如果不设置timeout参数就等子线程结束主线程再结束
                        #3 如果设置了setDaemon=True和timeout=1主线程等待1s后会强制杀死子线程,然后主线程结束
    print('---主线程结束---')

if __name__=='__main___':
    main()

112.解释以下什么是锁,有哪几种锁?

锁(Lock)是python提供的对线程控制的对象。有互斥锁,可重入锁,死锁。

113.什么是死锁?

若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。

GIL锁 全局解释器锁

作用: 限制多线程同时执行,保证同一时间只有一个线程执行,所以cython里的多线程其实是伪多线程!

所以python里常常使用协程技术来代替多线程,协程是一种更轻量级的线程。

进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent下切换是遇到了耗时操作时才会切换

三者的关系:进程里有线程,线程里有协程。

114.多线程交互访问数据,如果访问到了就不访问了?

怎么避免重读?

创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候先查看数据是否在已访问的列表中,若已存在就直接跳过。

115.什么是线程安全,什么是互斥锁?

每个对象都对应于一个可称为’互斥锁‘的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

同一进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。

116.说说下面几个概念:同步,异步,阻塞,非阻塞?

同步: 多个任务之间有先后顺序执行,一个执行完下个才能执行。

异步: 多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!

阻塞: 如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。

非阻塞: 如果不会卡住,可以继续执行,就是说非阻塞的。

同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。

117.什么是僵尸进程和孤儿进程?怎么避免僵尸进程?

孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进程号为1)所收养,并由init 进程对他们完成状态收集工作。

僵尸进程: 进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用wait 获waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。

避免僵尸进程的方法:

1.fork 两次用孙子进程去完成子进程的任务

2.用wait()函数使父进程阻塞

3.使用信号量,在signal handler 中调用waitpid,这样父进程不用阻塞

118.python中进程与线程的使用场景?

多进程适合在CPU密集操作(cpu操作指令比较多,如位多的的浮点运算)。

多线程适合在IO密性型操作(读写数据操作比多的的,比如爬虫)

119.线程是并发还是并行,进程是并发还是并行?

线程是并发,进程是并行;

进程之间互相独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。

120.并行(parallel)和并发(concurrency)?

并行: 同一时刻多个任务同时在运行

不会在同一时刻同时运行,存在交替执行的情况。

实现并行的库有: multiprocessing

实现并发的库有: threading

程序需要执行较多的读写、请求和回复任务的需要大量的IO操作,IO密集型操作使用并发更好。

CPU运算量大的程序,使用并行会更好

121.IO密集型和CPU密集型区别?

IO密集型: 系统运行,大部分的状况是CPU在等 I/O(硬盘/内存)的读/写

CPU密集型: 大部分时间用来做计算,逻辑判断等CPU动作的程序称之CPU密集型。

122.python asyncio的原理?

asyncio这个库就是使用python的yield这个可以打断保存当前函数的上下文的机制, 封装好了selector 摆脱掉了复杂的回调关系

Web

Flask

140.对Flask蓝图(Blueprint)的理解?

蓝图的定义

蓝图 /Blueprint 是Flask应用程序组件化的方法,可以在一个应用内或跨越多个项目共用蓝图。使用蓝图可以极大简化大型应用的开发难度,也为Flask扩展提供了一种在应用中注册服务的集中式机制。

蓝图的应用场景:

把一个应用分解为一个蓝图的集合。这对大型应用是理想的。一个项目可以实例化一个应用对象,初始化几个扩展,并注册一集合的蓝图。

以URL前缀和/或子域名,在应用上注册一个蓝图。URL前缀/子域名中的参数即成为这个蓝图下的所有视图函数的共同的视图参数(默认情况下) 在一个应用中用不同的URL规则多次注册一个蓝图。

通过蓝图提供模板过滤器、静态文件、模板和其他功能。一个蓝图不一定要实现应用或视图函数。

初始化一个Flask扩展时,在这些情况中注册一个蓝图。

蓝图的缺点:

不能在应用创建后撤销注册一个蓝图而不销毁整个应用对象。

使用蓝图的三个步骤

1.创建一个蓝图对象

blue = Blueprint("blue",__name__)

2.在这个蓝图对象上进行操作,例如注册路由、指定静态文件夹、注册模板过滤器...

@blue.route('/')
def blue_index():
    return "Welcome to my blueprint"

3.在应用对象上注册这个蓝图对象

app.register_blueprint(blue,url_prefix="/blue")

141.Flask 和 Django 路由映射的区别?

在django中,路由是浏览器访问服务器时,先访问的项目中的url,再由项目中的url找到应用中url,这些url是放在一个列表里,遵从从前往后匹配的规则。在flask中,路由是通过装饰器给每个视图函数提供的,而且根据请求方式的不同可以一个url用于不同的作用。

操作系统

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)
  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. 解除死锁
    1. 剥夺资源
    2. 撤销进程

死锁概念处理策略详细介绍:https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/10.html

4 程序编译与链接

推荐: http://www.ruanyifeng.com/blog/2014/11/compiler.html

Bulid过程可以分解为4个步骤:预处理(Prepressing), 编译(Compilation)、汇编(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 静态链接和动态链接

静态链接方法:静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来 静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库

动态链接方法:使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态链接的程序

6 虚拟内存技术

虚拟存储器是指具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储系统.

7 分页和分段

分页: 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。

分段: 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。

分页与分段的主要区别

  1. 页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
  2. 页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
  3. 分页的作业地址空间是一维的.分段的地址空间是二维的.

8 页面置换算法

  1. 最佳置换算法OPT:不可能实现
  2. 先进先出FIFO
  3. 最近最久未使用算法LRU:最近一段时间里最久没有使用过的页面予以置换.
  4. clock算法

9 边沿触发和水平触发

边缘触发是指每当状态变化时发生一个 io 事件,条件触发是只要满足条件就发生一个 io 事件

数据库

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当作一个中间存储系统,用来处理高并发的数据库操作

  • 速度快:使用标准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操作之前所有的操作已经执行落地。
    • 该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务将该行删除了。
  • 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

网络

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 的剩余数据分段, 确认收到客户端发来的 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
储存位置 客户端 服务器端
目的 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 跟踪会话
安全性 不安全 安全

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

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数据库中提取数据时,节省去格式化页面时间,以及不同应用程序之间按照HTTP通信协议,遵从XML格式执行资料互换,使其抽象于语言实现、平台和硬件。

14 RPC

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

总结:服务提供的两大流派.传统意义以方法调用为导向通称RPC。为了企业SOA,若干厂商联合推出webservice,制定了wsdl接口定义,传输soap.当互联网时代,臃肿SOA被简化为http+xml/json.但是简化出现各种混乱。以资源为导向,任何操作无非是对资源的增删改查,于是统一的REST出现了.

进化的顺序: RPC -> SOAP -> RESTful

15 CGI和WSGI

CGI是通用网关接口,是连接web服务器和应用程序的接口,用户通过CGI来获取动态数据或文件等。 CGI程序是一个独立的程序,它可以用几乎所有语言来写,包括perl,c,lua,python等等。

WSGI, Web Server Gateway Interface,是Python应用程序或框架和Web服务器之间的一种接口,WSGI的其中一个目的就是让用户可以用统一的语言(Python)编写前后端。

官方说明:PEP-3333

16 中间人攻击

在GFW里屡见不鲜的,呵呵.

中间人攻击(Man-in-the-middle attack,通常缩写为MITM)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。

17 c10k问题

所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection(这也是c10k这个名字的由来)。 推荐: https://my.oschina.net/xianggao/blog/664275

18 socket

推荐: http://www.360doc.com/content/11/0609/15/5482098_122692444.shtml

Socket=Ip address+ TCP/UDP + port

19 浏览器缓存

推荐: http://www.cnblogs.com/skynet/archive/2012/11/28/2792503.html

304 Not Modified

20 HTTP1.0和HTTP1.1

推荐: http://blog.csdn.net/elifefly/article/details/3964766

  1. 请求头Host字段,一个服务器多个网站
  2. 长链接
  3. 文件断点续传
  4. 身份认证,状态管理,Cache缓存

HTTP请求8种方法介绍 HTTP/1.1协议**定义了8种HTTP请求方法,HTTP请求方法也被叫做“请求动作”,不同的方法规定了不同的操作指定的资源方式。服务端也会根据不同的请求方法做不同的响应。

GET

GET请求会显示请求指定的资源。一般来说GET方法应该只用于数据的读取,而不应当用于会产生副作用的非幂等的操作中。

GET会方法请求指定的页面信息,并返回响应主体,GET被认为是不安全的方法,因为GET方法会被网络蜘蛛等任意的访问。

HEAD

HEAD方法与GET方法一样,都是向服务器发出指定资源的请求。但是,服务器在响应HEAD请求时不会回传资源的内容部分,即:响应主体。这样,我们可以不传输全部内容的情况下,就可以获取服务器的响应头信息。HEAD方法常被用于客户端查看服务器的性能。

POST

POST请求会 向指定资源提交数据,请求服务器进行处理,如:表单数据提交、文件上传等,请求数据会被包含在请求体中。POST方法是非幂等的方法,因为这个请求可能会创建新的资源或/和修改现有资源。

PUT

PUT请求会身向指定资源位置上传其最新内容,PUT方法是幂等的方法。通过该方法客户端可以将指定资源的最新数据传送给服务器取代指定的资源的内容。

DELETE

DELETE请求用于请求服务器删除所请求URI(统一资源标识符,Uniform Resource Identifier)所标识的资源。DELETE请求后指定资源会被删除,DELETE方法也是幂等的。

CONNECT

CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。

OPTIONS

OPTIONS请求与HEAD类似,一般也是用于客户端查看服务器的性能。 这个方法会请求服务器返回该资源所支持的所有HTTP请求方法,该方法会用’*’来代替资源名称,向服务器发送OPTIONS请求,可以测试服务器功能是否正常。JavaScript的XMLHttpRequest对象进行CORS跨域资源共享时,就是使用OPTIONS方法发送嗅探请求,以判断是否有对指定资源的访问权限。 允许

TRACE

TRACE请求服务器回显其收到的请求信息,该方法主要用于HTTP请求的测试或诊断。

HTTP/1.1之后增加的方法

在HTTP/1.1标准制定之后,又陆续扩展了一些方法。其中使用中较多的是 PATCH 方法:

PATCH

PATCH方法出现的较晚,它在2010年的RFC 5789标准中被定义。PATCH请求与PUT请求类似,同样用于资源的更新。二者有以下两点不同:

但PATCH一般用于资源的部分更新,而PUT一般用于资源的整体更新。 当资源不存在时,PATCH会创建一个新的资源,而PUT只会对已在资源进行更新。

21 Ajax

AJAX,Asynchronous JavaScript and XML(异步的 JavaScript 和 XML), 是与在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。

*NIX

unix进程间通信方式(IPC)

  1. 管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
  2. 命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
  3. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
  4. 消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
  5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  6. 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
  7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  8. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

网络编程

123.怎么实现强行关闭客户端和服务器之间的连接?

124.简述TCP和UDP的区别以及优缺点?

125.简述浏览器通过WSGI请求动态资源的过程?

浏览器发送的请求被Nginx监听到,Nginx根据请求的URL的PATH或者后缀把请求静态资源的分发到静态资源的目录,别的请求根据配置好的转发到相应端口。 实现了WSGI的程序会监听某个端口,监听到Nginx转发过来的请求接收后(一般用socket的recv来接收HTTP的报文)以后把请求的报文封装成environ的字典对象,然后再提供一个start_response的方法。把这两个对象当成参数传入某个方法比如wsgi_app(environ, start_response)或者实现了__call__(self, environ, start_response)方法的某个实例。这个实例再调用start_response返回给实现了WSGI的中间件,再由中间件返回给Nginx。

126.描述用浏览器访问www.baidu.com的过程

127.Post和Get请求的区别?

128.cookie 和session 的区别?

129.列出你知道的HTTP协议的状态码,说出表示什么意思?

130.请简单说一下三次握手和四次挥手?

131.说一下什么是tcp的2MSL?

132.为什么客户端在TIME-WAIT状态必须等待2MSL的时间?

133.说说HTTP和HTTPS区别?

134.谈一下HTTP协议以及协议头部中表示数据类型的字段?

135.HTTP请求方法都有什么?

136.使用Socket套接字需要传入哪些参数 ?

137.HTTP常见请求头?

138.七层模型?

139.url的形式?

数据结构

1 红黑树

红黑树与AVL的比较:

AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;

红黑是用非严格的平衡来换取增删节点时候旋转次数的降低;

所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。

红黑树详解: https://xieguanglei.github.io/blog/post/red-black-tree.html

教你透彻了解红黑树: https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md

编程题

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 = (high - low) / 2 + low    # 避免(high + low) / 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 not 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描述

interview_python's People

Contributors

584807419 avatar air82 avatar billbillbillbill avatar chaniel avatar dengshilong avatar inuyasha2012 avatar jtr109 avatar kakawxy avatar kkzxak47 avatar lambdaplus avatar light4 avatar mintisan avatar mrlevo520 avatar ph87 avatar qinggniq avatar ruyisee avatar sty945 avatar taizilongxu avatar wyattjia avatar yinwoods avatar zhongyuhang avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.