1 简述解释型和编译型编程语言?
解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候才翻译。这样解释型语言每执行一次就要翻译一次,效率比较低。
用编译型语言写的程序执行之前,需要一个专门的编译过程,通过编译系统,把源高级程序编译成为机器语言文件,翻译只做了一次,运行时不需要翻译,所以编译型语言的程序执行效率高,但也不能一概而论,
部分解释型语言的解释器通过在运行时动态优化代码,甚至能够使解释型语言的性能超过编译型语言。
2 Python解释器种类以及特点?
CPython
当 从Python官方网站下载并安装好Python2.7后,就直接获得了一个官方版本的解释器:Cpython,这个解释器是用C语言开发的,所以叫 CPython,在命名行下运行python,就是启动CPython解释器,CPython是使用最广的Python解释器。
IPython
IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的,好比很多国产浏览器虽然外观不同,但内核其实是调用了IE。
PyPy
PyPy是另一个Python解释器,它的目标是执行速度,PyPy采用JIT技术,对Python代码进行动态编译,所以可以显著提高Python代码的执行速度。
Jython
Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。
IronPython
IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。
在Python的解释器中,使用广泛的是CPython,对于Python的编译,除了可以采用以上解释器进行编译外,技术高超的开发者还可以按照自己的需求自行编写Python解释器来执行Python代码,十分的方便!
3 位和字节的关系?
bit就是位,也叫比特位,是计算机表示数据最小的单位
byte就是字节
1byte=8bit
1byte就是1B
一个字符=2字节
1KB=1024B
字节就是Byte,也是B
位就是bit也是b
转换关系如下:1)1KB=1024B
1B= 8b
4 请至少列举5个 PEP8 规范(越多越好)。
PEP8 编码规范
a.不要在行尾加分号, 也不要用分号将两条命令放在同一行。
b.每行不超过80个字符(长的导入模块语句和注释里的URL除外)
c.不要使用反斜杠连接行。Python会将圆括号, 中括号和花括号中的行隐式的连接起来
d.宁缺毋滥的使用括号,除非是用于实现行连接, 否则不要在返回语句或条件语句中使用括号. 不过在元组两边使用括号是可以的.
e.用4个空格来缩进代码,不要用tab, 也不要tab和空格混用. 对于行连接的情况, 你应该要么垂直对齐换行的元素,或者使用4空格的悬挂式缩进
f.顶级定义之间空2行, 方法定义之间空1行,顶级定义之间空两行, 比如函数或者类定义. 方法定义, 类定义与第一个方法之间, 都应该空一行. 函数或方法中, 某些地方要是你觉得合适, 就空一行.
5 通过代码实现如下转换:
二进制转换成十进制:v = “0b1111011”
#先将其转换为字符串,再使用int函数,指定进制转换为十进制。
print(int("0b1111011",2))
值为123
十进制转换成二进制:v = 18?
print("转换为二进制为:", bin(18))
#转换为二进制为: 0b10010
八进制转换成十进制:v = “011”?
print(int("011",8))
#9
十进制转换成八进制:v = 30
print("转换为八进制为:", oct(30))
#转换为八进制为: 0o36
十六进制转换成十进制:v = “0x12”?
print(int("0x12",16))
#18
十进制转换成十六进制:v = 87
print("转换为十六进制为:", hex(87))
转换为十六进制为: 0x57
6 python递归的最大层数?
def fab(n):
if n == 1:
return 1
else:
return fab(n-1)+ n
print (fab(998))
#得到的最大数为998,以后就是报错了,998这个数值莫名想起广告词····
import sys
sys.setrecursionlimit(100000)
def foo(n):
print(n)
n += 1
foo(n)
if __name__ == '__main__':
foo(1)
#得到的最大数字在3922-3929之间浮动,这个是和计算机有关系的,将数字调到足够大了,已经大于系统堆栈,python已经无法支撑到太大的递归崩了。
7 ascii、unicode、utf-8、gbk 区别?
最早只有127个字母被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。
但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
你可以想得到的是,全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。
因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。
新的问题又出现了:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。
所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。
UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
8 字节码和机器码的区别?
机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据。
通常意义上来理解的话,机器码就是计算机可以直接执行,并且执行速度最快的代码。
用机器语言编写程序,编程人员要首先熟记所用计算机的全部指令代码和代码的涵义。手编程序时,程序员得自己处理每条指令和每一数据的存储分配和输入输出,还得记住编程过程中每步所使用的工作单元处在何种状态。这是一件十分繁琐的工作,编写程序花费的时间往往是实际运行时间的几十倍或几百倍。而且,编出的程序全是些0和1的指令代码,直观性差,还容易出错。现在,除了计算机生产厂家的专业人员外,绝大多数的程序员已经不再去学习机器语言了。
机器语言是微处理器理解和使用的,用于控制它的操作二进制代码。
8086到Pentium的机器语言指令长度可以从1字节到13字节。
尽管机器语言好像是很复杂的,然而它是有规律的。
存在着多至100000种机器语言的指令。这意味着不能把这些种类全部列出来。
总结:机器码是电脑CPU直接读取运行的机器指令,运行速度最快,但是非常晦涩难懂,也比较难编写,一般从业人员接触不到。
字节码(Bytecode)是一种包含执行程序、由一序列 op 代码/数据对 组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。
通常情况下它是已经经过编译,但与特定机器码无关。字节码通常不像源码一样可以让人阅读,而是编码后的数值常量、引用、指令等构成的序列。
字节码主要为了实现特定软件运行和软件环境、与硬件环境无关。字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。字节码的典型应用为Java bytecode。
字节码在运行时通过JVM(JAVA虚拟机)做一次转换生成机器指令,因此能够更好的跨平台运行。
总结:字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。
9 三元运算规则以及应用场景?
表达式格式
为真时的结果 if 判定条件 else 为假时的结果
事例
1 if 3>2 else 0
10 用一行代码实现数值交换:
a =1
b =2
a,b=b,a
print(a,b)
1 Python3和Python2中 int 和 long的区别?
Python 2有为非浮点数准备的int和long类型。int类型的最大值不能超过sys.maxint,而且这个最大值是平台相关的。可以通过在数字的末尾附上一个L来定义长整型,显然,它比int类型表示的数字范围更大。在Python 3里,只有一种整数类型int,大多数情况下,它很像Python 2里的长整型。由于已经不存在两种类型的整数,所以就没有必要使用特殊的语法去区别他们。
2 文件操作时:xreadlines和readlines的区别?
read(size)
读入指定大小的内容,以byte为单位,size为读入的字符数,返回str类型
readline()
readline()读取一行内容,放到一个字符串变量,返回str类型。
readlines()
readlines() 读取文件所有内容,按行为单位放到一个列表中,返回list类型。
xreadlines()
返回一个生成器,来循环操作文件的每一行。循环使用时和readlines基本一样,但是直接打印就不同
3列举布尔值为False的常见值?
下面的值在作为布尔表达式的时候,会被解释器看作假(false)
False None 0 "" () [] {}
换句话说,也就是标准值False和None,所有类型的数字0(包括浮点型,长整型和其他类型),空序列(比如空字符串、元组和列表)以及空的字典都为假。其他的一切都被解释为真,包括特殊值True.
也就是说Python中的所有值都能被解释为真值。”标准的“布尔值为True和False。事实上,True和False只不过是1和0的一种“华丽”的说法而已----看起来不同,但是作用相同。
4 lambda表达式格式以及应用场景?
对于简单的函数,也存在一种简便的表示方式,即:lambda表达式
#普通函数
def func(a):
return a+1
print 'test1_func0:',func(1000)
#lambda表达式
func0 = lambda a:a+1
print 'test2_func0:',func0(1000)
上面这种方法,都实现了将1000+1的结果打印出来这个功能,但是用下面
lambda存在意义就是对简单函数的简洁表示。
说道lambda,这里再赠送一些可以给lambda加buff小伙伴:
map函数
我们使用map函数将会对列表中的所有元素进行操作。map有两个参数(函数,列表),它会在内部遍历列表中的每一个元素,执行传递过来的函数参数。在输出到新列表中。
li = [11, 22, 33]
new_list = map(lambda a: a + 100, li)
输出:[111, 122, 133]
reduce函数
对于序列内所有元素进行累计操作:
lst = [11,22,33]
func2 = reduce(lambda arg1,arg2:arg1+arg2,lst)
print 'func2:',func2
输出:func2: 66
filter函数
他可以根据条件对数据进行过滤:
li = [11, 22, 33]
new_list = filter(lambda arg: arg > 22, li)
print new_list
输出:[33]
divmod()
函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。
>>>divmod(7, 2)
(3, 1)
>>> divmod(8, 2)
(4, 0)
zip() 函数
用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
>>>a = [1,2,3]
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b) # 打包为元组的列表
[(1, 4), (2, 5), (3, 6)]
>>> zip(a,c) # 元素个数与最短的列表一致
[(1, 4), (2, 5), (3, 6)]
>>> zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
[(1, 2, 3), (4, 5, 6)]
5 *arg和**kwarg作用
首先我们可以定一个简单的函数, 函数内部只考虑required_arg这一个形参(位置参数)
def exmaple(required_arg):
print required_arg
exmaple("Hello, World!")
>> Hello, World!
那么,如果我们调用函数式传入了不止一个位置参数会出现什么情况?当然是会报错!
*arg和**kwarg 可以帮助我们处理上面这种情况,允许我们在调用函数的时候传入多个实参
def exmaple2(required_arg, *arg, **kwarg):
if arg:
print "arg: ", arg
if kwarg:
print "kwarg: ", kwarg
exmaple2("Hi", 1, 2, 3, keyword1 = "bar", keyword2 = "foo")
>> arg: (1, 2, 3)
>> kwarg: {'keyword2': 'foo', 'keyword1': 'bar'}
从上面的例子可以看到,当我传入了更多实参的时候
*arg会把多出来的位置参数转化为tuple
**kwarg会把关键字参数转化为dict
6 is和==的区别
在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。
is和==都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同。下面来看看具体区别在哪。
==比较操作符和is同一性运算符区别
==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等,例如下面两个字符串间的比较:
>>> a = 'cheesezh'
>>> b = 'cheesezh'
>>> a == b
True
is也被叫做同一性运算符,这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同。通过对下面几个list间的比较,你就会明白is同一性运算符的工作原理:
>>> x = y = [4,5,6]
>>> z = [4,5,6]
>>> x == y
True
>>> x == z
True
>>> x is y
True
>>> x is z
False
>>>
>>> print id(x)
>>> print id(y)
>>> print id(z)
7 简述Python的深浅拷贝以及应用场景?
可变对象-不可变对象
在Python中不可变对象指:一旦创建就不可修改的对象,包括字符串,元祖,数字
在Python中可变对象是指:可以修改的对象,包括:列表、字典
>>> L1 = [2,3,4] #L1变量指向的是一个可变对象:列表
>>> L2 = L1 #将L1值赋给L2后,两者共享引用同一个列表对象[1,2,3,4]
>>> L1[0] = 200 #因为列表可变,改变L1中第一个元素的值
>>> L1; L2 #改变后,L1,L2同时改变,因为对象本身值变了
[200, 3, 4]
[200, 3, 4]
如果不想改变列表L2的值,有两种方法:切片 和 copy模块
>>> L1 = [2,3,4]
>>> L2 = L1
>>> id(L1);id(L2) #共享引用一个可变对象
45811784L
45811784L
>>> L2 = L1[:] #切片操作
>>> id(L1);id(L2) #切片后,对象就不一样了
45811784L
45806920L
>>> L1[0] = 200
>>> L1;L2 #L1发生改变,L2没有变化
[200, 3, 4]
[2, 3, 4]
拷贝
切片技术应用于所有的序列,包括:列表、字符串、元祖
但切片不能应用于字典。对字典只能使用D.copy()方法或D.deepcopy()方法.
深浅拷贝,即可用于序列,也可用于字典
import copy
X = copy.copy(Y) #浅拷贝:只拷贝顶级的对象,或者说:父级对象
X = copy.deepcopy(Y) #深拷贝:拷贝所有对象,顶级对象及其嵌套对象。或者说:父级对象及其子对象
如果字典只有顶级对象:
如果字典中嵌套对象:
【结论】
深浅拷贝都是对源对象的复制,占用不同的内存空间
如果源对象只有一级目录的话,源做任何改动,不影响深浅拷贝对象
如果源对象不止一级目录的话,源做任何改动,都要影响浅拷贝,但不影响深拷贝
序列对象的切片其实是浅拷贝,即只拷贝顶级的对象
8 Python垃圾回收机制?
import sys sys.getrefcount()查看引用计数
字符串中间有空格!?等会重新创建新的字符串
总结
1. 小整数[-5,257)共用对象,常驻内存,不会被释放。
2. 单个字符共用对象,常驻内存。
3. 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁 。
4. 大整数不共用内存,引用计数为0,销毁 .
5. 数值类型和字符串类型在 Python 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象 .
Garbage collection(GC垃圾回收)
python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收、分代回收)两种机制为辅的策略
引用计数机制的优点:
1、简单
2、实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
维护引用计数消耗资源
循环引用,解决不了
gc模块
3.1. 垃圾回收机制
导致引用计数+1的情况
1.对象被创建,例如a = "hello"
2.对象被引用,例如b=a
3.对象被作为参数,传入到一个函数中,例如func(a)
4.对象作为一个元素,存储在容器中,例如list1=[a,a]
1. 常用函数
1、gc.set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
2、gc.collect([generation]) 显式进行垃圾回收,可以输入参数,0代表只检查零代的对象,1代表检查零,一代的对象,2代表检查零,一,二代的对象,如果不传参数,执行一个full collection,也就是等于传2。 在python2中返回不可达(unreachable objects)对象的数目
3、gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。
4、gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。
5、gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。通过分代回收以空间换取时间进一步提高垃圾回收的效率。
标记-清除
标记-清除的出现打破了循环引用,也就是它只关注那些可能会产生循环引用的对象
缺点:该机制所带来的额外操作和需要回收的内存块成正比。
一旦这个差异累计超过某个阈值(700,10,10),则Python的收集机制就启动了,并且触发上边所说到的零代算法释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
隔代回收
原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。
dir(__builtins__)查看内建属性
__getattribute__内建属性。属性访问拦截器(方法和属性都可以被拦截),可以返回一个值:以后不要在__getattribute__方法中调用self.xxxx会引起递归时程序死掉
map函数会根据提供的函数对指定序列做映射返回值是列表
map(function, sequence[, sequence, ...]) -> list
? function:是一个函数
? sequence:是一个或多个序列,取决于function需要几个参数
? 返回值是一个list
filter函数python3返回的是生产器filter函数会对指定序列执行过滤操作
filter(function or None, sequence) -> list, tuple, or string
? function:接受一个参数,返回布尔值True或False
? sequence:序列可以是str,tuple,list
list(filter(lambda x x%2==0,[1,2,3,4,5,6])---->[2,4,6]
sorted函数-排序
sorted(iterable, reverse=False) --> new sorted list
functools模块import functools
partial函数(偏函数)把一个函数的某些参数设置默认值,返回一个新的函数,调用这个新函数会更简单。
wraps函数 使用装饰器时,让外界看被装饰的函数时内容一致。
例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。
functools.wraps(func)
9 求结果:
v = dict.fromkeys(['k1','k2'],[])
v['k1'].append('000')
print(v)
v['k1']=666
print(v)
{'k1': ['000'], 'k2': ['000']}
{'k1': 666, 'k2': ['000']}
10 求结果
def num():
return [lambda x:i*x for i in range(4)]
print([m(2) for m in num()])
[6, 6, 6, 6]
以上代码的输出是 [6, 6, 6, 6] (而不是 [0, 2, 4, 6])。
这个的原因是 Python 的闭包的后期绑定导致的 late binding,这意味着在闭包中的变量是在内部函数被调用的时候被查找。所以结果是,当任何 multipliers() 返回的函数被调用,在那时,i 的值是在它被调用时的周围作用域中查找,到那时,无论哪个返回的函数被调用,for 循环都已经完成了,i 最后的值是 3,因此,每个返回的函数 multiplies 的值都是 3。因此一个等于 2 的值被传递进以上代码,它们将返回一个值 6 (比如: 3 x 2)。
(顺便说下,正如在 The Hitchhiker’s Guide to Python 中指出的,这里有一点普遍的误解,是关于 lambda 表达式的一些东西。一个 lambda 表达式创建的函数不是特殊的,和使用一个普通的 def 创建的函数展示的表现是一样的。)
这里有两种方法解决这个问题。
最普遍的解决方案是创建一个闭包,通过使用默认参数立即绑定它的参数。例如:
def num():
return [lambda x, i=i : i * x for i in range(4)]
另外一个选择是,你可以使用 functools.partial 函数:
from functools import partial
from operator import mul
def num():
return [partial(mul, i) for i in range(4)]
1 一行代码实现9*9乘法表
print ("\n".join("\t".join(["%s*%s=%s" %(x,y,x*y) for y in range(1, x+1)]) for x in range(1, 10)))
==================================================
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
2 列举常见的内置函数?
内置函数
abs() divmod() input() open() staticmethod()
all() enumerate() int() ord() str()
any() eval() isinstance() pow() sum()
basestring() execfile() issubclass() print() super()
bin() file() iter() property() tuple()
bool() filter() len() range() type()
bytearray() float() list() raw_input() unichr()
callable() format() locals() reduce() unicode()
chr() frozenset() long() reload() vars()
classmethod() getattr() map() repr() xrange()
cmp() globals() max() reverse() zip()
compile() hasattr() memoryview() round() __import__()
complex() hash() min() set()
delattr() help() next() setattr()
dict() hex() object() slice()
dir() id() oct() sorted() exec 内置表达式
3 如何安装第三方模块?以及用过哪些第三方模块?
使用软件管理工具(pip,pip2,pip3)
python2和python3都自带了pip,而pip就仿佛有一个仓库,将我们需要安装的第三方模块都收纳其中,使用简单的安装命令即可完成安装。
注意事项:用python3自带的pip或者pip3安装的第三方模块就只能为python3的编译器使用,这对于python2的pip和pip2是同理的。
具体安装方法:直接: pip3 install 模块名
常用第三方模块
Requests.Kenneth Reitz写的最富盛名的http库。每个Python程序员都应该有它。
Scrapy.如果你从事爬虫相关的工作,那么这个库也是必不可少的。用过它之后你就不会再想用别的同类库了。
wxPython.Python的一个GUI(图形用户界面)工具。我主要用它替代tkinter。你一定会爱上它的。
Pillow.它是PIL(Python图形库)的一个友好分支。对于用户比PIL更加友好,对于任何在图形领域工作的人是必备的库。
SQLAlchemy.一个数据库的库。对它的评价褒贬参半。是否使用的决定权在你手里。
BeautifulSoup.我知道它很慢,但这个xml和html的解析库对于新手非常有用。
Twisted.对于网络应用开发者最重要的工具。它有非常优美的api,被很多Python开发大牛使用。
NumPy.我们怎么能缺少这么重要的库?它为Python提供了很多高级的数学方法。
SciPy.既然我们提了NumPy,那就不得不提一下SciPy。这是一个Python的算法和数学工具库,它的功能把很多科学家从Ruby吸引到了Python。
matplotlib.一个绘制数据图的库。对于数据科学家或分析师非常有用。
Pygame.哪个程序员不喜欢玩游戏和写游戏?这个库会让你在开发2D游戏的时候如虎添翼。
Pyglet.3D动画和游戏开发引擎。非常有名的Python版本Minecraft就是用这个引擎做的。
pyQT.Python的GUI工具。这是我在给Python脚本开发用户界面时次于wxPython的选择。
pyGtk.也是Python GUI库。很有名的Bittorrent客户端就是用它做的。
Scapy.用Python写的数据包探测和分析库。
pywin32.一个提供和windows交互的方法和类的Python库。
nltk.自然语言工具包。我知道大多数人不会用它,但它通用性非常高。如果你需要处理字符串的话,它是非常好的库。但它的功能远远不止如此,自己摸索一下吧。
nose.Python的测试框架。被成千上万的Python程序员使用。如果你做测试导向的开发,那么它是必不可少的。
SymPy.SymPy可以做代数评测、差异化、扩展、复数等等。它封装在一个纯Python发行版本里。
IPython.怎么称赞这个工具的功能都不为过。它把Python的提示信息做到了极致。包括完成信息、历史信息、shell功能,以及其他很多很多方面。一定要研究一下它。
4 re的match和search区别?
match()函数只检测RE是不是在string的开始位置匹配,search()会扫描整个string查找匹配;也就是说match()只有在0位置匹配成功的话才有返回,
如果不是开始位置匹配成功的话,match()就返回none。
例如:
print(re.match(‘super’, ‘superstition’).span()) 会返回(0, 5)
而print(re.match(‘super’, ‘insuperable’)) 则返回None
search()会扫描整个字符串并返回第一个成功的匹配
例如:print(re.search(‘super’, ‘superstition’).span())返回(0, 5)
5 什么是正则的贪婪匹配?
如:String str="abcaxc";
Patter p="ab.*c";
贪婪匹配:正则表达式一般趋向于最大长度匹配,也就是所谓的贪婪匹配。如上面使用模式p匹配字符串str,结果就是匹配到:abcaxc(ab.*c)。
非贪婪匹配:就是匹配到结果就好,就少的匹配字符。如上面使用模式p匹配字符串str,结果就是匹配到:abc(ab.*c)。
编程中如何区分两种模式
默认是贪婪模式;在量词后面直接加上一个问号?就是非贪婪模式。
量词:{m,n}:m到n个
*:任意多个
+:一个到多个
?:0或一个
6 求结果:?
l = [i % 2 for i in range(10)]
print(l)
t = (i % 2 for i in range(10))
print(t)
=============================
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
7 求结果
print(1 or 2)
print(1 and 2)
print(1 < (2==2))
print(1 < 3 == 3)
print(True == 3)
print(True == 1)
=============================
1
2
False
True
False
True
8 def func(a,b=[]) 这种写法有什么坑?
def func(a,b=[]):
b.append(a)
print(b)
func(1)
func(1)
func(1)
func(1)
=================================
[1]
[1, 1]
[1, 1, 1]
[1, 1, 1, 1]
函数的第二个默认参数是一个list,当第一次执行的时候实例化了一个list,第二次执行还是用第一次执行的时候实例化的地址存储,所以三次执行的结果就是 [1, 1, 1] ,想每次执行只输出[1] ,默认参数应该设置为None。
9 如何实现 “1,2,3” 变成 [‘1’,’2’,’3’] ?
L = [1,2,3]
NL =list(map(str,L))
print(NL)
10 1、2、3、4、5 能组成多少个互不相同且无重复的三位数
for x in range(1,5):
for y in range(1,5):
for z in range(1,5):
if (x!=y) and (y!=z) and (z!=x):
print("%d%d%d" % (x, y, z))
1 常用字符串格式化哪几种?
最方便的
print('hello %s and %s' % ('df', 'another df'))
但是,有时候,我们有很多的参数要进行格式化,这个时候,一个一个对应就有点麻烦了,于是就有了第二种,字典形式的。上面那种是tuple形式的。
最好用的
print('hello %(first)s and %(second)s' % {'first': 'df', 'second': 'another df'})
这种字典形式的字符串格式化方法,有一个最大的好处就是,字典这个东西可以和json文件相互转换,所以,当配置文件使用字符串设置的时候,就显得相当方便。
最先进的
print('hello {first} and {second}'.format(first='df', second='another df'))
2 简述 生成器、迭代器、可迭代对象 以及应用场景?
迭代器
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。
使用迭代器的优点
对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。
另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了iter()方法对象,就可以使用迭代器访问。
迭代器有两个基本的方法
next方法:返回迭代器的下一个元素
__iter__方法:返回迭代器对象本身
下面用生成斐波那契数列为例子,说明为何用迭代器
#代码1:直接在函数fab(max)中用print打印会导致函数的可复用性变差,因为fab返回None。其他函数无法获得fab函数返回的数列。
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1
#代码2:代码2满足了可复用性的需求,但是占用了内存空间,最好不要。
def fab(max):
L = []
n, a, b = 0, 0, 1
while n < max:
L.append(b)
a, b = b, a + b
n = n + 1
return L
#代码3:
class Fab(object):
def __init__(self, max):
self.max = max
self.n, self.a, self.b = 0, 0, 1
def __iter__(self):
return self
def next(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration()
执行
>>> for key in Fabs(5):
print key
1
1
2
3
5
Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数
使用内建的工厂函数iter(iterable)可以获取迭代器对象:
>>> lst = range(5)
>>> it = iter(lst)
>>> it
使用next()方法可以访问下一个元素:
>>> it.next()
0
>>> it.next()
1
>>> it.next()
2
事实上,因为迭代器如此普遍,python专门为for关键字做了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。如下
>>> a = (1, 2, 3, 4)
>>> for key in a:
print key
1
2
3
4
首先python对关键字in后的对象调用iter函数迭代器,然后调用迭代器的next方法获得元素,直到抛出StopIteration异常。
生成器
带有 yield 的函数在 Python 中被称之为 generator(生成器),几个例子说明下(还是用生成斐波那契数列说明)
可以看出代码3远没有代码1简洁,生成器(yield)既可以保持代码1的简洁性,又可以保持代码3的效果
#代码4
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
执行
>>> for n in fab(5):
print n
1
1
2
3
5
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:
>>> f = fab(3)
>>> f.next()
1
>>> f.next()
1
>>> f.next()
2
>>> f.next()
Traceback (most recent call last):
File "
f.next()
StopIteration
Python可迭代对象(Iterable)
我们已经知道可以对list、tuple、str等类型的数据使?for...in...的循环语法从
其中依次拿到数据进?使?,我们把这样的过程称为遍历,也叫迭代。
但是,是否所有的数据类型都可以放到for...in...的语句中,然后让for...in...
每次从中取出?条数据供我们使?,即供我们迭代吗?
我们把可以通过for...in...这类语句迭代读取?条数据供我们使?的对象称之为可迭代对象(Iterable)
两种方法:
1.可迭代对象.__iter__()
2.iter(可迭代对象)
如何判断对象是否可以迭代
可以使用 isinstance() 判断?个对象是否是 Iterable 对象:
from collections import Iterable
isinstance([], Iterable) Out[51]: True
isinstance({}, Iterable)
True
isinstance('abc', Iterable)
True
isinstance(mylist, Iterable)
False
isinstance(100, Iterable)
False
3 用Python实现一个二分查找的函数。
题目:
输入指定列表和一个待查找的元素,输出元素是否在列表中,若存在则返回下标
思想:
利用二分查找来做,事先需要对列表进行排序,二分查找只对有序表有效
下面是具体的实现:
def binary_search(num_list, x):
'''
二分查找
'''
num_list=sorted(num_list)
left, right = 0, len(num_list)
while left < right:
mid = int((left + right) / 2)
if num_list[mid] > x:
right = mid
elif num_list[mid] < x:
left = mid + 1
else:
return '待查元素{0}在列表中下标为:{1}'.format(x, mid)
return '待查找元素%s不存在指定列表中'%x
if __name__ == '__main__':
num_list = [34,6,78,9,23,56,177,33,2,6,30,99,83,21,17]
print(binary_search(num_list, 34))
print(binary_search(num_list, 177))
print(binary_search(num_list, 21))
print(binary_search(num_list, 211))
print(binary_search(num_list, 985))
结果如下:
待查元素34在列表中下标为:9
待查元素177在列表中下标为:14
待查元素21在列表中下标为:5
待查找元素211不存在指定列表中
待查找元素985不存在指定列表中
4 谈谈你对闭包的理解?
闭包(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)
Process finished with exit code 0
这个例子中,函数line与环境变量a,b构 成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。
如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。
5 os和sys模块的作用?
os与sys模块的官方解释如下:
os: This module provides a portable way of using operating system dependent functionality.
这个模块提供了一种方便的使用操作系统函数的方法。
sys: This module provides access to some variables used or maintained by the interpreter and to functions that interact strongly with the interpreter.
这个模块可供访问由解释器使用或维护的变量和与解释器进行交互的函数。
总结就是,os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口;sys模块负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境。
os 常用方法
os.remove(‘path/filename’) 删除文件
os.rename(oldname, newname) 重命名文件
os.walk() 生成目录树下的所有文件名
os.chdir('dirname') 改变目录
os.mkdir/makedirs('dirname')创建目录/多层目录
os.rmdir/removedirs('dirname') 删除目录/多层目录
os.listdir('dirname') 列出指定目录的文件
os.getcwd() 取得当前工作目录
os.chmod() 改变目录权限
os.path.basename(‘path/filename’) 去掉目录路径,返回文件名
os.path.dirname(‘path/filename’) 去掉文件名,返回目录路径
os.path.join(path1[,path2[,...]]) 将分离的各部分组合成一个路径名
os.path.split('path') 返回( dirname(), basename())元组
os.path.splitext() 返回 (filename, extension) 元组
os.path.getatime\ctime\mtime 分别返回最近访问、创建、修改时间
os.path.getsize() 返回文件大小
os.path.exists() 是否存在
os.path.isabs() 是否为绝对路径
os.path.isdir() 是否为目录
os.path.isfile() 是否为文件
sys 常用方法
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.modules.keys() 返回所有已经导入的模块列表
sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback当前处理的异常详细信息
sys.exit(n) 退出程序,正常退出时exit(0)
sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.maxunicode 最大的Unicode值
sys.modules 返回系统导入的模块字段,key是模块名,value是模块
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout 标准输出
sys.stdin 标准输入
sys.stderr 错误输出
sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息
sys.exec_prefix 返回平台独立的python文件安装的位置
sys.byteorder 本地字节规则的指示器,big-endian平台的值是'big',little-endian平台的值是'little'
sys.copyright 记录python版权相关的东西
sys.api_version 解释器的C的API版本
1 谈谈你对面向对象的理解?
面向对象的编程---object oriented programming,简称:OOP,是一种编程的思想。OOP把对象当成一个程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的出现极大的提高了编程的效率,使其编程的重用性增高。
python面向对象的重要术语:
多态(polymorphism):一个函数有多种表现形式,调用一个方法有多种形式,但是表现出的方法是不一样的。
继承(inheritance)子项继承父项的某些功能,在程序中表现某种联系
封装(encapsulation)把需要重用的函数或者功能封装,方便其他程序直接调用
类:对具有相同数据或者方法的一组对象的集合
对象:对象是一个类的具体事例
实例化:是一个对象事例话的实现
标识:每个对象的事例都需要一个可以唯一标识这个事例的标记
python中的类与对象
class Person(object):
def __init__(self,name): #————————>初始化函数
self.name = name
print "------>create:",name
def say_name(self):
print "my name is %s" %self.name
p1 = Person("gf1") # --------->类的实例化
p2 = Person("gf2")
p1.say_name()
p2.say_name()
以上程序是类的一个基本写法,理解如下:
person就是一个类,在这个类中是很多的方法集合。例如类中包含sayname等,还可以按照程序要求灵活添加各种类的方法。
类中self其实就是类的对象,是一个具体的实例。多种的实例通过类中的self表现出来。
程序中的p1、p2是具体的实例,实体。(学名叫:对象。)。一个对象就是一个实体。实体通过调用属性、方法在类中体现相应的功能。
python中的继承
承就是子类继承了父类相应的功能和方法。
如下代码说明了子类继承父类:
class firsttest:
def __init__(self,name):
self._name = name
def sayfirst(self):
print("hello {0}".format(self._name))
class secondtest(firsttest): #子类继承父类(firsttest)的方法
def __init__(self,name):
firsttest.__init__(self,name)
def saysecond(self):
print("good {0}").format(self._name)
s = secondtest("gf1") #类的具体实例。
s.sayfirst()
s.saysecond()
函数和面向对象编程的区别
相同点:都是把程序进行封装、方便重复利用,提高效率。
不同点:函数重点是用于整体调用,一般用于一段不可更改的程序。仅仅是解决代码重用性的问题。
而面向对象出来代码重用性。还包括继承、多态等。使用上更加灵活。
2 面向对象中super的作用?
Python中对象方法的定义很怪异,第一个参数一般都命名为self(相当于其它语言的this,比如:C#),用于传递对象本身,而在调用的时候则不
必显式传递,系统会自动传递。
今天我们介绍的主角是super(), 在类的继承里面super()非常常用, 它解决了子类调用父类方法的一些问题, 父类多次被调用时只执行一次, 优化了执行逻辑,下面我们就来详细看一下。
举一个例子:
class Foo:
def bar(self, message):
print(message)
>>> Foo().bar("Hello, Python.")
Hello, Python
当存在继承关系的时候,有时候需要在子类中调用父类的方法,此时最简单的方法是把对象调用转换成类调用,需要注意的是这时self参数需要显式传递,例如:
class FooParent:
def bar(self, message):
print(message)
class FooChild(FooParent):
def bar(self, message):
FooParent.bar(self, message)
>>> FooChild().bar("Hello, Python.")
Hello, Python.
这样做有一些缺点,比如说如果修改了父类名称,那么在子类中会涉及多处修改,另外,Python是允许多继承的语言,如上所示的方法在多继承时就需要重复写多次,显得累赘。为了解决这些问题,Python引入了super()机制,例子代码如下:
class FooParent:
def bar(self, message):
print(message)
class FooChild(FooParent):
def bar(self, message):
super(FooChild, self).bar(message)
>>> FooChild().bar("Hello, Python.")
Hello, Python
表面上看 super(FooChild, self).bar(message)方法和FooParent.bar(self, message)方法的结果是一致的,实际上这两种方法的内部处理机制大大不同,当涉及多继承情况时,就会表现出明显的差异来,直接给例子: 代码一:
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
A.__init__(self)
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
A.__init__(self)
print("Leave C")
class D(A):
def __init__(self):
print("Enter D")
A.__init__(self)
print("Leave D")
class E(B, C, D):
def __init__(self):
print("Enter E")
B.__init__(self)
C.__init__(self)
D.__init__(self)
print("Leave E")
E()
结果:
Enter E
Enter B
Enter A
Leave A
Leave B
Enter C
Enter A
Leave A
Leave C
Enter D
Enter A
Leave A
Leave D
Leave E
执行顺序很好理解,唯一需要注意的是公共父类A被执行了多次。
代码二:
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
super(B, self).__init__()
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
super(C, self).__init__()
print("Leave C")
class D(A):
def __init__(self):
print("Enter D")
super(D, self).__init__()
print("Leave D")
class E(B, C, D):
def __init__(self):
print("Enter E")
super(E, self).__init__()
print("Leave E")
E()
print("MRO:", [x.__name__ for x in E.__mro__])
Enter E
Enter B
Enter C
Enter D
Enter A
Leave A
Leave D
Leave C
Leave B
Leave E
MRO: ['E', 'B', 'C', 'D', 'A', 'object']
在super机制里可以保证公共父类仅被执行一次,至于执行的顺序,是按照MRO(Method Resolution Order):方法解析顺序 进行的。
3 MRO 多继承属性查找机制
最近在写代码的时候遇到了一个问题,直接实例上代码
class A(object):
def __init__(self):
print "A"
super(A, self).__init__()
class B(object):
def __init__(self):
print "B"
super(B, self).__init__()
class C(A):
def __init__(self, arg):
print "C","arg=",arg
super(C, self).__init__()
class D(B):
def __init__(self, arg):
print "D", "arg=",arg
super(D, self).__init__()
class E(C,D):
def __init__(self, arg):
print "E", "arg=",arg
super(E, self).__init__(arg)
#print "MRO:", [x.__name__ for x in E.__mro__]
E(10)
对于这段代码,我们可能期望输出像这样:
E arg= 10
C arg= 10
A
D arg= 10
B
但事实上,这段代码会引发错误,因为python没有像我们想的那样调用正确的函数。
E arg= 10
C arg= 10
A
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/example1-2.py", line 27, in
E(10)
File "C:/Users/Administrator/Desktop/example1-2.py", line 24, in __init__
super(E, self).__init__(arg)
File "C:/Users/Administrator/Desktop/example1-2.py", line 14, in __init__
super(C, self).__init__()
File "C:/Users/Administrator/Desktop/example1-2.py", line 4, in __init__
super(A, self).__init__()
TypeError: __init__() takes exactly 2 arguments (1 given)
我们先给出上面的代码中注释掉的输出mro的语句的输出:
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
出错的原因是因为调用继续到A.__init__时,我们调用了super(A,self).__init__。记得上面我们说过super类似于next函数,是调用mro中下一个类型的方法。
这里我们给出的类型是A,那么mro中下一个类型就是D,很显然,super将会调用D.__init__(self)。可是,D.__init__却接受一个额外的参数arg,所以调用错误。
super并不像它的名字那样,只调用父类的方法,而是调用MRO中,下一个类型的方法。
4 是否使用过functools中的函数?其作用是什么?
partial
首先是partial函数,它可以重新绑定函数的可选参数,生成一个callable的partial对象:
>>> int('10') # 实际上等同于int('10', base=10)和int('10', 10)
10
>>> int('10', 2) # 实际上是int('10', base=2)的缩写
2
>>> from functools import partial
>>> int2 = partial(int, 2) # 这里我没写base,结果就出错了
>>> int2('10')
Traceback (most recent call last):
File "
TypeError: an integer is required
>>> int2 = partial(int, base=2) # 把base参数绑定在int2这个函数里
>>> int2('10') # 现在缺省参数base被设为2了
2
>>> int2('10', 3) # 没加base,结果又出错了
Traceback (most recent call last):
File "
TypeError: keyword parameter 'base' was given by position and by name
>>> int2('10', base=3)
3
>>> type(int2)
从中可以看出,唯一要注意的是可选参数必须写出参数名。
update_wrapper
接着是update_wrapper函数,它可以把被封装函数的__name__、__module__、__doc__和 __dict__都复制到封装函数去:
def thisIsliving(fun):
def living(*args, **kw):
return fun(*args, **kw) + '活着就是吃嘛。'
return living
@thisIsliving
def whatIsLiving():
"什么是活着"
return '对啊,怎样才算活着呢?'
print(whatIsLiving() )
print(whatIsLiving.__doc__ )
from functools import update_wrapper
def thisIsliving(fun):
def living(*args, **kw):
return fun(*args, **kw) + '活着就是吃嘛。'
return update_wrapper(living, fun)
@thisIsliving
def whatIsLiving():
"什么是活着"
return '对啊,怎样才算活着呢?'
print(whatIsLiving() )
print(whatIsLiving.__doc__ )
结果:
对啊,怎样才算活着呢?活着就是吃嘛。
None
对啊,怎样才算活着呢?活着就是吃嘛。
什么是活着
不过也没多大用处,毕竟只是少写了4行赋值语句而已。
wraps
再有是wraps函数,它将update_wrapper也封装了进来:
from functools import wraps
def thisIsliving(fun):
@wraps(fun)
def living(*args, **kw):
return fun(*args, **kw) + '活着就是吃嘛。'
return living
@thisIsliving
def whatIsLiving():
"什么是活着"
return '对啊,怎样才算活着呢?'
print(whatIsLiving() )
print(whatIsLiving.__doc__ )
结果还是一样的:
对啊,怎样才算活着呢?活着就是吃嘛。
什么是活着
total_ordering
最后至于total_ordering函数则给予类丰富的排序方法,使用装饰器简化了操作。如果使用必须在类里面定义一个__lt__(),__le__(), __gt__(), 或__ge__()。应该给类添加一个__eq__() 方法。
from functools import total_ordering
@total_ordering
class Student(object):
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name.lower() == other.name.lower()
def __lt__(self, other):
return self.name.lower() < other.name.lower()
a = Student('dan')
b = Student('mink')
print(a > b)
print (a)
print (sorted([b, a]))
打印结果
False
<__main__.Student object at 0x7f16ecb194d0>
[<__main__.Student object at 0x7f16ecb194d0>, <__main__.Student object at 0x7f16ecb195d0>]
5 列举面向对象中带爽下划线的特殊方法,如:__new__、__init__
类的特殊成员方法
__doc__ :打印类的描述信息
class Foo:
""" 描述类信息,这是用于看片的神奇 """
def func(self):
pass
print(Foo.__doc__)
#输出:类的描述信息
__module__:表示当前操作的对象在那个模块
__class__:表示当前操作的对象的类是什么
from lib.aa import C
obj = C()
print(obj.__module__) # 输出 lib.aa,即:输出模块
print(obj.__class__) # 输出 lib.aa.C,即:输出类
__init__ :构造方法,通过类创建对象时,自动触发执行
class Role(object):
#初始化函数,在生成一个角色时要 初始化的一些属性就填写在这里
def __init__(self,name,role,weapon,life_value=100,money=15000):
#__init__中的第一个参数self,和这里的self都 是什么意思? 看下面解释
self.name = name
self.role = role
__del__:析构方法,当对象在内存中被释放时,自动触发执行
class Role(object):
def __init__(self,name,role,weapon:
self.name = name
self.role = role
self.weapon = weapon
def __del__(self): #析构函数
print("del.....run...")
r1 = Role('A','police','AK47') #生成一个角色
__call__:对象后面加括号,触发执行
#注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
__dict__:查看类或对象中的所有成员
print(类.__dict__) # 打印类里所有属性,不包括实例属性
print(实例.__dict__) #打印实例所有属性,不包括类属性
__str__:如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值
class Foo:
def __str__(self):
return 'a li'
obj = Foo()
print(obj)
# 输出:a li
1 异常处理写法以及如何主动跑出异常(应用场景)
try:
"""执行语句"""
except: #异常类型
"""触发异常后执行的语句"""
finally:
"""有没有异常都执行的语句"""
# 主动抛出异常
raise #异常类实例
2 json序列化时,可以处理的数据类型有哪些?如何定制支持datetime类型?
#字符串、字典、列表、数字、布尔值、None、、自定义class类
import json
import datetime
class MyEncoder(json.JSONEncoder):
def default(self, o): # o是数据类型
if isinstance(o, datetime.datetime)
return o.strftime('%Y-%m-%d')
else:
return super(MyEncoder, self).default(o)
3 什么是断言?应用场景?
断言条件为真时,代码继续执行,负责抛出异常,这个异常通常不会去捕获他,我们设置一个断言目的就是要求必须实现某个条件。
assert True
4 使用代码实现查看列举目录下的所有文件。
# 递归方法
def print_directory_contents(sPath):
import os
for sChild in os.listdir(sPath):
sChildPath = os.path.join(sPath,sChild)
if os.path.isdir(sChildPath):
print_directory_contents(sChildPath)
else:
print(sChildPath)
5 简述yield和yield from关键字。
yield:生成器函数关键字
yield from:相当于 for i in obj : yield i
def a():
yield 1
def b():
yield from a()
6 简述 OSI 七层协议。
物理层
主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
数据链路层
定义了如何让格式化数据以进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。
网络层
在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层。
传输层
定义了一些传输数据的协议和端口号(WWW端口80等),如:
TCP(transmission control protocol –传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据)
UDP(user datagram protocol–用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
会话层
通过运输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)
表示层
可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换。
应用层
是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。
更多请参考网络基础
7 什么是C/S和B/S架构?
一、C/S 架构
1、 概念
C/S 架构是一种典型的两层架构,其全程是Client/Server,即客户端服务器端架构,其客户端包含一个或多个在用户的电脑上运行的程序,而服务器端有两种,一种是数据库服务器端,客户端通过数据库连接访问服务器端的数据;另一种是Socket服务器端,服务器端的程序通过Socket与客户端的程序通信。
C/S 架构也可以看做是胖客户端架构。因为客户端需要实现绝大多数的业务逻辑和界面展示。这种架构中,作为客户端的部分需要承受很大的压力,因为显示逻辑和事务处理都包含在其中,通过与数据库的交互(通常是SQL或存储过程的实现)来达到持久化数据,以此满足实际项目的需要。
2 、优点和缺点
优点:
2.1 C/S架构的界面和操作可以很丰富。
2.2 安全性能可以很容易保证,实现多层认证也不难。
2.3 由于只有一层交互,因此响应速度较快。
缺点:
2.4 适用面窄,通常用于局域网中。
2.5 用户群固定。由于程序需要安装才可使用,因此不适合面向一些不可知的用户。
2.6 维护成本高,发生一次升级,则所有客户端的程序都需要改变。
二、B/S架构
1、概念
B/S架构的全称为Browser/Server,即浏览器/服务器结构。Browser指的是Web浏览器,极少数事务逻辑在前端实现,但主要事务逻辑在服务器端实现,Browser客户端,WebApp服务器端和DB端构成所谓的三层架构。B/S架构的系统无须特别安装,只有Web浏览器即可。
B/S架构中,显示逻辑交给了Web浏览器,事务处理逻辑在放在了WebApp上,这样就避免了庞大的胖客户端,减少了客户端的压力。因为客户端包含的逻辑很少,因此也被成为瘦客户端。
2 、优点和缺点
优点:
1)客户端无需安装,有Web浏览器即可。
2)BS架构可以直接放在广域网上,通过一定的权限控制实现多客户访问的目的,交互性较强。
3)BS架构无需升级多个客户端,升级服务器即可。
缺点:
1)在跨浏览器上,BS架构不尽如人意。
2)表现要达到CS程序的程度需要花费不少精力。
3)在速度和安全性上需要花费巨大的设计成本,这是BS架构的最大问题。
4)客户端服务器端的交互是请求-响应模式,通常需要刷新页面,这并不是客户乐意看到的。(在Ajax风行后此问题得到了一定程度的缓解)
三、B/S对C/S的改进和扩展
正如前文所说,C/S和B/S都可以进行同样的业务处理,但是B/S随着Internet技术的兴起,是对C/S结构的一种改进或者扩展的结构。相对于C/S,B/S具有如下优势:
1、分布性:可以随时进行查询、浏览等业务
2、业务扩展方便:增加网页即可增加服务器功能
3、维护简单方便:改变网页,即可实现所有用户同步更新
4、开发简单,共享性强,成本低,数据可以持久存储在云端而不必担心数据的丢失。
8 简述 三次握手、四次挥手的流程。
3次握手过程详解
所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
(1)第一次握手:
Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:
Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:
Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
SYN攻击:
在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN攻击是一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行:
#netstat -nap | grep SYN_RECV
4次挥手过程详解
三次握手耳熟能详,四次挥手估计就少有人知道了。所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
第一次挥手:
Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:
Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
第三次挥手:
Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
第四次挥手:
Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:
流程和状态在上图中已经很明了了,在此不再赘述,可以参考前面的四次挥手解析步骤。
关于三次握手与四次挥手通常都会有典型的面试题,在此提出供有需求的XDJM们参考:
(1) 三次握手是什么或者流程?四次握手呢?答案前面分析就是。
(2) 为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
9 什么是arp协议?
arp协议由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送,在谈及以太网协议时候,我门了解到通信是基于mac的广播方式实现,计算机在发包时,获取自身的mac是容易的,如何获取目标主机的mac,就需要通过arp协议
arp协议功能:广播的方式发送数据包,获取目标主机的mac地址
协议工作方式:每台主机ip都是已知的
例如:主机172.16.10.10/24访问172.16.10.11/24
一:首先通过ip地址和子网掩码区分出自己所处的子网
场景 数据包地址
同一子网 目标主机mac,目标主机ip
不同子网 网关mac,目标主机ip
二:分析172.16.10.10/24与172.16.10.11/24处于同一网络(如果不是同一网络,那么下表中目标ip为172.16.10.1,通过arp获取的是网关的mac)
源mac 目标mac 源ip 目标ip 数据部分
发送端
主机
发送端
mac
FF:FF:FF:FF:FF:FF 172.16.10.10/24 172.16.10.11/24 数据
三:这个包会以广播的方式在发送端所处的自网内传输,所有主机接收后拆开包,发现目标ip为自己的,就响应,返回自己的mac
10 TCP和UDP的区别?
TCP与UDP区别总结:
a、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
b、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
c、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
d.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
e、TCP对系统资源要求较多,UDP对系统资源要求较少。
为什么UDP有时比TCP更有优势?
UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。
(1)网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
(2)TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。
采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响
UDP和TCP编程步骤也有些不同,如下:
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接; closesocket(SocketListen)
8、关闭监听;
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
与之对应的UDP编程步骤要简单许多,分别如下:
UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;
UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;
1 什么是局域网、广域网、城域网?
①局域网LAN(Local Area Network):一般指覆盖范围在10公里以内,一座楼房或一个单位内部的网络。由于传输距离直接影响传输速度,因此,局域网内的通信,由于传输于距离短,传输的速率一般都比较高。目前,局域网的传输速率一般可达到10MB/S和100MB/S,高速局域网传输速率可达到1000MB/S。②广域网WAN(Wide Area Network):是指远距离的、大范围的计算机网络。跨地区、跨城市、跨国家的网络都是广域网。由于广域的覆盖范围广,联网的计算机多,因此广域网上的信息量非常大,共享的信息资源很丰富。INTERNET是全球最大的广域网,它覆盖的范围遍布全世界。③城域网MAN(Metropolitan Area Network):其覆盖范围在局域网和广域网之间。一般指覆盖范围为一个城市的网络。
2 什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?
socket粘包:
socket 交互send时,连续处理多个send时会出现粘包,soket会把两条send作为一条send强制发送,会粘在一起。
send发送会根据recv定义的数值发送一个固定的数值,如果最后一次,所剩的数值小于recv定义数就会连带两条send数据同时发送,发生粘包状况。
解决方案:
方案1:可以使用time.sleep 在两send之间加上时间(不建议)
方案2:可以在send两条之间加入一条 conn.recv(1024)
服务端
conn.send(str(len(cmd_res.encode())).encode("utf-8"))
client_ack = conn.recv(1024) #wait client to confirm
conn.send(cmd_res.encode("utf-8"))
客户端
client.send("准备好接收了,loser可以发了".encode("utf-8"))
原理:recv在接收数据时是一个堵塞状态,自动卡在中间,
客户会自动返回client_ack的数据信息,相当于两个
send之间多了一次交互,就不会出现粘包情况。
方案3:通过if判断实现粘包解决(推荐)
while 总数值 > 递增接收数据:
if 总数值 - 递增接收数据 > recv(1024): #比定义值大就成立
size = 1024
else: #最后一次
size = 总数值 - 递增接收数据 #剩的数值
recv(size) #赋值给 recv
3 IO多路复用的作用?
什么是文件描述符
我们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO、管道、终端,对我们来说,一切都是文件,一切都是流。在信息 交换的过程中,我们都是对这些流进行数据的收发操作,简称为I/O操作(input and output),往流中读出数据,系统调用read,写入数据,系统调用write。不过话说回来了 ,计算机里有这么多的流,我怎么知道要操作哪个流呢?对,就是文件描述符,即通常所说的fd,一个fd就是一个整数,所以,对这个整数的操作,就是对这个文件(流)的操作。我们创建一个socket,通过系统调用会返回一个文件描述符,那么剩下对socket的操作就会转化为对这个描述符的操作。不能不说这又是一种分层和抽象的思想。
阻塞?
什么是程序的阻塞呢?想象这种情形,比如你等快递,但快递一直没来,你会怎么做?有两种方式:
快递没来,我可以先去睡觉,然后快递来了给我打电话叫我去取就行了。
快递没来,我就不停的给快递打电话说:擦,怎么还没来,给老子快点,直到快递来。
很显然,你无法忍受第二种方式,不仅耽搁自己的时间,也会让快递很想打你。
而在计算机世界,这两种情形就对应阻塞和非阻塞忙轮询。
非阻塞忙轮询:数据没来,进程就不停的去检测数据,直到数据来。
阻塞:数据没来,啥都不做,直到数据来了,才进行下一步的处理。
先说说阻塞,因为一个线程只能处理一个套接字的I/O事件,如果想同时处理多个,可以利用非阻塞忙轮询的方式,伪代码
while true:
for i in stream[]:
if i has data:
read until unavailable
我们只要把所有流从头到尾查询一遍,就可以处理多个流了,但这样做很不好,因为如果所有的流都没有I/O事件,白白浪费CPU时间片。正如有一位科学家所说,计算机所有的问题都可以增加一个中间层来解决,同样,为了避免这里cpu的空转,我们不让这个线程亲自去检查流中是否有事件,而是引进了一个代理(一开始是select,后来是poll),这个代理很牛,它可以同时观察许多流的I/O事件,如果没有事件,代理就阻塞,线程就不会挨个挨个去轮询了,伪代码如下:
while true:
select(streams[]) //这一步死在这里,知道有一个流有I/O事件时,才往下执行
for i in streams[]:
if i has data
read until unavailable
但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))伪代码如下:
while true:
active_stream[] = epoll_wait(epollfd)
for i in active_stream[]:
read or write till
可以看到,select和epoll最大的区别就是:select只是告诉你一定数目的流有事件了,至于哪个流有事件,还得你一个一个地去轮询,而epoll会把发生的事件告诉你,通过发生的事件,就自然而然定位到哪个流了。不能不说epoll跟select相比,是质的飞跃,我觉得这也是一种牺牲空间,换取时间的思想,毕竟现在硬件越来越便宜了。
I/O多路复用
好了,我们讲了这么多,再来总结一下,到底什么是I/O多路复用。
先讲一下I/O模型:
首先,输入操作一般包含两个步骤:
等待数据准备好(waiting for data to be ready)。对于一个套接口上的操作,这一步骤关系到数据从网络到达,并将其复制到内核的某个缓冲区。
将数据从内核缓冲区复制到进程缓冲区(copying the data from the kernel to the process)。
其次了解一下常用的3种I/O模型:
阻塞I/O模型
最广泛的模型是阻塞I/O模型,默认情况下,所有套接口都是阻塞的。 进程调用recvfrom系统调用,整个过程是阻塞的,直到数据复制到进程缓冲区时才返回(当然,系统调用被中断也会返回)。
非阻塞I/O模型
当我们把一个套接口设置为非阻塞时,就是在告诉内核,当请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。当数据没有准备好时,内核立即返回EWOULDBLOCK错误,再次调用系统调用时,数据已经存在,这时将数据复制到进程缓冲区中。这其中有一个操作时轮询(polling)。
I/O复用模型
此模型用到select和poll函数,这两个函数也会使进程阻塞,select先阻塞,有活动套接字才返回,但是和阻塞I/O不同的是,这两个函数可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监视所有select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,我们就可以调用recvfrom处理数据。
正因为阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫做多路复用。
4 简述 进程、线程、协程的区别 以及应用场景?
线程和进程:
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。
线程、进程与协程:
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保持状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景: 当程序中存在大量不需要CPU的操作时(IO),适用于协程;
5 GIL锁是什么鬼?
我们所说的Python全局解释锁(GIL)简单来说就是一个互斥体(或者说锁),这样的机制只允许一个线程来控制Python解释器。
这就意味着在任何一个时间点只有一个线程处于执行状态。GIL对执行单线程任务的程序员们来说并没什么显著影响,但是它成为了计算密集型(CPU-bound)和多线程任务的性能瓶颈。
由于GIL即使在拥有多个CPU核的多线程框架下都只允许一次运行一个线程,所以在Python众多功能中其声誉可谓是“臭名昭著”。
在这篇文章中,你将了解到GIL是如何影响到你的Python程序性能的以及如何减轻它对代码带来的影响。
GIL解决了Python中的什么问题?
Python利用引用计数来进行内存管理,这就意味着在Python中创建的对象都有一个引用计数变量来追踪指向该对象的引用数量。当数量为0时,该对象占用的内存即被释放。
我们来通过一个简单的代码演示引用计数是如何工作的:
在上述例子中,空列表对象[ ]的引用计数为3。该列表对象被a、b和传递给sys.getrefcount( )的参数引用。
回到GIL本身:
问题在于,这个引用计数变量需要在两个线程同时增加或减少时从竞争条件中得到保护。如果发生了这种情况,可能会导致泄露的内存永远不会被释放,抑或更严重的是当一个对象的引用仍然存在的情况下错误地释放内存。这可能会导致Python程序崩溃或带来各种诡异的bug。
通过对跨线程分享的数据结构添加锁定以至于数据不会不一致地被修改,这样做可以很好的保证引用计数变量的安全。
但是对每一个对象或者对象组添加锁意味着会存在多个锁这也就导致了另外一个问题——死锁(只有当存在多个锁时才会发生)。而另一个副作用是由于重复获取和释放锁而导致的性能下降。
GIL是解释器本身的一个单一锁,它增加的一条规则表明任何Python字节码的执行都需要获取解释锁。这有效地防止了死锁(因为只存在一个锁)并且不会带来太多的性能开销。但是这的确使每一个计算密集型任务变成了单线程。
GIL虽然也被其他语言解释器使用(如Ruby),但是这不是解决这个问题的唯一办法。一些编程语言通过使用除引用计数以外的方法(如垃圾收集)来避免GIL对线程安全内存管理的请求。
从另一方面来看,这也意味着这些语言通常需要添加其他性能提升功能(如JIT编译器)来弥补GIL单线程性能优势的损失。
为什么选取GIL作为解决方案?
那么为什么在Python中使用了这样一种看似绊脚石的技术呢?这是Python开发人员的一个错误决定么?
正如Larry Hasting所说,GIL的设计决定是Python如今受到火热追捧的重要原因之一。
当操作系统还没有线程的概念的时候Python就一直存在着。Python设计的初衷是易于使用以便更快捷地开发,这也使得越来越多的程序员开始使用Python。
人们针对于C库中那些被Python所需的功能写了许多扩展,为了防止不一致变化,这些C扩展需要线程安全内存管理,而这些正是GIL所提供的。
GIL是非常容易实现而且很容易添加到Python中。因为只需要管理一个锁所以对于单线程任务来说带来了性能提升。
非线程安全的C库变得更容易集成,而这些C扩展则成为Python被不同社区所接受的原因之一。
正如您所看到的,GIL是CPython开发者在早期Python生涯中面对困难问题的一种实用解决方案。
对多线程Python程序的影响
当你留意一些典型的Python程序或任何计算机程序时你会发现一个程序针对计算密集型和I/O密集型任务之间的性能表现是有所差异的。
计算密集型任务是那些促使CPU达到极限的任务。这其中包括了进行数学计算的程序,如矩阵相乘、搜索、图像处理等。
I/O密集型任务是一些需要花费时间来等待来自用户、文件、数据库、网络等的输入输出的任务。I/O密集型任务有时需要等待非常久直到他们从数据源获取到他们所需要的内容为止。这是因为在准备好输入输出之前数据源本身需要先进行自身处理。举例来说,一个用户考虑在输入提示中输入什么或者在其自己进程中运行的数据库查询。
让我们先来看一个执行倒计时的简单的计算密集型程序:
在我的4核系统上运行得到以下输出:
接下来我对代码做出微调,使用两个线程并行处理来完成倒计时:
接下来我再次运行:
正如你所看到的,两个版本的完成时间相差无几。在多线程版本中GIL阻止了计算密集型任务线程并行执行。
GIL对I/O密集型任务多线程程序的性能没有太大的影响,因为在等待I/O时锁可以在多线程之间共享。
但是对于一个线程是完全计算密集型的任务来说(例如,利用线程进行部分图像处理)不仅会由于锁而变成单线程任务而且还会明显的增加执行时间。正如上例中多线程与完全单线程相比的结果。
这种执行时间的增加是由于锁带来的获取和释放开销。
如何处理Python中的GIL?
如果GIL给你带来困扰,你可尝试一下方法:
多进程vs多线程:最流行的方法是应用多进程方法,在这个方法中你使用多个进程而不是多个线程。每一个Python进程都有自己的Python解释器和内存空间,因此GIL不会成为问题。Python拥有一个multiprocessing模块可以帮助我们轻松创建多进程:
在系统上运行得到
相比于多线程版本,性能有所提升。
但是时间并没有下降到我们之前版本的一半,这是因为进程管理有自己的开销。多进程比多线程更“重”,因此请记住,这可能成为规模瓶颈。
替代Python解释器:Python中有多个解释器实现办法,分别用C,Java,C#和Python编写的CPython,JPython,IronPython和PyPy是最受欢迎的。GIL只存在于传统的Python实现方法如CPython中。如果你的程序及其库文件可以通过别的实现方式实现,那么你也可以尝试一下。
等等看吧:许多用户利用GIL提升了单线程任务性能表现。当然多线程程序员们也不必为此烦恼,因为Python社区内的一些聪明大脑们正在致力于从CPython中删除GIL。其中一种尝试为Giletomy。
Python GIL经常被认为是一个神秘而困难的话题。但是请记住作为一名Python支持者,只有当您正在编写C扩展或者您的程序中有计算密集型的多线程任务时才会被GIL影响。
在这种情况下,这篇文章应该给了你需要的一切去了解GIL是什么以及如何在自己的项目中处理它。如果您希望了解GIL的低层次内部运行,我建议您观看David Beazley的Understanding the Python GIL。