本人做过8年的Java开发,后来有接触了2年的python,这两种语言各有各的味道。Java就像德国战车,各种精密的零件配合起来庞大无比力拔山兮!python像红色法拉利,没有战车那么强悍的战斗力但是灵活迅捷充满艺术之美。
我想通过两篇博文来好好的深挖一下python,第一篇从深层次的细节入手再“深度学习”一次python;第二篇从宏观原则上来分析下到底怎么写python代码才能把它写成一辆法拉利(至少达到保时捷的要求吧),而不是写成一辆BYD。
首先,我们要对python充满信心,经过码农门的精雕吸啄,我们可以让程序及有很好的可读性,又能很简单的解决问题,这就是艺术之美。
先看个快排的例子:
def quicksort(array) :
less, lagger = [],[]
if len(array)<=1 :
return array
element = array.pop()
for obj in array :
if obj <= element : less.append(obj)
else : lagger.append(obj)
return quicksort(less) + [element] + quicksort(lagger)
如果有种语法简练而且可读性很好的感觉,那么就说明我们写的代码是合格的。
为了写出更多合格的代码,我们必须深挖一些python语言的细节,python到底能干什么,还有哪些我们没有pick up的知识点?本文讲跟大家一一过滤一遍。
1 变量交换
Java中需要定义个中间变量,先把其中一个赋值给中间变量,然后互换,再然后把中间变量的赋值给另一个。
Python直接:x,y=y,x
在底层是根据右边的表达式先生成了一个tuple(y,x),然后按顺序赋值给左边的变量。
2 %占位符
在3个以内的占位%使我们能够接受的,一旦更多的字符变量就会让程序的可读性变得很差,我们就需要换另一种写法了:
people = {'name':'chalie','age':20,'male':'he','county':'USA'}
print('%(name)s is %(age)d years old, and %(male)s comes from %(county)s' %(people))
3 学习一些高质量的模块
多了解一些符合Pythonic的模块(flask、requests等),可以最大程度的简化代码,并提高可读性。
关于flask我打算单独拿出一个主题好好讲解一次,欢迎持续关注.
x=1
y=2
assert x==y, 'not equal'
低层实现是:
if __debug__ and not x==y :
raise AssertionError('not equal')
虽然我们可以通过python –O xxx.py的方式运行时忽视断言,但是并不会优化字节码,所以断言的使用要慎重,不是越多越好。
5 枚举
import enum
class myEnum(enum.Enum) :
Zero = 0
One = 1
Two = 2
Other = 99
if __name__ == '__main__':
oneEnum = myEnum.One.value
fourEnum = myEnum['Other'].value
print(oneEnum)
print(fourEnum)
6 类型检查
由于python底层是强对象而语法上是弱对象类型,所以必要时候需要做好类型检查,格式如下:
class typeA :
pass
class typeB:
pass
if __name__ == '__main__':
a = typeA()
b = typeB()
print(isinstance(a,(typeA)))
print(isinstance(b, (typeA)))
PS:类型检查与python的鸭子原理又是背道而驰的,所以只需要做理解,不到万不得已不建议使用,只要知道有这个能力就好。
7 yield
先从表面来看,有写过oracle存储过程经历的开发人员对yield最好的类比就是存过里面的游标,它是一个生成器,里面将会不断的吐出内容,直到所有内容吐完后游标会关闭。而且它是延时执行的,也就是说不像集合那样是一直占用内存的,只有当游标打开处理每个数据的时候才会真正被执行。
案例代码:
def dofunction(numList):
for i in numList :
yield i*i
if __name__ == '__main__' :
numList = [1,2,3,4,5]
for x in dofunction(numList) :
print(x)
本质上是用了协程,每send一次执行到下一个yield,全代码应该如下:
if __name__ == '__main__' :
numList = [1,2,3,4,5]
pump = dofunction(numList)
while 1:
try:
print(pump.send(None))
except StopIteration :
break
本篇后面有对生成器的详细介绍,看到后面就会理解yield的原理了
myList = [[1,2], [3,4], [5,6], [7,8], [9,0]]
for i,listValue in enumerate(myList) :
print('%d index , value is %s' %(i,str(listValue)))
myMap = {'A':1,'B':2,'C':3,'D':4}
for key,mapValue in myMap.items() :
print('%s key , value is %s' % (key, str(mapValue)))
9 __init__.py文件
作用1:指明当前目录是个python的包
作用2:级联导入。我不知道我取的“级联导入”这个名字正确与否,网上有很多种叫法,我给他的叫法就是级联导入吧。
digit想要调用mianshi这个包里的内容,其中mianshi内还包含一个叫taxi的子包。digit、mianshi、taxi目录下都有一个__init__.py文件,有这个文件它们这写目录才能被称为是python的包。
from mianshi.taxi import arrayTest
import mianshi.aop
from mianshi.taxi import arrayTest
我只要在digit中import mianshi就可以直接使用arrayTest模块了。
OK,叨叨了这么多,思路就是一个,init文件更像是个API接口或者叫API路由,它里面的内容非必须的,你当然可以饶过它自己在外层想用啥导入啥。但是在面向服务编程的思想来考虑,作为一个包,我不需要调用我的客户端关心我内部是放在那个模块下它们之间的引入关系是什么样子的,我只要把我提供服务的能力暴漏在init中就可以了。个人认为这是init设计的初中,面向服务编程。
如果想把所有模块和函数都公开,也不需要一条条的写,直接在init中添加__all__ = ['aop','hello','duck']
10 上下文管理器 with语句
__enter__()方法在语句执行之前进入运行时上下文,__exit__()在
with context_expression [as target(s)]:
with-body
class AddContext(object) :
def __init__(self,x,y):
self.x = x
self.y = y
def __enter__(self):
print('Transformation')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
print('Success')
return False
elif exc_type is ValueError :
print('Value Error')
return True
else:
print('Other Error')
return True
def doAdd(self):
return int(self.x)+int(self.y)
if __name__ == '__main__':
with AddContext('1','3') as addContext:
print(addContext.doAdd())
with AddContext('1','a') as addContext:
print(addContext.doAdd())
运行结果是:
Transformation
4
Success
Transformation
Value Error
整个处理过程是:构建对象à调用__enter()__函数并将返回值给as后的变量à执行with-body的内容,上面案例就是调用了doAdd()方法à所有都处理完后调用__exit()__函数。
with open(‘text.txt’,’w’) as file :
file.write(‘Hello world’)
省去了try finally关闭流等一些列体力活。
11 python对空值的判断
a = 0
b = {}
c = []
d = None
e = ''
if a or b or c or d or e :
print('True')
else:
print('False')
输出为False
低层判断是先调用一个类的 __nonzero()__函数,该函数返回boolean类型或者0/1;如果没有定义__nonzero()__函数将会调用__len()__函数,该函数返回内容的长度,如果返回0则认为为空。如果既没有定义nonzero()函数也没有定义len()函数,则返回True,也就是非空。
12 字符串的连接+和join
这个就像Java中的String和StringBuffer的区别了,点到为止,大家都应该懂。
13 str()函数和repr()函数的区别
在效果上大部分是相同的,但是两个侧重点不一样,str()像是Java的toString,侧重的描述信息;repr()是面向python解析器的,是准确性的。这两个函数分别调用对象内置的__str()__函数和__repr()__函数,如果__str()__不存在的话就用__repr()__来代替。
14 sort()和sorted()的区别
这两个函数功能类似,但是使用方法大不相同。
Sort()是列表list自带的方法,而且是对列表内部进行排序,并不会产生新的对象。
Sorted()是个辅助函数,入参是需要排序的对象,出参是排序完成后的对象。
listx = ['C','B','D','A']
tupley = ('C','B','D','A')
print(listx.sort())
print(listx)
newy = sorted(tupley)
print(newy)
None
['A', 'B', 'C', 'D']
['A', 'B', 'C', 'D']
因为tuple是不能改变的,而sort()是内部的排序,所以tuple就没有sort()功能,只能通过sorted()进行排序产生新的列表。
我使用他们两个的原则是:能用sort就用sort,因为它简单而且省资源,不能用sort就来用sorted,因为sorted功能太强大能处理很多sort处理不了的事情。
下面代码我要尝试给一个班级的考试结果排个名词:
oldMap = {'Tony':99,'Ivy':79,'Eva':56,'Charlie':100,'Lucy':95}
newMap = sorted(oldMap.items(),key=lambda item : item[1],reverse=True)
print(newMap)
结果:
[('Charlie', 100), ('Tony', 99), ('Lucy', 95), ('Ivy', 79), ('Eva', 56)]
15 积极使用collections模块,里面提供了很多集合操作的辅助函数
oldList = ['ab','bc',3,'d','bc',90,'e',3]
from collections import Counter
print(Counter(oldList))
textCounter = Counter('Hello World')
print(textCounter)
#update定义有些异议,这里应该是insert或者append的意思
textCounter.update('This is a new Hello World')
print(textCounter.most_common(3))
结果:
Counter({'bc': 2, 3: 2, 'ab': 1, 'd': 1, 90: 1, 'e': 1})
Counter({'l': 3, 'o': 2, 'H': 1, 'e': 1, ' ': 1, 'W': 1, 'r': 1, 'd': 1})
[('l', 6), (' ', 6), ('o', 4)]
如果不使用collections里的工具,我们自己去实现就要用嵌套循环或者匿名函数之类的才可以达到效果,代码会又臭又长。
16 configparser
configparser管理python运行时配置文件的模块,就像Java项目中的properties或yml文件那样,不过configparser管理的文件有自己的格式。
代码:
import configparser
#演示configparser的写
configWrite = configparser.RawConfigParser()
configWrite.add_section('Section1')
configWrite.set('Section1','ip','192.168.225.133')
configWrite.set('Section1','port','21')
configWrite.set('Section1','username','admin')
configWrite.set('Section1','password','admin123')
configWrite.set('Section1','url','%(username)s %(password)s@%(ip)s:%(port)s')
configWrite.set('Section1','nothing')
with open('C:\\python\\tmp\\test.cfg','w') as configfile:
configWrite.write(configfile)
#演示configparser的读
configReader = configparser.RawConfigParser()
configReader.read('C:\\python\\tmp\\test.cfg')
#这个输出并没有带入我们要的参数
print(configReader.get('Section1','url'))
configReader = configparser.ConfigParser()
configReader.read('C:\\python\\tmp\\test.cfg')
#这个输出有了我们想要的内容
print(configReader.get('Section1','url'))
configReader.set('Section1','ip','localhost')
print(configReader.get('Section1','url'))
print(configReader.get('Section1','nothing'))
生成的文件test.cfg内容如下:
[Section1]
ip = 192.168.225.133
port = 21
username = admin
password = admin123
url = %(username)s %(password)s@%(ip)s:%(port)s
nothing = None
运行结果:
%(username)s %(password)s@%(ip)s:%(port)s
admin [email protected]:21
admin admin123@localhost:21
None
17 xml解析
Python对于xml解析方式有基于流式的sax解析(xml.sax)、基于内存构建的dom解析(xml.dom)、推荐使用的ElementTree解析(xml.etree.ElementTree)
篇幅问题,可以重点再去了解下ElementTree,特别是它自带的辅助工具函数.
18 python的序列化
Java有自己的序列化和反序列化方式,Python也一样,Python负责序列化的模块是pickle,分为dump和load两个动作.
import pickle
datalist = ['A','c',3,'5']
with open('C:\\python\\tmp\\data.dat','wb') as file :
pickle.dump(datalist,file)
with open('C:\\python\\tmp\\data.dat','rb') as file :
out = pickle.load(file)
print(out)
print(type(out))
dumpData = pickle.dumps(datalist)
rebuildEntity = pickle.loads(dumpData)
print(rebuildEntity)
print(type(rebuildEntity))
['A', 'c', 3, '5']
['A', 'c', 3, '5']
从代码可读和简洁性来讲非常完美,但是pickle致命的缺陷是只能在python之间玩耍,如果要跨语言需要用json xml等方式.随着微服务化的火爆以及RestFul接口的流行,Json俨然已经成为最热门的跨平台的序列化方式.
代码:
datalist = ['A','c',3,'5']
import json
jsonStr = json.dumps(datalist)
print(jsonStr)
rebuildEntity = json.loads(jsonStr)
print(rebuildEntity)
print(type(rebuildEntity))
结果:
["A", "c", 3, "5"]
['A', 'c', 3, '5']
可以看出json与pickle一样都是dump和load两个动作(python的鸭子原理..),所以理解和使用起来方便无比.
既然Json和Pickle都可以做序列化,我们把它们之间的优缺点领出来对比下,就可以知道什么场景下需要用哪种序列化了.
Pickle:
优点:二进制流,效率高,可以方便处理复杂类型(它有个叫cPickle的兄弟,效率更高)
缺点:只python内部使用,无法跨平台
Json:
优点:跨平台,可读性高
缺点:复杂类型很难处理,效率低
下图是json、pickle、cPickle性能上的差距:
19 python的日志
关于python的日志,我主要用了python自带的logging模块,但是由于日志的重要性以及其篇幅和代码比较多,我单独拆开了一篇专门介绍python日志《logging—python日志之深入浅出》.
20 单利模式的实现
import threading
class Singleton(object) :
_singleInstance = None
locker = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._singleInstance is None :
cls.locker.acquire()
try :
cls._singleInstance = super(Singleton, cls).__new__(cls, *args, **kwargs)
finally:
cls.locker.release()
return cls._singleInstance
if __name__ == '__main__' :
entity_x = Singleton()
entity_y = Singleton()
print(entity_x is entity_y)
但是它存在一些缺陷:第一,子类可以覆盖__new__使其失去单利性,第二,__init__方法还是会多次初始化,这不是单利模式的本质。
所以python中最好的单利模式是利用模块,模块是天然的单利模式,我想要单利的部分单独拆分成一个模块,模块single.py:
class Singleton(object) :
pass
__entity = Singleton()
def getSingleEntity():
return __entity
调用时直接引入single.py既可:
from pyart import single
if __name__ == '__main__' :
entityx = single.getSingleEntity()
entityy = single.getSingleEntity()
print(entityx is entityy)
21 python中的订阅发布
先看一段Java代码:
public static void main(String[] args) {
HashMap myMap = new HashMap();
myMap.put("myKey","myValue");
System.out.println(myMap.get("noKey"));
}
结果打印是null,因为我们的map里没有这个键值对。
再回头看一段python代码:
myDict = {'myKey':'myValue'}
print(myDict['noKey'])
结果报错了:KeyError: 'noKey'
所以python中用dictionary(字典)来定义这种键值对模式,而不是用java里的table或者map,是有原因的,他们当key找不到的时候给出的处理结果完全不同,所以写过java的同学在用python的字典时要特别小心,不要入坑。Python中与map相似的key不存在时返回空的是另一个类型“defaultdict”,我们利用它来写一个订阅发布模式。
from collections import defaultdict
route_table = defaultdict(list)
class Broker(object):
# 订阅,不准重复订阅
def sub(self, topic, callback):
if callback in route_table[topic]: return
route_table[topic].append(callback)
# 消费
def pub(self, topic, *a, **kw):
for func in route_table[topic]:
func(*a, **kw)
def comsumer1(name) :
print('comsumer1 %s' %name)
def comsumer2(name) :
print('comsumer2 %s' %name)
if __name__ == '__main__' :
broker = Broker()
broker.sub('myjob',comsumer1)
broker.sub('myjob', comsumer2)
broker.pub('myjob','input')
broker.pub('myjob', 'input2')
结果是:
comsumer1 input
comsumer2 input
comsumer1 input2
comsumer2 input2
代码很好理解,有个细节需要注意下:
defaultdict(list),有点泛型的意思,对应java的是HashMap
Python有很多上层封装的很好的、语法更简洁的轮子可以用,像订阅发布我们可以用blinker来搞定,代码:
import blinker
def comsumer(name):
print('comsumer %s' %name)
if __name__ == '__main__':
mycomsumer = blinker.signal('myjob')
mycomsumer.connect(comsumer)
myproducer = blinker.signal('myjob')
#证明是单利模式
print(mycomsumer is myproducer)
myproducer.send('Hello')
怎么样,使用起来可读性更好更简洁了吧!mycomsumer和myproducer是同一个实例,所以signal里是单利模式,消费者通过connect进行订阅,生产者通过send进行发布。
订阅发布的意义在于功能上解耦,connect方法的入参是个函数,我可以丢任何的函数给队列,而整个队列只需要维护好主题(title)就好,帮我做好转发。所以当你的应用场景是要对某个信息做很多种不确定操作,可以考虑考虑订阅发布模式。
22 python中的工厂模式
代码:
class Animal(object):
def run(self):
print('Animal is running')
class Ant(object):
def run(self):
print('Ant is running')
class Ship(object):
def run(self):
print('Ship is running')
class Bee(object):
def run(self):
print('Bee is running')
class Snake(object):
def run(self):
print('Snake is running')
class AnimalFactory(object):
__animalMap = {'ant':Ant,'ship':Ship,'bee':Bee,'snake':Snake}
def __new__(cls, name):
if name in cls.__animalMap.keys():
return cls.__animalMap[name]()
else:
return Animal()
if __name__ == '__main__':
AnimalFactory('ship').run()
AnimalFactory('duck').run()
23 LEGB法则
这个法则不是什么新鲜货,Java语言中也有,涉及到参数的作用域,以及在当前位置按范围从小大到搜索变量的规则:localàenclosingàglobalàbuild-in.也就是说从本函数内先找,找不到就去嵌套作用域里去找,还是找不到就去模块的global里面找(包括导入进来的),还是找不到就去内置的里面去找(有点像java里的lang包)
这个设计基本都是编程语言通用的方式,不难理解,这里点出来是因为这知识点经常被拿来面试,而且LEGB简写乍一听有点懵圈。
24 MRO与多继承
又一个绕口的英文简写(Method Resolution Order方法解析顺序),这个知识点还一个问法就是python的菱形继承。
因为python不像java那样是单继承,所以很容易出现菱形继承,一旦出现这种样式的继承很难判断函数来自那个爹或者爷爷。
网上大量的资料在介绍,古典型怎么个结果,新类型怎么个结果,我在python3.6里都试验过就一个结果(不要跟我讲默认是新类型,默认的就是我们要学的,记太多过去的东西不容易混淆么)。
当调用D的getValue()时,按照D继承时括号中从左到右的顺序,先找到就哪个;当D调用show()时,按照血缘关系近的那个,也就是按照爹C的而不是爷爷A的。
就这一个规则,记住就好。
25 property讲解
在Java中,持久层入库的实体类,或者action层处理表单的form类都属于pojo类的写法,java代码如下
public class Tester {
public static final String HELLO = "hello";
private String name;
private int age;
public String getName() {
return HELLO+name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>120 || age<0) {
throw new RuntimeException("age is error");
}
this.age = age;
}
}
变量私有,提供公共的get set方法来操作变量,而且可以在get set中添加转换和校验等。
Python中同样会遇到这种场景,我们当然可以仍然使用getset方法来写我们的代码,但是这失去了python编程的特点,我们想直接通过操作属性的方式来操作get set方法,于是乎借助下property。
python代码如下:
class Tester(object):
hello = 'hello '
def __init__(self,name,age):
self._name = name
self._age = age
def name_get(self):
return self.__class__.hello+self._name
def name_set(self,name):
self._name = name
def age_get(self):
return self._age
def age_set(self,age):
if age>120 or age<0 :
raise AssertionError('age is error')
n = property(name_get,name_set)
a = property(age_get, age_set)
if __name__ == '__main__':
tester = Tester('yejingtao',20)
print(tester.n)
tester.n='lucy'
print(tester.n)
tester.a=130
输出结果:
hello yejingtao
hello lucy
AssertionError: age is error
我个人比较推崇这种写法,网上还介绍了一种写法,个人觉得不如上面这种可读性高,也给大家敲个案例:
class Tester(object):
hello = 'hello '
def __init__(self,name,age):
self._name = name
self._age = age
@property
def name(self):
return self.__class__.hello+self._name
@name.setter
def name(self,name):
self._name = name
@property
def age(self):
return self._age
@age.setter
def age(self,age):
if age>120 or age<0 :
raise AssertionError('age is error')
if __name__ == '__main__':
tester = Tester('yejingtao',20)
print(tester.name)
tester.name='lucy'
print(tester.name)
tester.age=130
26 Python的迭代器(iterator)和生成器(generator)
我们在python中经常通过for i in XXX这种格式来处理数据,用起来很爽,集合、迭代器、生成器都可以被for来处理,所以他们经常会被混淆,这里我们就好好解释下它们的区别和各自的原理。
首先把集合单独拿出来,集合与迭代器和生成器不同的是它是要实实在在将内容load到内存的,在处理大批量数据的时候对内存消耗非常大;而后两者有点像游标的设计,调用时一个个的吐数据来处理,所以性能上后两者要优于集合类型。
迭代器,主要包含__iter__和__next__两个方法,__iter__方法为了让调用方通过iter()函数来获取到迭代器本身,所以一般返回self自己;__next__方法是每次迭代真正执行的逻辑。
代码如下:
class MyIterator(object) :
def __init__(self,start):
self.start = start
#被iter()调用,返回self自己
def __iter__(self):
print('Invoke __iter__ function')
return self
#被next()调用
def __next__(self):
print('Invoke __next__ function')
self.start+=1
return self.start
#调用迭代器
def mokeFor(times):
myIter = iter(MyIterator(0))
while times>0 :
print(next(myIter))
times -=1
mokeFor(5)
num = 5
iterator = MyIterator(5)
for i in iterator:
print(i)
num-=1
if num==0 : break
执行结果:
Invoke __iter__ function
Invoke __next__ function
1
Invoke __next__ function
2
Invoke __next__ function
3
Invoke __next__ function
4
Invoke __next__ function
5
Invoke __iter__ function
Invoke __next__ function
6
Invoke __next__ function
7
Invoke __next__ function
8
Invoke __next__ function
9
Invoke __next__ function
10
我们可以看到mokeFor里面先通过iter()调用了迭代器的__iter__函数获取到迭代器自己,然后每次next()调用了迭代器内部的__next__函数完成真正的业务。
案例后面直接用for循环来处理迭代器,与mokeFor对比后可以猜的出来for内部也是iter()和next()的过程。
生成器与迭代器的原理完全不同,是通过协程yield关键字和next()、send()方法来操作的,让我们来看个例子:
def MyGenerator(start,times) :
while times>0 :
start += 1
print('befor yield')
inputVal = yield start
print('after yield %s' %inputVal)
times-=1
generator = MyGenerator(0,999)
print('------')
print(next(generator))
print('------')
print(generator.send('send1'))
print('------')
print(next(generator))
print('------')
print(generator.send('send2'))
print('------')
print(generator.close())
print('------')
#print(generator.send('send3'))
执行结果:
------
befor yield
1
------
after yield send1
befor yield
2
------
after yield None
befor yield
3
------
after yield send2
befor yield
4
------
None
------
看不懂没关系,我们把重点部分拿出来详细讲解下:
记住3个规则:
第一, yield所在语句等号左边是输入,yield后面的是输出
第二, obj.send(val)函数send里面就是对应着上面的输入,而next(obj)相当于obj.send(None),所以无论是send和next基本可以看成是等价的
第三, 每next或者send一次,就会停在我红框标注的地方,从右往左执行,只执行到返回输出的地方(标注了“前”的那一部分)。
好记住了这些原理再回头根据日志对照下吧,就很好理解为什么是这种输出了。
生成器还有个close()函数,一旦执行后相当于协程结束了,再来next或者send就会报错:“StopIteration”
生成器还有个优点,通过简单加工可以被with调用,例如:
@contextmanager
def MyGenerator() :
print('--start--')
yield 'for print'
print('--end--')
myGenerator = MyGenerator()
with myGenerator as g:
print(g)
27 又一个简写新名词(GIL)
GIL:Global InterpreterLock的缩写,中文叫全局解析器锁,这个锁限制了同一时间只能有一个线程在执行。对的你没有听错,换句话说就是:python不支持多线程。
你可能说明明有threading包啊,语法上是支持多线程的啊,可惜现实是那都是假象,python的多线程用通信行业的专业名词来讲是“时分多址”,所有的thread按照时间短竞争享用1个线程~。
你可能会问,这么个二货东西为什么还要保留这个限制?其实python语言发展方向上是想抛弃它,但是由于历史的包袱太重所以还得需要几个大版本的迭代后才能完成吧。
所以python所谓的高并发,什么协程、多线程、多进程,其实只有多进程才是真正意义上多CPU运算,协程和多线程都是单CPU的,所以在处理CPU密集型的场景时需要考虑多进程来处理,但是进程的维护又是很大的消耗,不如真正的多线程来的好。
简然之吧,不得不说python在处理高并发CPU密集型运算这方面是个很乏力的语言,不客气的讲“基本是个废物”!