[TOC]
变量和结构基础
-
#注释;缩进视为代码块;句末无分号;
-
浮点数,当需要用科学计数时,1.2*10^6表示为1.2e6;0.00012表示为1.2e-4;
-
字符串用’或者”,多行使用”’语法
print('''one two three''')
-
布尔值,True或False,注意大小写
-
空值,None
-
变量赋值
a=123 b='test'
同一个变量可以被赋值不同类型的值。这儿和静态语言不同。
-
常量,Python中一般使用大写表示常量,和变量赋值一样,只是约定的常量,不是真不能改变的。
-
算术操作符
1/2 #整数除法默认返回地板除,为0 1.0/2.0 #浮点除法执行真正的除法,为0.5 1//2 #地板除,为0.这是为了兼容新版把传统除法默认替换为真正的除法
10%3 #取余,为1
3**2 #幂运算,为9
其余的加减乘除和位运算都与C语言相通
-
编码
计算机中8bit表示1byte,所以1byte能表示256个值,在ASCII编码中,1byte表示一个字符。
在中文中1byte表示一个字符是不够的,所以使用2byte,有65536个值。使用了GB2312编码。
为了兼容大多语言,有了Unicode编码,通常用2byte表示。大多语言和操作系统支持Unicode。
但是这样英文在前面补零就浪费IO,所以出现了可变长编码utf-8。可以使用1~6byte表示,英文为1byte,汉字通常3byte。
在计算机内存中,统一使用Unicode编码,当需要保存或者传输时转换成utf-8
-
序列包括字符串、列表和元组。
-
序列操作符
seq[ind] #获取下标为ind的元素 seq[ind1:ind2] #获取两下标之间的元素 seq*expr #重复expr次 seq1+seq2 #连接两序列 obj in seq #判断是否包含,返回True或False obj not in seq #判断obj是否不包含
-
Python的字符串类型str,在内存中用Unicode表示。如果需要在网络传输或者保存到本地,就需要把str转为以字节为单位的bytes
x=b'ABC'
-
格式化
'hello,%s' % 'world' 'hello,%s and %s' % ('a', 'b') 'rate %d %%' 7 #rate 7 %
-
列表是一种可变的有序的集合。
l=[] l=[1,2,3,4,5] l1=[1,2,[3,4],5] l.pop() l[-1] l[:2] l1[2][1]
-
元组是一种不可变的有序集合
t=() t1=(1,) #为了避免被理解成运算的() t2=(1,2,3,[4,5]) #这是列表中的值是可变的
-
条件判断
a=100 if a<100: print('less') elif a=100: print('proper') else: print('more')
-
for循环
l=[1,2,3,4,5,6,7,8,9] sum=0 for i in l: sum = sum + i print(sum)
-
while循环
sum=0 n=1 while n<10: sum = sum + n n = n + 1 print(sum)
-
三元操作符 类似c?x:
y在Python中如下
smaller = x if x>y else y
这儿等效于smaller = x>y ? x : y
-
映射类型:字典
d={'a':1,'b':2,'c':3} d['a'] 'a' in d #判断key是否存在 d.pop('c')
-
Python里的映射类型只有dict。一个字典对象是可变的,容器类型,无序,存储任意多个。映射类型通常被叫做hash表。
序列类型用有序的数字键做索引将数据以数组的形式存储,查找和插入随着元素增加而增加。
字典类型使用键名的哈希函数值决定值的地址,查找和插入的时间复杂度恒定。
更多序列容器和关联式容器参见STL容器
-
集合set
Python的set没有特定的语法格式,一般使用set()和frozenset()(不可变集合)创建
s=set([1,2,3,4,5,6,7]) #{1,2,3,4,5,6,7} s<t #s是t的严格子集 s>=t #s是t的超集 s-t #差集,只属于s不属于t s^t #对称差分,只属于s或只属于t,而不能是交集 集合操作符 in # not in != < #是严格子集 <= #是子集 > #是严格超集 > = #是超集 > & > | - #差集 ^ #对称差分函数
-
列表生成式 对于要生成一个列表,可以使用for in语法,然后在里面append元素
不过这种方法太low太冗长了,Python里提供了一种更优雅的写法(其实TM就是数不清的语法糖),这儿最前面的就相当于for循环内的表达式
[x * x for x in range(1, 11)] #生成1~11中所有数的平方的列表 [x * x for x in range(1, 11) if x % 2 == 0] #有条件的生成 [m + n for m in 'ABC' for n in 'XYZ'] #双层生成 d = {'x': 'A', 'y': 'B', 'z': 'C' } [k + '=' + v for k, v in d.iteritems()] #对字典遍历 ['y=B', 'x=A', 'z=C']
函数
-
定义函数
def nop(): pass #定义空函数 def func(x): if x>10: return True else: return False
import math def move(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny #这儿其实返回的是个tuple,省略了括号
-
函数返回0个值时返回的对象为None,1个的时候为object,大于1时为tuple
-
位置参数、默认参数、关键字参数
位置参数必须以准确顺序来传递,且数目必须一致。
默认参数可以缺省,缺省时为默认值。默认参数要必需的参数之后。
如果命名了参数,则可以不按照次序来给出参数。
def net_conn(host, port=80, stype='tcp'): net_conn_suite a、net_conn('phaze', 8000, 'udp') # no def args used b、net_conn('kappa') # both def args used c、net_conn('chino', stype='icmp') # use port def arg d、net_conn(stype='udp', host='solo') # use port def arg e、net_conn('deli', 8080) # use stype def arg f、net_conn(port=81, host='chino') # use stype def arg
-
可变长度的参数
函数调用中可使用*和**符号来指定元组和字典的元素作为非关键字以及关键字参数的方法。定义时也这样表示。
可变长的参数元组必须在位置和默认参数之后。
def tupleVarArgs(arg1, arg2='defaultB', *theRest): ... 'display regular args and non-keyword variable args' ... print 'formal arg 1:', arg1 ... print 'formal arg 2:', arg2 ... for eachXtrArg in theRest: ... print 'another arg:', eachXtrArg ... >>> tupleVarArgs('abc') formal arg 1: abc formal arg 2: defaultB >>> tupleVarArgs(23, 4.56) formal arg 1: 23 formal arg 2: 4.56 >>> tupleVarArgs('abc', 123, 'xyz', 456.789) formal arg 1: abc formal arg 2: 123 another arg: xyz another arg: 456.789
可变长的参数字典要是最后一个参数,并且非关键字参数元组要先于它出现。
def dictVarArgs(arg1, arg2='defaultB', **theRest): ... 'display 2 regular args and keyword variable args' ... print 'formal arg1:', arg1 ... print 'formal arg2:', arg2 ... for eachXtrArg in theRest.keys(): ... print 'Xtra arg %s: %s' % (eachXtrArg, str(theRest[eachXtrArg])) ... >>> dictVarArgs(1220, 740.0, c='grail') formal arg1: 1220 formal arg2: 740.0 Xtra arg c: grail >>> dictVarArgs(arg2='tales', c=123, d='poe', arg1='mystery') formal arg1: mystery formal arg2: tales Xtra arg c: 123 Xtra arg d: poe >>> dictVarArgs('one', d=10, e='zoo', men=('freud', 'gaudi')) formal arg1: one formal arg2: defaultB Xtra arg men: ('freud', 'gaudi') Xtra arg e: zoo Xtra arg d: 10
同时存在关键字和非关键字的可变长参数的情况:
>>> def newfoo(arg1, arg2, *nkw, **kw): ... print 'arg1 is:', arg1 ... print 'arg2 is:', arg2 ... for eachNKW in nkw: ... print 'additional non-keyword arg:', eachNKW ... for eachKW in kw.keys(): ... print "additional keyword arg '%s': %s" % (eachKW, kw[eachKW]) ... >>> newfoo('wolf', 3, 'projects', freud=90, gamble=96) arg1 is: wolf arg2 is: 3 additional non-keyword arg: projects additional keyword arg 'gamble': 96 additional keyword arg 'freud': 90
-
调用带有可变长参数对象函数
先匹配位置和默认参数,匹配完再看非关键词参数,最后看关键词参数
在调用的时候也可以用如下的方式:
>>> newfoo(2, 4, *(6, 8), **{'foo': 10, 'bar': 12}) arg1 is: 2 arg2 is: 4 additional non-keyword arg: 6 additional non-keyword arg: 8 additional keyword arg 'foo': 10 additional keyword arg 'bar': 12 >>> aTuple = (6, 7, 8) >>> aDict = {'z': 9} >>> newfoo(1, 2, 3, x=4, y=5, *aTuple, **aDict) arg1 is: 1 arg2 is: 2 additional non-keyword arg: 3 additional non-keyword arg: 6 additional non-keyword arg: 7 additional non-keyword arg: 8 additional keyword arg 'y': 5 additional keyword arg 'x': 4 additional keyword arg 'z': 9
-
内部/内嵌函数,函数中定义的函数,只能在函数体内调用。
-
函数装饰器(Decorator)
装饰器是一种设计模式,AOP(Aspect Oriented Programming,面向方面编程)
装饰器其实是一个函数,一个用来包装函数的函数。返回一个修改后的函数对象,将其重新赋值原来的标识符,并永久丧失对原始函数对象的访问。
比如我们定义了一个func,不希望对func进行任何修改的增强func的功能,增加对一类func的日志打印。
使用语法糖@来实现装饰器
def log(func): def wrapper(*args, **kw): print 'call %s():' % func.__name__ return func(*args, **kw) return wrapper @log def now(): print "2016" now()
如上面代码所示,调用now的时候,其实now已经发生了改变,now=log(now)
在这儿还能定义多个装饰器,会一层一层包裹起来
当定义装饰器带参数时,情况稍复杂… -
传递函数
所有的对象都是通过引用来传递的,函数也不例外。当对一个变量赋值时,实际上是将相同对象的引用复制给这个变量。如果对象是函数时,这个对象所有的别名都是可调用的。
def foo(): print 'in foo()' bar = foo bar() # in foo()
-
匿名函数与lambda
Python允许使用lambda来创造匿名函数。我们创建了一个匿名函数,但是没有在任何地方保存它,调用它。这样的函数对象在创建时引用计数被设置为True,但是因为没有引用保存下来,计数又回到零,然后被垃圾回收掉。为了保留住这个对象,我们将它保存到一个变量中,以后可以随时调用。
def true(): return True #假设可以这样写,则等效于下面的 lambda :True def add(x, y=2): return x+y #等效于下面 lambda x, y=2 : x+y a =lambda x, y=2 : x+y a(5)
-
偏函数
当调用对于大量同一个函数的情况,大部分参数相同,少量一两个参数不同时,我们会写出很冗余的代码。这时可以使用偏函数来解决,将其中部分参数固化,就只需要改变其他少量参数。
from functools import partial #使用partial函数来创建PFA add1 = partial(add, 1) #等效 add1(x) == add(1, x) baseTwo = partial(int, base=2)
-
变量作用域
全局变量全局可见,在函数内部也可见。
局部变量只在函数内部可见。在函数内,Python先从局部作用域 开始搜索,然后才去全局域寻找,找到为止。
同名全局和局部变量不冲突,只是由于寻找次序不同而看上去函数内部像是覆盖,其实不是。
可以使用golbal关键字在函数内部建立全局域的引用,这样函数内部同名变量就能修改全局变量。
-
闭包
当在一个函数中定义一个函数,当外部函数返回内部函数,调用时内部函数会带着外部函数的作用域。这样内部函数就被认为是闭包。
def line_conf(): b = 15 def line(x): return 2*x+b return line # return a function object b = 5 my_line = line_conf() print(my_line.__closure__) print(my_line.__closure__[0].cell_contents)
从这个例子中可以看到,my_line在调用的时候,引用了line_conf的作用域中的b
相当于使用line带着一个流浪的作用域
看这个例子,能更深理解调用闭包时带着外层函数的作用域到处跑def counter(start_at = 0): count = [ start_at ] def incr(): count[0] += 1 return count[0] return incr count = counter(5) count() 6 count() 7
这儿可以看到每次调用count方法,其引用的count值是内部函数附带的流浪的作用域中的变量,他在这次调用中始终存在。
这样就能创建一种具有广泛含义的函数,有点类似偏函数,但是闭包是关于使用定义在其他作用域的变量,而PFA更像是currying,与函数调用有关。看这个例子,创建了一种直线的类型def line_conf(a, b): def line(x): return ax + b return line line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(line1(5), line2(5))
-
生成器
当需要遍历一个巨大的list的时候,一般做法是先生成列表,再遍历
但是我们不能不考虑内存的使用情况
这时候应当考虑使用生成器,通过yield语法挂起获取其中一个的返回值,当调用回到程序中时,仍能从我们上次离开的地方继续。
g = (x * x for x in range(10))
前面我们看到了列表生成式,这儿[]变成了(),就变成了生成器,前面的xx等效于yield xx
这里如果调用g()会返回一个迭代器对象,其实可以使用迭代器的接口来返回每一个值,g.next(),这样依次迭代
在执行中每次遇到yield其实就中断了,需要使用迭代器的接口来处理,然后再次调用在迭代器中还有个send()方法,每次把值传给yield后的变量,看例子
def counter(start_at=0): count=start_at while True: val=(yield count) if val is not None count=val else count+=1 count=counter(5) count.next() 5 count.next() 6 count.send(9) 9 count.next() 10
模块
-
import.一般按照标准库模块、第三方模块、自定义模块来导入
import module import module1,module2,module3
-
如果模块在全局中导入,他的作用域是全局的。如果在函数中导入,作用域是局部的。
如果模块是第一次被导入,它会被加载并执行
-
from-import导入指定的模块属性。这个时候不需要使用属性/句点属性标识来访问模块的标识符。但是赋值的时候,却只能通过属性/句点属性标识来操作,否则只会赋值到本模块内的变量中
from module import name1,name2,name3
-
as别名
import Tkinter as tk from cgi import FieldStorage as form
-
一个模块只被加载一次,无论它被导入多少次
-
可以使用__import__()来导入模块
sys = __import__('sys')
-
reload()内建函数可以重新导入一个已经导入的模块。首先使用的时候只能全部导入,不能是from-import。然后导入语法,reload(sys)而不是reload(‘sys’)
-
包
有时候模块和模块可能会名称冲突,所以引入了包
文件夹名为包名
里面可以多层包结构,通过.引用
一个文件夹目录必须有一个__init__.py文件,否则不会被当做包。这个文件可以为空,也可以不为空,不为空时代表包的属性和方法
-
相对导入和绝对导入//TODO
-
两个下划线的方法和变量一般是特殊用途的,我们不要命名成这样
一个下划线一般指非公开的,_var,当from module import *时就不会被引入
-
当运行当前脚本时,解释器会赋予当前脚本一个特殊值__name__为__main__
if __name__ == '__main__': test()
-
使用pip命令来安装第三方模块
pip install Pillow
类和对象
-
定义一个类
class Student(object): #类名通常是大写开头,Object表示继承的类 def __init__(self, name, score): #构造函数,self固定不变 self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score)) bart = Student('Bart Simpson', 59) bart.print_score()
-
访问限制
上面的例子中,可以在类外直接赋值,bart.name=’luca’
所以可以通过修改属性名来限制,限制把name前加上__,两个下划线
self.__name = name
这样外面调用bart.__name就会报错,这样就变成了私有变量
而其实在Python解释器中,属性变成了_student.__name,外部还是能通过bart._student__name来访问
-
继承
class MiddleStudent(Student): pass
这样就继承了Student类,并继承了Student中的方法
当存在同名方法时,子类的方法会覆盖父类的方法 -
用type(fn)来获取类型,用isinstance(instance, class)来判断是否是某个对象的实例,用dir(‘abc’)来获取一个对象的所有属性和方法。
-
当定义一个class,创建一个实例后,我们可以给绑定任意方法和属性。
s = Student() s.name = 'luca' def set_score(self, score): self.score = score Student.set_score = set_score #给所有实例绑定方法
如果我们要限制不能动态绑定属性,可以使用__slots__
class Student(object): __slots__ = ('name', 'age')
如果这时候再动态绑定属性,会提示不存在,因为新属性不会被放到__slots__中。但是这个对子类不生效。
-
把属性变成方法调用:使用@property,使用装饰器的方法
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 an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value
把一个getter方法变属性,使用@property
这时@property又创造了一个@score.setter方法,对score进行属性赋值。
这样可以对属性score的赋值进行控制。s = Student() s.score = 60 s.score 60 s.score = 9999 Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!
-
Python支持多重继承
class Bat(Mammal, Flyable): pass
-
使用元类,type,metaclass
-
错误处理
try: print('try...') r = 10 / int('2') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError as e: print('ZeroDivisionError:', e) else: print('no error!') finally: print('finally...') print('END')
在try中执行代码时,遇到错误会不再执行,而是进入except块中,执行完后,如果有finally块,还会进入finally块,然后结束。
不同类型的错误可以进入不同类型的except块。
所有的错误类型都是继承自BaseException类。
调用堆栈,如果错误没有被捕获,会根据调用栈一直往上抛。
抛出异常class FooError(ValueError): pass def foo(s): n = int(s) if n==0: raise FooError('invalid value: %s' % s) return 10 / n foo('0') #other def bar(): try: foo('0') except ValueError as e: print('ValueError!') raise bar()
使用raise抛出异常,先定义好一个错误类。
在except中还能使用raise,这儿虽然有except,但只是print一下并不处理,把错误继续抛给上一级调用。
IO编程
-
读写文件
try:
f = open('/path/to/file', 'r') print(f.read()) finally: if f: f.close()
这个代码块可以用with来替代
with open('/path/to/file', 'r') as f: print(f.read())
这儿表示只有存在f的时候才执行下面的代码块。
read每次读完整个文件,可以使用read(size)循环调用来完成。
或者可以使用readlines来读取for line in f.readlines(): print(line.strip()) # 把末尾的'\n'删掉
写文件的时候要调用close(),才能保证把内容写进磁盘。为了避免忘记调用,使用下面的方式来写
with open('/Users/michael/test.txt', 'w') as f: f.write('Hello, world!')
-
往内存里读写字符串StringIO,读写二进制文件BytesIO。
-
我们把变量从内存中变成可存储或者可传输的过程叫做序列化,pickling,其他语言中叫serialize等。
-
我们在不同语言中传递对象,需要把对象序列化成标准格式,比如JSON。
进程和线程
-
Linux提供一个fork的系统调用,它调用一次返回两次,因为它会把当前的进程复制一份,分别在父进程和子进程返回。子进程永远返回0,父进程返回子进程的id。
-
可以使用os.fork()创建子进程,但是Windows平台不可用,所以使用另外一个模块multiprocessing
from multiprocessing import Process import os # 子进程要执行的代码 def run_proc(name): print('Run child process %s (%s)...' % (name, os.getpid())) if __name__=='__main__': print('Parent process %s.' % os.getpid()) p = Process(target=run_proc, args=('test',)) print('Child process will start.') p.start() p.join() print('Child process end.')
-
如果要开多个进程,可以使用Pool方法
from multiprocessing import Pool import os, time, random def long_time_task(name): print('Run task %s (%s)...' % (name, os.getpid())) start = time.time() time.sleep(random.random() * 3) end = time.time() print('Task %s runs %0.2f seconds.' % (name, (end - start))) if __name__=='__main__': print('Parent process %s.' % os.getpid()) p = Pool(4) for i in range(5): p.apply_async(long_time_task, args=(i,)) print('Waiting for all subprocesses done...') p.close() p.join() print('All subprocesses done.')
Parent process 669. Waiting for all subprocesses done... Run task 0 (671)... Run task 1 (672)... Run task 2 (673)... Run task 3 (674)... Task 2 runs 0.14 seconds. Run task 4 (673)... Task 1 runs 0.27 seconds. Task 3 runs 0.86 seconds. Task 0 runs 1.41 seconds. Task 4 runs 1.91 seconds. All subprocesses done.
看代码知道,进程池创建了4个进程,每个进程都跑long_time_task这个任务。
调用的时候调了五次,这时task 4在其中task 2结束后才启动。
一般pool的参数是默认CPU的核数。 -
进程间通信,使用queue或者pipes
from multiprocessing import Process, Queue import os, time, random # 写数据进程执行的代码: def write(q): print('Process to write: %s' % os.getpid()) for value in ['A', 'B', 'C']: print('Put %s to queue...' % value) q.put(value) time.sleep(random.random()) # 读数据进程执行的代码: def read(q): print('Process to read: %s' % os.getpid()) while True: value = q.get(True) print('Get %s from queue.' % value) if __name__=='__main__': # 父进程创建Queue,并传给各个子进程: q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) # 启动子进程pw,写入: pw.start() # 启动子进程pr,读取: pr.start() # 等待pw结束: pw.join() # pr进程里是死循环,无法等待其结束,只能强行终止: pr.terminate()
Process to write: 50563 Put A to queue... Process to read: 50564 Get A from queue. Put B to queue... Get B from queue. Put C to queue... Get C from queue.
这里可以看到pw和pr两个子进程,他们的数据都是来自queue,通过queue共享数据。
这儿两个子进程差不多是同时启动的,pw进程慢慢地往队列里压数据,而pr进程则需要等待有数据了才执行,否则就被挂起。 -
任何进程启动时会默认启动一个线程,也叫主线程。主线程又可以启动新的线程。在多进程中,同一个变量,每个进程会有一份自己的拷贝,互不影响。而在一个进程中的多线程却共享同一个变量,多个线程如果同时修改一个变量,可能把内容改乱。
这个时候需要考虑加锁,确保关键运算的时候只有一个线程运行。
import time, threading # 假定这是你的银行存款: balance = 0 lock = threading.Lock() def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n def run_thread(n): for i in range(100000): # 先要获取锁: lock.acquire() try: # 放心地改吧: change_it(n) finally: # 改完了一定要释放锁: lock.release() t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)
这个例子中,change_it先加后减,按理来说应该为0,但是两个线程同时跑,由操作系统调度,很可能出现交叉情况,导致最后的结果出错。但是上面加了锁,其实就是把线程串行化了,其实还是相当于单线程。
-
在多线程中,一个线程跑满,会100%占用一个CPU。如果两个线程跑满,则占用200%个CPU。在N核CPU中,可以启动N个死循环线程把CPU全部跑满。
在C等语言中可以这样做,但是在Python中这样做却只能跑满一个CPU,因为Python有一个GIL锁,任何Python线程启动前都会先获得GIL锁,然后每跑完100个字节码,解释器就会释放GIL锁,让其他线程有机会跑。所以即使有多核,也最多只能用到一个核。
所以在Python中如果要利用多核,可以使用多进程。
-
在多线程中,每个线程都有自己的数据。一个线程使用自己的局部变量比全局变量好,因为局部变量只有线程内部可见,不会影响其他线程。而全局变量的修改必须加锁。
ThreadLocal(TODO)
-
多任务一般采用master-worker模式,master负责分配任务,worker负责执行任务。多线程或者多进程都可以使用master-worker模式。
多进程模式稳定性高但是创建新进程代价高。
多任务一般要考虑核心数,否则资源都消耗在了任务切换了。
计算密集型耗CPU多,比较适合c等语言,而Python等效率不行。
IO密集型,涉及到网络、磁盘IO,这类适合用Python等开发,而c开发无法提升效率。
-
在IO密集型任务中,大部分时间用来等待IO,这时候不太适合单进程单线程方式,最好使用多进程多线程模式。
而现代操作系统支持异步IO,可以在单进程单线程模型执行多任务(协程),这叫做事件驱动模型,比如nginx。
-
process可以分不到不同机器上,而thread只能分布到同一台机器的几个CPU上。