选择的平台是Ubuntu16.04,发现里面居然已经有Python3.5.2了。不过Ubuntu应该是自带一个python2.*的,所以要输入Python3才是最新版本。
命令模式和交互模式
Python也像Lua一样有一个交互模式,终端输入Python即可。
输入输出
print()输出,比如print(“hello”,“world”)括号里可以可以用逗号隔开,print遇到逗号会输出一个空格
input()输入,比如name=input(),等待用户输入一个名字然后存到name中。可以在括号里写上一些提示,比如 name = input(“input your name:”)
注释
Python的注释用#
大小写敏感
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。
用\转义字符,常见的比如 \n 换行,\t 表示制表符等等,同样的用 \来转义\。如果有很多要转义的,那就需要写很多\,Python允许用 r ' ' 表示 ' ' 内部的字符串默认不转义
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,允许用 '''...''' 的格式表示多行内容,>>> 会变成 ... 如
上面是在交互模式下,如果是写在文件中的话,就写成这样
布尔值
只有 True和False , 注意大小写
与或非 和lua同样是 and or not
空值 None
算数运算的除法 / 和 //的区别 //取整数
4/3 = 1.3333333333333333
4//3 = 1
关于编码
ASCII只能表示英文,数字,以及一些标点符号,毕竟是美国人弄的。不能表示中文,当然也不能韩文,日文等等,然后各国就有了自己的编码,比如中国的GB2312把中文编码进去了。那么有那么多语言,就会有那么多各自不同的编码,难免会冲突,多语言混合的时候就会出现乱码。然后就有了Unicode把所有语言都统一到一套编码里。ASCII编码是1个字节,而Unicode编码通常是2个字节。如果都用Unicode,当你的文本大量是英文的时候,就造成了一倍的空间浪费。然后,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。
现在计算机系统通用的字符编码工作方式:在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
比如:用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件
在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言
对于单个字符的编码,Python提供了ord()
函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
比如 print(ord(‘a’)) #97
print(chr(97)) # a
Python对bytes类型的数据用带b前缀的单引号或双引号表示:b = 'ABC'.encode('ascii') # b = b'ABC'
len()函数显示字符串长度。
在操作字符串时,我们经常遇到str
和bytes
的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str
和bytes
进行转换。
格式化输出字符串,比如
'my name is %s , i am a %s' % ('philo' , 'student')
特别的,当%后面只有一个参数,可以省略括号,比如
'my name is %s' % 'philo'
关于代码规范
我发现在用vim写Python的时候,会提示一些看着奇怪的警告,查了下大概是Python代码规范?
http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/
记录一下实践出来的:
1.括号(包括各种括号)内的逗号前面不要有空格,但是逗号后面要有一个
2.行内的注释至少要在 #号前面有两个空格
3.注释应该以 '# '开始,也就是#后面要有一个空格,且只能有一个空格
4.dist中,键值对的冒号后面要有一个空格
list
l = ['kobe' , 'kg' , 'tracy'] 这样就表示了一个list,len()函数可以返回list的元素个数,与lua中table不同的是,这里的元素下表是从0开始的而不是1
特别的,用-1可以索引到倒数第一个元素,并且,可以以此类推
list里面的元素类型可以不一样,list里面还可以包含list,这样就是二维数组了,类似的,还可以继续有三维,四维……
l = ['kobe', 'kg', 'tim'] # 创建一个list a = len(l) # 用len()函数获取list长度 a = 3 l.append('tracy') # 用append()函数追加元素到list末尾 print(l[-1]) # tracy l.insert(2, 'Joe') # 插入元素到指定位置 print(l[2]) # Joe l.pop() # 删除最后一个元素 print(l[-1]) # tim l.pop(2) # 删除指定位置的元素 print(l[2]) # tim l[2] = 'curry' # 对指定位置赋值 print(l[2]) # curry
tuple
相对于list,tuple中的元素是不可变的,不可变也就意味着数据更安全。因此,也就没有增删改这些方法,但是还是可以同样通过索引来查询元素。
要注意的是这里的不可变的含义,不可变是指的“指向不变”,比如有一个tuple里面有一个list,list里面的元素是可以改变的,这是允许的,因为对于tuple来说,他里面list这个元素的指向并没有变
还有一个要注意的是,当tuple里面只有一个元素的时候,要另外加一个逗号来消除可能产生的歧义
t = ('kobe', 24, True) # 创建一个tuple t = (1) # 表示数字1,而不是一个tuple,这样就产生了歧义 t = (1, ) # 避免歧义,加一个逗号,这样t就表示一个tuple了 l = ['kobe', 'tracy'] t = ('a', 'b', l) print(t[2][0]) # kobe l[0] = 'tim' print(t[2][0]) # tim
条件判断
age = input() age = int(age) # 因为input输入的是str,str不能直接和整数比较,所以先转换成整数 if age >= 18: print('adult') elif age >= 6: print('teenager') else: print('kid')
Python是根据缩进来判断条件语句时候结束了的,不像Lua要加个end
names = ['kobe', 'tracy', 'jordan'] for name in names: print(name) sum = 0 # range(101)生成一个从0开始小于101的整数list for x in range(101): sum = sum + x print(sum) # 5050 sum = 0 n = 1 while n <= 100: sum = sum + n n = n + 1 print(sum) # 5050
dist
就是键值对存储,相当于Java中的map,用花括号{}和冒号来表示,通过方括号[]来索引
和list相比:
dist查找插入的时间很快,不会随着key增多而变慢,需要占用大量内存,浪费空间。(用空间换时间)
list查找和插入的时间随着元素增多而增多,占用空间小。 ####有点像是在比较Java中List和Map的区别
正确的使用dist要记住一条,key一定要是不能改变的,在Python中字符串和整数都是不能改变的,但是list不是,因此list是不能作为key的
d = { 'kobe': 24, 'tim': 21, 'arenas': 0} print(d['kobe']) # 24 d['kobe'] = 8 print(d['kobe']) # 8 if 'tim' in d: # 通过in来判断dict里面时候有该key,返回布尔值 print('tim in d')
set
set是一组key的集合,但没有value,set中没有重复元素,创建一个set要提供一个list作为输入集合。
和Java中set的概念大概差不多?无序不重复
通过add(key)添加元素,添加了重复元素,也只会有一个存在。remove(key)删除元素
& 来执行两个set的交, | 来执行两个set的并
s = set([1, 2, 3]) print(s) # {1, 2, 3} s.add(99) print(s) # {99, 1, 2, 3} s.remove(2) print(s) # {99, 1, 3} s1 = set([1, 2, 3]) s2 = set([2, 5, 1]) print(s1 & s2) # {1, 2} print(s1 | s2) # {1, 2, 3, 5} # 不能有list这种可变的元素,因为可变,所以无法计算哈希值 # s = set([1, 2, 4, [1, 2]]) # TypeError: unhashable type: 'list'
关于字符串是不可变的,见下面的代码
a = 'abc' a.replace('a', 'A') print(a) # abc,a指向的内容没有被改变,即字符串是不可变的 b = a.replace('a', 'A') print(b) # Abc
函数
# 定义一个函数def my_abs(x): if x>0: return x else: return -x
上述代码写在fun.py里面,那么在交互模式,可以用 from fun import my_abs 来导入这个函数,然后就可以直接使用了
空函数
当需要一个什么事都不做的函数,可以用pass语句
有一种这样的情景,当还没想好函数里面写什么的时候,可以用个空函数,让代码先跑起来,pass还可以用在其他语句,如if语句里
参数检查
上面的my_abs是没有参数检查的,可以用内置函数isinstance()来检查。这样在调用my_abs('a')的时候就会抛出一个错误
def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x>0: return x else: return -x
多个返回值
def mul_ret(): return 22,33a, b = mul_ret()print(a, b) # 22 33
def mul_ret(): return 22,33a = mul_ret()print(a) # (22, 33)
对比这两个执行结果可以发现,其实Python并不是返回了多个值,用一个值a也同样能接受到,因为Python是返回的一个tuple,在语法上,返回一个tuple可以省略括号,按位置赋给对应值
默认参数
def default_para(x, y = 3) pass
简化函数调用,当y=3时,就只要传入参数x。
还有就是在之后增加了函数的参数的时候,早期版本的函数调用不就不能用了,因为函数参数个数不匹配了,这个时候就可以用默认参数,这样就不用去修改以前的函数调用
注意有多个默认参数的情况,比如,fun(a, b, c = 1, d = 2)。当d默认,c不默认的时候,可以这样调用,比如fun(a, b, 4 ), 这个好理解
当d默认,c不默认的时候,要怎么调用?指定特定参数进行赋值,比如,fun(a,b,d = 9)
特别注意一个问题,当有一个这样的函数时候,正常使用感觉也还好,结果也很合理,但是如果多次用默认参数调用该函数就会出问题。
原因是,Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
所以,定义默认参数要牢记一点:默认参数必须指向不变对象!
改成这样,就调用多少次都没有问题了
可变参数
在不使用可变参数的时候,平方和函数要写成这样。通过传入一个list或者一个tuple来计算
用可变参数的时候,写成这样,在调用的时候,不用再套一个list或者tuple。在函数调用的时候会自动组装成一个tuple
如果数据已经是存放到一个list或者tuple里面了,可以这样来调用
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数
总结
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
递归函数
见到一个很有趣题目,就是汉诺塔的问题。这个问题用递归来做真是简单优雅,特此一提。传说某菊苣要手下搬运汉诺塔的盘子,一个64个盘子,三根柱子。问他要搬多少年?
答案是2^64 - 1,什么概念?不算不知道,一算吓一跳,如果他走的每一步都是对的的话,一秒走一步,他要走5849亿万年,这个数还是忽略了亿后面的数字的。。。。
这位菊苣太凶残了,卧槽。。。。。有点像那个国王下象棋的故事
高级特性
切片
l = ['kobe', 'tracy', 'tim', 'lakers', 'spurs'] print(l[1:3]) # ['tracy', 'tim']表示取从索引1开始取,到索引3为止,但不包括索引3 print(l[:3]) # ['kobe', 'tracy', 'tim'] 如果索引从0开始,那么0是可以省略不写的 print(l[-2:]) # ['lakers', 'spurs']倒数切片,从索引为-2开始取,也就是倒数第二个元素开始取print(l[-2:-1]) # ['lakers'] 从倒数第二个开始取,到倒数第一个为止,但不包括倒数第一个,恩,就这样理解l = list(range(100)) # 生成一个0到99的list print(l[:10:2]) # [0, 2, 4, 6, 8] 有点像matlab的for循环?从0开始,到10结束(不包括10),以2为步进长度print(l[:10]) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 如果不写步进长度,默认为1 print(l[::10]) # [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] 从头到为,以10为步进长度 # tuple也是一种list,跟list唯一区别在于元素不可变,因此也是可以用这种切片方式获取元素的t = ('a', 'b', 'c', 'd', 'e') print(t[0:3]) # ('a', 'b', 'c') # 字符串 也可以看做是一个list,每个字符就是一个元素,因此也可以用切片操作 print('abcdefghijk'[2:7]) # cdefg
迭代
python用for ... in ...来迭代,对比于其他语言比如Java,这种迭代可以实用很多场景,比如list,tuple,以及dict和字符串
d = { 'kobe': 24, 'tracy': 1, 'kg': 21} # 迭代key for key in d: print(key) ''' kg kobe tracy 因为dict不是顺序存储,所以每次打印结果顺序可能不一样 ''' # 迭代value for value in d.values(): print(value) # 迭代key和value for k, v in d.items(): print(k, v) # 迭代字符串 s = 'ABCDEFG' for ch in s: print(ch) # 迭代每一个字符打印输出
# 迭代list的索引和对应元素
l = ['a', 'b', 'c', 'd'] for k, v in enumerate(l): print(k, v) ''' 0 a 1 b 2 c 3 d '''
只要一个对象是可迭代的就可以用for,那要如何判断时候可迭代?用collections模块的Iterable类型判断
from collections import Iterable a = isinstance('abc', Iterable) print(a) # True 字符串是可以迭代的 a = isinstance([1, 2, 3], Iterable) print(a) # True a = isinstance(123, Iterable) print(a) # False 整数是不可迭代的
列表生成式
多写几种熟悉下这种写法,主要还是让代码更简洁点
L = []for x in range(1, 11): L.append(x*x) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]# 上述代码同样的可以这样写,结果是一样的L = [x*x for x in range(1, 11)]# 里面还可以加条件判断L = [x*x for x in range(1, 11) if x % 2 == 0] # [4, 16, 36, 64, 100] # 套用两层循环L = [m + n for m in 'abc' for n in 'ABC'] # ['aA', 'aB', 'aC', 'bA', 'bB', 'bC', 'cA', 'cB', 'cC']# for循环同时使用多个变量d = { 'kobe':'Lakers', 'tim duncan':'spurs', 'vince carter':'raptors'}L = [k + ' in ' + v for k, v in d.items()] # ['kobe in Lakers', 'tim duncan in spurs', 'vince carter in raptors'] L = ['Hello', 'World', 18, 'Apple', None]L = [s.lower() for s in L if isinstance(s, str)]print(L)
生成器
g = (x*x for x in range(1, 11))next(g)
跟之前的列表生成式相比,区别就是[] 换成了 (),g就是一个生成器,生成器只保存算法,而不会像列表生成器一样生成一个完整的列表
优点在于如果要生成一个大量数据的(比如100万)的列表,而经常用到的是前面几个,那么后面的就会一直占用空间
生成器只保存算法,每次通过next()来迭代生成下一个元素。不过使用next还是太傻了,生成器是可以迭代的,所以可以用for
生成器的创建方式有两种:
1.就是上面写的那种,列表生成式的[]换成()
2.含有yield的generator function
g = (x*x for x in range(1, 11))for x in g: print(x) # 这样就能打印出所有元素
yield
斐波拉契数列用函数写成这样
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'fib(6)'''112358'''
把上面的变成generator,只要把print(b)改为 yield b就可以了。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done'
generator和普通函数执行流程的区别:在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行,比如:
>>> f = fib(6)
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
>>> next(f)
8
>>> next(f)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: done
作业:写一个generator做杨辉三角
n = 0for t in triangles(): print(t) n = n + 1 if n == 10: break# 期待输出:# [1]# [1, 1]# [1, 2, 1]# [1, 3, 3, 1]# [1, 4, 6, 4, 1]# [1, 5, 10, 10, 5, 1]# [1, 6, 15, 20, 15, 6, 1]# [1, 7, 21, 35, 35, 21, 7, 1]# [1, 8, 28, 56, 70, 56, 28, 8, 1]# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
记录一下我写的,没有网上那么简洁,但是也还是好理解。思路是第一行和第二行单独考虑,第三行及以后头尾先设置好,中间的再做计算得到即可
def triangles(): pre = cur = [] n = 1 while True: if n == 1: cur = [1] elif n == 2: cur = [1, 1] else: cur = [1 for x in range(n)] for x in range(1, n-1): cur[x] = pre[x] + pre[x-1] pre = cur n = n + 1 yield cur
迭代器
可以作用于for循环的数据类型有两类:
1.集合数据类型,比如list,tuple,dict,set,str
2.generator
这些可以直接作用于for循环的对象称为可迭代对象:Iterable
可以使用isinstance()判断一个对象是否是可迭代对象,比如
from collections import Iterable # 需要导包a = isinstance([], Iterable) # Truea = isinstance((), Iterable) # Truea = isinstance('abc', Iterable) # Truea = isinstance(123, Iterable) # Falsesa = isinstance((x*2 for x in range(10)), Iterable) # Trueprint(a)
其中,generator不仅可以用于for循环,还可以被next()不断调用返回下一个值,知道最后抛出StopIteration错误表示无法继续返回下一个值
可以被next()调用并不断返回下一个值的对象称为迭代器:Iterator
同样的,可以用isinstance()方法判断是否是迭代器对象
from collections import Iterator # 需要导包a = isinstance([], Iterator) # Falsea = isinstance((), Iterator) # Falsea = isinstance('abc', Iterator) # Falsea = isinstance(123, Iterator) # Falsesa = isinstance((x*2 for x in range(10)), Iterator) # Trueprint(a)
可见,list,tuple,str这些虽然可迭代(iterable)但不是迭代器(iterator)
为什么?
因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的
可以使用iter()将list,tuple,str这些iterable变成iterator
from collections import Iterator # 需要导包a = isinstance(iter([]), Iterator) # Truea = isinstance(iter(()), Iterator) # Truea = isinstance(iter('abc'), Iterator) # Trueprint(a)
python的for循环的本质是通过调用next()来实现的
for x in [1, 2, 3, 4]: pass
完全等价于
it = iter([1, 2, 3, 4])while True: try: x = next(it) except StopIteration: break
高阶函数
Higher-order function,一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。这个概念好像跟Lua中的高阶函数是一样的?
Google的MapReduce好像很厉害,然而我并不知道是什么东西。python内建了map()和reduce()函数。
map()
map()函数接受两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
为了理解上面这个话,复习下,
什么是Iterable?可直接用于for循环的对象,称作可迭代对象。有哪些呢?集合数据类型(list,tuple,str等)和generator
什么是Iterator?generator不仅可以用于for循环,还可以被next()不断调用返回下一个值,这样的对象称作Iterator
所以,map的第二个参数是放哪些东西并返回什么东西就知道了。
def f(x): return x*xr = map(f, [1, 2, 3, 4, 5])print(list(r)) # [1, 4, 9, 16, 25]
上述代码中,map将传入的函数f(x)=x2依次作用到序列[1, 2, 3, 4, 5]的每个元素。返回的r是一个Iterator。等价于下面的代码。只是用map显得更简洁点。
l = []for x in [1, 2, 3, 4, 5]: l.append(f(x))
reduce()
reduce()把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比如,求一个list的元素的和
def add(x, y): return x+yr = reduce(add, [1, 2, 3, 4, 5])print(r)
也可以写成lambda函数,这样就可以省去add函数了。感觉如果是一样简单的操作,用这个还是比较方便的。因为直接就得到了xy两个参数进行操作。
r = reduce(lambda x, y:x+y , [1, 2, 3, 4, 5])
reduce中的那个函数必须接受两个参数,那如果传入的序列只有一个参数要怎么办?
其实reduce的格式是这样的 reduce( func, seq[, init] ) ,里面的init是可选的,如果写了init,那么init就和序列中的第一个元素作为func的参数传入,但是只参加一次,以后的迭代不会再用到init了。
作业
1.利用map()
函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT']
,输出:['Adam', 'Lisa', 'Bart']
:
# 测试:
L1 = ['adam', 'LISA', 'barT']L2 = list(map(normalize, L1))print(L2)
def normalize(name): c = '' # 因为字符串不可变,构建一个新的字符串返回 for index, ch in enumerate(name): # 用enumerate可以得到元素和对应的索引 if index == 0: c = c + ch.upper() # 第一个元素大写 else: c = c + ch.lower() # 其他的小写 return c
查了下,有个叫capitalize()的函数专门干这个的。一句就够。
def normalize(name): return name.capitalize()
filter
python内建filter()函数用于过滤序列。和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。返回True就留下,False丢弃。最终返回的是一个Iterator。例如
def is_odd(x): # 只要奇数不要偶数 return x % 2 == 1l = list(filter(is_odd, range(1, 10))) # [1, 3, 5, 7, 9]
作业
用filter()过滤回数,回数就是从左到右跟从右到左年一样的数,
def is_palindrome(n): return str(n) == str(n)[::-1] # 这里[::-1]表示的是字符串从头到尾,步进长度-1,也就是逆序output = filter(is_palindrome, range(1, 1000))print(list(output))
排序 sorted
l = [99, -2, 0, 2, 18, 10]print(sorted(l)) # [-2, 0, 2, 10, 18, 99]# sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序# 将l的每一个元素作用到函数abs上,然后再进行排序print(sorted(l, key = abs)) # [0, -2, 2, 10, 18, 99]l = ['Kobe', 'allen', 'Tracy', 'tim', 'kg']print(sorted(l)) # ['Kobe', 'Tracy', 'allen', 'kg', 'tim'] 默认的字母排序是'Z' > 'z'的, 也就是大写会排在前面print(sorted(l, key = str.lower)) # ['allen', 'kg', 'Kobe', 'tim', 'Tracy'] 这样就实现的不分大小写来进行排序print(sorted(l, key = str.lower, reverse = True)) # ['Tracy', 'tim', 'Kobe', 'kg', 'allen'] 逆序输出,注意True是大写的T
作业
假设我们用一组tuple表示学生名字和成绩:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
请用sorted()
对上述列表分别按名字排序,然后再按成绩从高到低排序::
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]def by_name(t): return str.lower(t[0])def by_score(t): return t[1]L2 = sorted(L, key=by_name)print(L2)L2 = sorted(L, key=by_score, reverse=True)print(L2)
返回函数
高阶函数除了可以接受函数作为参数外还可以把函数作为返回值返回
def cala_sum(*numbers): # 求和函数写成这样 a = 0 for i in numbers: a = a + i return aprint(cala_sum(1, 2, 3, 4, 5)) # 15# 如果不要求立刻执行def lazy_sum(*numbers): def cala_sum(): # 注意这里不能*numbers作为形参了,我理解的是这样就覆盖了lazy_sum的形参,因为同名 # 但是却不会因为同名而把参数传下来,除非实际调用cala_sum(args),函数体里面才能用到传进来的args a = 0 for i in numbers: a = a + i return a return cala_sumf = lazy_sum(1, 2, 3, 4, 5)print(f())
每次执行lazy_sum都会返回一个函数,但是每次返回的函数是不一样的,也就是不是指向一个地址,大概可以这样理解。两个函数的调用结果互相不影响
f1 = lazy_sum(1, 2, 3, 4, 5)f2 = lazy_sum(1, 2, 3, 4, 5)print(f1 == f2) # false
再看一个例子
def f(): l = [] for i in range(1, 4): def r(): return i*i l.append(r) return lf1, f2, f3 = f()print(f1()) # 9print(f2()) # 9print(f3()) # 9
这里不是输出1,4,9 为什么?因为for循环每次循环都创建一个函数,然后将这个函数加到一个list里面,而这个函数并没有被执行,我这样理解,因为这个函数并没有被调用,而只是定义好了,也就是说里面的i*i并不会执行
等到显示的调用该函数的时候,for循环已经结束,并且i=3。
这种搞法叫做Closure,闭包,Lua里面也有
匿名函数
比如之前的map提到的lambda,如下代码所示,lambda中表示的就是f(x) = x2, 这就是一个匿名函数。限制是只能有一个表达式,不用写return,返回值就是表达式的结果,匿名函数也可以当做一个函数的返回值来返回
l = list(map(lambda x: x*x , [1, 2, 3, 4, 5]))print(l) # [1, 4, 9, 16, 25]
装饰器
现在要增强一个函数的功能,比如,在函数调用前执行begin call,在调用后执行end call,可以这样干
def hello(): print('hello world')def deco(func): print('begin call') func() print('end call') return funchello = deco(hello)
这样虽然会输出想要的,hello的定义没变。
继续用语法糖@来实现。在定义hello的时候加上@deco,其实就表示hello = deco(hello),会自动执行deco方法,并且hello没有改变,再单独调用 hello()的时候只会输出 hello world,不会有begin call这些
def deco(func): print('begin call') func() print('end call') return func@decodef hello(): print('hello world')
使用内嵌包函数,使得之后每次调用也会出现begin call 和 end call。这个时候hello就指向deco里面的wrapper函数了,可以打印hello.__name__来看函数的名字,结果是wrapper而不是hello,所以之后每次调用hello都会有装饰结果
def deco(func): def wrapper(): print('begin call') func() print('end call') # 这里需要返回原函数func的返回值 return wrapper@decodef hello(): print('hello world')
装饰带参数的函数
def deco(func): def wrapper(a, b): print('begin call') res = func(a, b) print('end call') return res return wrapper@decodef add(a, b): return a + br = add(1, 2) # 3
当装饰不确定参数数量时
def deco(func): def wrapper(*args, **kw): print('begin call') print('call', func.__name__) res = func(*args, **kw) print('end call') return res return wrapper@decodef add(a, b): return a + b@decodef addMore(a, b, c): return a + b + cr = add(1, 2) # 3print(r)r = addMore(1, 2, 3) # 6print(r)
偏函数
*args
和**kw
这3个参数 import functoolsdef hello(s = 'world', st = '!'): print('hello', s, st)h = functools.partial(hello, s = 'kobe', st = ', I see you!')h() # hello kobe , I see you!kw = { 's':'tracy'}f = functools.partial(hello, **kw)f() # hello tracy!
模块
有点类似lua的模块,不同的是,Python的每一个包目录下面都会有一个__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py
可以是空文件,也可以有Python代码,因为__init__.py
本身就是一个模块,而它的模块名就是mycompany
。
模块头几行的标准注释,当然不写也没有关系
第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
#!/usr/bin/env python3# -*- coding: utf-8 -*-' a test module '__author__ = 'Michael Liao'
__**和_**的命名默认是private,其实是可以引用的,只是习惯和一种约定,因为Python没法实现private私有变量,Lua也类似。
面向对象
创建类和对象
class Student(object): # 父类写在括号里面 def __init__(self, name, score): # 构造函数,__init__方法的第一个参数永远是self,表示创建的实例本身。 self.name = name self.score = score def print_score(self): # 成员函数的第一个参数也是self print(self.score)bart = Student('kobe', 24) # 不用传self,这个有点像lua用冒号来定义函数的意思了bart.print_score() # 调用不用传self
一个类的两个不同的对象可以有不同的属性,比如创建了对象后,自己定义一个age属性,bart.age = 33 。那么这个属性只会在bart这个对象里。
访问限制
用双下划线开头的成员变量,比如__name是不能直接访问的。双下划线开头就意味着,这个是private。如果用bart.__name = 'aaaa'来赋值,会成功,但是这个__name与类里面定义的__name不是同一个变量了!!!
但是,注意__name__这种变量是特殊变量,并不是私有变量,所以是可以访问的。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量
所以!一切靠自觉!养成好的习惯!
继承
class Animal(object): def run(self): print('Animal is running') class Dog(Animal): #括号里写上父类,表示继承自Animal pass # 可以复写run方法d = Dog() # 继承了父类的run方法d.run() # Animal is running
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了(居然还有这种事!)
获取对象信息
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431866385235335917b66049448ab14a499afd5b24db000
面相对象高级编程
使用 __slots__
Python是可以动态设置对象的属性的。比如类Student里面没有name这个属性,那么s1.name = "kobe"后,s1对象是有name这个属性的,但是s2就没有了。这个毫无疑问。但是Python是可以直接对类动态设置属性的,比如Student.name = "kobe",那么他的对象就都有这个属性了。
使用 __slots__ 可以限制这种动态设置。比如
class Student(object): __slots__ = ("name", "age") s1 = Student()s1.name = "Kobe"s1.age = 24s1.number = 8
Student类中用 __slots__ 限制了只能绑定name和age这两个属性,所以在s1.number = 8 企图绑定number的时候会报错。测试了下,这个只是针对动态绑定这种情况,事先在类里面定义其他属性是没有关系的。
而且这种限定不会延伸到子类。比如
class GraduateStudent(Student): passg = GraduateStudent()g.number = 8
子类可以绑定number这个属性
使用 @property
简单来说@property可以给属性加上检查。就是当使用 s.score = 59 这种方式给属性赋值时,可以检查赋值的值是否符合规范。
class Student(object): @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError('score must be int') if value < 0 or value > 100: raise ValueError('score must be 0~100') self._score = values = Student()s.score = 59print(s.score) # 59
可以这样理解,@property把一个getter()方法变成了属性,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值。
只读属性。只设置@property不设置对应的@setter,该属性就是可读的。比如
class Student(object): @property def age(self): return 89s = Student()print(s.age) # 89s.age = 30 # AttributeError: can't set attribute
多重继承
没想到,这货还可以多重继承。方式就是,在定义类的时候,括号里写上所有父类。就实现了多重继承。比如
class Animal(object): def eat(self): print("i can eat")class RunnableMixin(object): def run(self): print("i can run")class Dog(Animal, RunnableMixin): passd = Dog()d.eat()d.run() # 同时继承到了eat()和run()方法
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为MixIn。把Runnable写成RunnableMixin是为了更好的看出继承关系。
定制类
__str__
这玩意有点像Java的toString()方法。Java中print一个对象名的时候好像是会去调用toString()方法的,会返回一个内存地址啥的。__str__有点这个意思。
class Person(object): passprint(Person()) # <__main__.Person object at 0x02F67810>
class Person(object): def __str__(self): return ("A Person Object")print(Person()) # A Person Object
__iter__
如果一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环
上面这段描述有点Java的迭代器的感觉,实现一个接口。怎么怎么样的。。。
斐波那契数列
class Fib(object): def __init__(self): self.a, self.b = 0, 1 def __iter__(self): return self def __next__(self): self.a, self.b = self.b, self.a + self.b if self.a > 50: raise StopIteration() return self.a for n in Fib(): print(n)
__getitem__
可以让上面的Fib像调用list那样直接取第几个数,比如Fib()[5],其实也就是像前面的__str__一样,python在出现方法后面跟[],Fib()[5]这种类型的时候会去调用__getitem__这个函数。你实现了这个方法就能用这种形式,没实现这样调用就报错。
def __getitem__(self, n): a, b = 1, 1 for n in range(n): a, b = b, a + b return a
__getattr__
这个有点想Lua的元表里面的__index 元方法,当访问不存在的属性时,就会去调用这个方法。比如
class Person(object): passprint(Person().name) # AttributeError: 'Person' object has no attribute 'name'
class Person(object): def __getattr__(self, attr): return "NONE"print(Person().name) # NOE
__call__
一般调用一个方法是 instance.method() 这种形式的,但是__call__ 可以实现 instance()这种形式调用
class Person(object): def __init__(self, name): self.name = name def __call__(self): print("My name is %s" % self.name)p = Person("kobe")p() # My name is kobe
去判断一个对象是否能被调用,能被调用的就是一个callable对象,比如callable(Person())就返回True
枚举
偷个懒。。。
元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
type
type可以查看对象的类型,也可以动态创建类
Car = type("Car", (object,), dict(fn = lambda self:"drive me"))c = Car()print(c.fn()) # drive me
要创建一个class对象,type()
函数依次传入3个参数:
- class的名称;试了下,上例中type里面的Car换成其他也行的,这里不知道有没有其他规定。
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上。
错误、调试、测试
python也是采用像Java那种try...catch方式。格式如下,执行规则与Java一样
try: print("try") r = 10/0 print("result:", r)except ZeroDivisionError as e: print("except:", e)finally: print("finally")print("end")
不过不同的是,可以可以在except后面加个 else表示当没有错误的时候执行
同样的,所有错误继承自BaseException,当有多个except的时候,捕获错误的时候,会把所有的子类错误也捕获到。
也可以自定义错误。
记录错误
Python内置的logging
模块可以非常容易地记录错误信息,import logging后,代码出错了,会打印出错误信息,并且也会继续运行下去.
抛出错误
使用raise关键字。这里自定义一个错误
class MyError(ValueError): passdef fun(): raise MyError()fun()
def foo(s): n = int(s) if n==0: raise ValueError('invalid value: %s' % s) return 10 / ndef bar(): try: foo('0') except ValueError as e: print('ValueError!') raisebar()
捕获到错误后继续 raise 抛出,因为当前函数不知道如何处理,所以继续向上级调用者抛出。
assert
断言,格式如下
def fun(n): assert n != 0, "n is zero" print('fun')fun(0)
AssertionError: n is zero
可以用 -O 参数关掉assert,比如, python -O test.py ,那么assert语句就相当于pass了
logging
和assert
比,logging
不会抛出错误,而且可以输出到文件
pdb
单步调试,好的IDE PyCharm
单元测试
一个测试类,需要继承自unittest.TestCase。类里面的以test
开头的方法就是测试方法,不以test
开头的方法不被认为是测试方法,测试的时候不会被执行。
unittest.TestCase提供了一些内置的判断,比如assertEqual()
setUp()、tearDown()。没测,估计是在测试类运行前后结束的时候分别调用的。
IO
通常的格式
try: f = open('E:/t.txt', 'r') print(f.read())finally: if f: f.close()
和Java一样,打开了记得关闭,为了防止打开文件出错,后面的close执行不到,所以用try catch这种方式,r 表示读文件。
Python还提供一种更简洁的方式,效果和上面的一样。
with open("E:/t.txt", "r") as f: print(f.read())
read()方法会一次性读取全部内容,如果内容有好几个G,内存就爆了,所以,用read(size)多次反复调用是更合理的方法
readline()
可以每次读取一行内容,调用readlines()
一次读取所有内容并按行返回list
读取二进制文件(图片,视屏等), rb
with open("E:/t.PNG", "rb") as f: print(f.read())
读取GBK编码的文件
with open("E:/t.txt", "r", encoding='gbk') as f: print(f.read())
忽略错误(比如编码错误)
with open("E:/t.txt", "r", encoding='gbk', errors="ignore") as f: print(f.read())
写文件。参数有 w , 二进制文件写 wb。操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with
语句来得保险
StringIO
从内存中读写str,区别于从文件读写
from io import StringIO# 读f = StringIO("Hello World\nLearn Python") # 初始化一个StringIOwhile True: s = f.readline() if s == "": break print(s)# 写f = StringIO()f.write("Hello Python") # write方法会返回写入的字符数,比如这里是12print(f.getvalue())
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
操作文件和目录
import osos.name # ntos.environ # 打印出环境变量os.environ.get("PATH") # 通过get加上key来获取
操作文件和目录的函数一部分放在os
模块中,一部分放在os.path
模块中
os.path.abspath('.') # 获取绝对路径,有点像pwdos.mkdir("E:/testdir") # 创建目录os.rmdir("E:/testdir") # 删除目录
把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()
函数,这样可以正确处理不同操作系统的路径分隔符
同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()
函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名
os.path.splitext()
可以直接让你得到文件扩展名
os.path.splitext("E:/t.txt") # ('E:/t', '.txt') 得到一个tuple
重命名 os.rename
删除文件 os.remove
复制文件没有提供,可以用IO来实现,shutil
模块也提供了copyfile()
的函数
序列化
Python中的序列化叫做pickling,反序列化叫做unpickling,Python提供pickle模块来实现序列化
序列化:
import pickled = dict(name = "kobe", num = 24)with open("D:/t.txt", "wb") as f: pickle.dump(d, f) # 序列化后保存到文件中
文件中的内容是这样的一堆乱七八糟的东西:
(dp0
S'num'p1I24sS'name'p2S'kobe'p3s.反序列化
with open("D:/t.txt", "rb") as f: p = pickle.load(f) # load反序列化出对象print(p) # {'num': 24, 'name': 'kobe
反序列化出来的对象跟原来的那个不是同一个对象,只是内容相同而已
pickle可能会在不同版本的Python中不兼容,所以,最好只用这个保存不重要的数据。反序列化不了也没关系。。。(所以,就是自己玩玩就好
JSON
为了在不同的编程语言之间传递对象,必须把对象序列化成标准格式。比如XML,JSON,最好是JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取。也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
总之,JSON大法好,嗯。
import json# dumps返回一个标准的json字符串print(json.dumps(d)) # {"num": 24, "name": "kobe"}# dump把json字符串写到文件中with open("D:/t.txt", "w") as f: json.dump(d, f)# 同样有个load方法把json数据反序列化with open("D:/t.txt", "r") as f: j = json.load(f) print(j)
类的序列化
就像Java中序列化一个对象需要他的类实现了某些接口。Python中也不能直接序列化,除了那些本来就支持的数据类型,自定义类的话,要自己写个转换方法。比如
class Player(object): def __init__(self, name, team, age): self.name = name self.team = team self.age = agedef player2dict(p): return { "name":p.name, "age":p.age, "team":p.team }p = Player("kobe", "Lakers", 35)print(json.dumps(p, default=player2dict)) # {"age": 35, "name": "kobe", "team": "Lakers"}
或者偷懒,写成这样:
class
的实例都有一个__dict__
属性,它就是一个dict
,用来存储实例变量。也有少数例外,比如定义了__slots__
的class。 class Player(object): def __init__(self, name, team, age): self.name = name self.team = team self.age = agedef player2dict(p): return { "name":p.name, "age":p.age, "team":p.team }p = Player("kobe", "Lakers", 35)str = json.dumps(p, default=lambda o:o.__dict__) # {"age": 35, "name": "kobe", "team": "Lakers"}def dict2player(d): return Player(d["name"], d["age"], d["team"])s = json.loads(str, object_hook=dict2player)print(s) # <__main__.Player object at 0x03B901F0>
进程和线程
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间
Windows下启动子进程
from multiprocessing import Processimport osdef fun(name): print("Run child proc %s (%s)..." % (name, os.getpid()))if __name__=="__main__": print("parent proc %s." % os.getpid()) p = Process(target=fun, args=("test", )) print("child proc will start") p.start() p.join() print("child proc end.")
parent proc 14780.
child proc will startRun child proc test (15192)...child proc end.
注意:这玩意本来用vs code直接F5,是不会执行fun函数的,但是在终端 py test.py 是可以的!
Pool
如果要启动大量的子进程,可以用进程池的方式批量创建子进程
from multiprocessing import Poolimport os, time, randomdef task(name): print("Run task %s (%s)" % (name, os.getpid())) start = time.time() time.sleep(random.random() * 3) end = time.time() print("task %s run %0.2f seconds" % (name, end - start))if __name__ == "__main__": print("parent proc %s " % os.getpid()) p = Pool(4) for i in range(5): p.apply_async(task, args = (i,)) print("waiting for all subprocess done...") p.close() p.join() print("all subprocess done")
parent proc 10660waiting for all subprocess done...Run task 0 (13592)Run task 1 (5020)Run task 2 (5180)Run task 3 (7012)task 2 run 1.03 secondsRun task 4 (5180)task 1 run 2.06 secondstask 3 run 2.30 secondstask 0 run 2.78 secondstask 4 run 1.98 secondsall subprocess done
对Pool
对象调用join()
方法会等待所有子进程执行完毕,调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process
了。
task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。
进程的通信
Python的multiprocessing
模块包装了底层的机制,提供了Queue
、Pipes
等多种方式来交换数据
from multiprocessing import Process, Queueimport os, time, randomdef write(q): print("write process (%s)" % os.getpid()) for i in ["kobe", "tracy", "nash"]: print("put %s to queue" % i) q.put(i) time.sleep(random.random())def read(q): print("read process (%s)" % os.getpid()) while True: v = q.get(True) print("get %s from queue" % v)if __name__ == "__main__": q = Queue() wp = Process(target=write, args=(q,)) rp = Process(target=read, args=(q,)) wp.start() rp.start() wp.join() rp.terminate()
rp是死循环,所以不能用join()来等待结束,只能直接杀死他
write process (6576)put kobe to queueread process (6032)get kobe from queueput tracy to queueget tracy from queueput nash to queueget nash from queue
多线程
Python的标准库提供了两个模块:_thread
和threading
,_thread
是低级模块,threading
是高级模块,对_thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块
启动一个线程就是把一个函数传入并创建Thread
实例,然后调用start()
开始执行
import time, threadingdef loop(): print('thread %s is running' % threading.current_thread().name) for i in range(5): print('thread %s >>> %s ' % (threading.current_thread().name, i)) time.sleep(1) print('thread %s ended' % threading.current_thread().name)print('thread %s is running...' % threading.current_thread().name)t = threading.Thread(target=loop, name='loopThread') # 创建Thread对象,赋值要运行的函数,以及线程名字t.start() # 开始t.join() # 等待结束print('thread %s ended' % threading.current_thread().name)
thread MainThread is running...thread loopThread is runningthread loopThread >>> 0 thread loopThread >>> 1 thread loopThread >>> 2 thread loopThread >>> 3 thread loopThread >>> 4 thread loopThread endedthread MainThread ended
Lock
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改
嗯。只要有线程,就会要讨论同步的问题
基本使用方式就这样
lock = threading.Lock() # 获取Lock对象n = 0def count(): for i in range(100): lock.acquire() # 获取锁,放到需要数据同步的地方 try: global n # 关键代码写在try catch中 n = n + 1 print("thread %s runing count >>> n = %s \n" % (threading.current_thread().name, n)) finally: lock.release() # 记得在finally里面释放锁
Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核
Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦
分布式进程
在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。
Python的multiprocessing
模块不但支持多进程,其中managers
子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers
模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。
这玩意在Linux下运行没有问题。windows下面不行。
网络编程
TCP/IP
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。