本篇基于Python 2.7.9

根据廖雪峰Python教程整理

  • URL:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000
  • 安装及IDE

上Python官网下载安装包。

配置环境变量:C:\Python27。

cmd中输入 python 出现信息则为安装正确。


IDE推荐集成在Eclipse上的pydev,eclipse插件安装路径:http://pydev.org/updates


Minor Problem:

    用eclipse IDE 写Python时可能会出现输出中文(print “中文”)乱码的情况,这时候要在文件头加入

#-*- encoding: utf-8 -*-

如果不想每次都更改,可以再Preferences-PyDev-Editor-Templates中的Empty中加入:

#-*- encoding: utf-8 -*-
'''
Created on ${date}

@author: ${user}
'''
${cursor}


  • 简介

python作为更高级的语言,解释器是由C或者Java等其他语言实现的,所以在速度上要比较慢。

python中换行和缩进对齐是划分代码块一种方法,结尾没有分号。结尾如有冒号,则下面缩进的语句视为代码块。(缩进一般4个空格)

python源代码不能加密,发布既要发布源代码。

在CMD中也可以写Python,使用exit()退出。

注释使用#标记一行。使用'''@Notes'''可以标记多行。

大小写敏感,如bool型的True和False开头大写。

  • 输出和输入

输出print:

    加字符即可输出指定文字:

print 'hello world'

    不同字符可以用,连接。但是用,连接相当于中间增加一个空格:

print 'hello','world'

    不想增加空格,可以使用:

print 'hello''world'
print 'hello'+'world'

    print自带换行,print就换一行,也可在字符中使用\n。

    如果不想换行,可以用:

print "hello",
print "world"

    但是这种情况自带空格了(+显然不可以),如果要求更高的自由度,可以使用:(需要import sys)

sys.stdout.write("Hello World")

    类似于java的system.out.print();

    print中‘’和“”都是可以的。

    当然Python也支持格式化输出

print "Hi %s, your score is %d." %('Aran',99)


输入raw_input():

    调用raw_input()函数就相当于等待用户输入,可以用:

name = raw_input()

    把结果存入name中。  

    其中raw_input()可以带参数,参数是字符串,相当于输入提示。返回的结构都是字符串类型。


    顺便提下input()函数,它接受的对象是一个Python表达式,返回的是表达式的结果。

    在name = input()后输入:

    4==5

    name的值是是bool型的False


  • 数据类型和变量

python中可以使用type(para)来得到para的数据类型。也可以用isinstance(para, int)来判断。

整型int

浮点型float

字符串型str

    是str不是String。

    “”‘’嵌套是可以使用转义符\。

    使用r''表示''内字符不转义。

布尔型bool

    True、False

    bool值得运算符有and、or、not三种运算

    判断使用 == 

空值None:

    内建类型None表示一个空对象,没有方法和属性。

    None是一个特殊的常量。

    None不是0。None不是空字符串。

    None和任何其他的数据类型比较永远返回False。

    None有自己的数据类型NoneType。

    你可以将None复制给任何变量,但是你不能创建其他NoneType对象。

变量:

    大小写英文、数字和_的组合,且不能用数字开头。

深入理解Python变量在内存中的实现(和Java类似):

    当我们写

    a = 'ABC'

    时,Python解释器是这么执行的:

    1. 在内存中创建一个‘ABC’的字符串

    2. 在内存中穿件一个名为a的变量,并指向‘ABC’

    由此当我们写

    a = 'ABC'

    b = a

    a = 'XYZ'

    时,系统会这样执行:

    

python两个灯闪烁时间不同 python 两个点_python两个灯闪烁时间不同


    

python两个灯闪烁时间不同 python 两个点_Python_02



    

python两个灯闪烁时间不同 python 两个点_python两个灯闪烁时间不同_03


    第一个赋值是指向。第二个赋值是将a的指向给予b。

    理解这一点,对后面复杂函数对象的理解很有意义。


  • ACSII码

ord()函数将字符转换为ASCII码输出。

chr()函数将ASCII码转换为字符输出。


  • 容器之list、tuple

list:

    list是一个可变的有序数据集。首元素从0开始。

    name  = ['Mike','Bob','Tracy']

    python支持倒序。可以是从0~2,也可以是从-1~-3。超出范围后python会报IndexError错误。

    list中数据类型不一定要相同。里面也可以继续使用list:

p = ['asp', 'php']
s = ['python', 'java', p, 'scheme']
print s[2][1]

    当然也可以为空:L = []

    常用函数有:    

        len():    可以得到list的元素个数:len(name)

        append(): 追加到list末尾:append('Adam')

        insert(): 插入到某一索引:insert(1,'Jack')

        pop():    删除某一位置的元素,没有参数则为最后一个元素:    pop() pop(1)


tuple:

    tuple是一个不可变的有序数据集。

    name = ('Mike','Bob','Tracy')

    其它的操作和list类似。只是定义一个元素的tuple时,为了和()区分开来,需要加逗号:name = ('Mike',)

    

    重点解释下不可变

    是指tuple中元素的指向不会变化,即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

    看下面一段代码:

a = ['a1','a2']
b = ['b1','b2']
name = (a,b)
a = ['A1','A2']
b[0] = 'B1'
b[1] = 'B2'
print name

    输出的结果为:(['a1', 'a2'], ['B1', 'B2'])

    这样理解:name中首元素指向了['a1','a2']的内存地址,虽然后来变量a指向了别处,但name中首元素所指向的内存地址中数据没有变化的。name中第二个元素指向了['b1','b2']的内存地址,但是后来该地址中的数据发生了变化,变成了['B1','B2']。所有name中就也有了变化。

    

  • 容器之dict、set

dict:

    类似于Java语言中的Map,使用key-value进行存储,是可变的无序不重复数据集。

    举个栗子(名字—成绩):

    dd = {'Mike':95, 'Bob':96, 'Jenny':92, 'Aran':99}

    对于重复的key,后写入的key-value会覆盖前面的。

    基于key来得到value:dd['Mike']。如果不存在该key则会报KeyError的错误。

    其他函数:

    =:     赋值会覆盖掉原来key的value,如果key不存在,则相当于新增。

    in:     判断一个元素是否在一个容器中,在后面的循环中会介绍。

    get():  也是基于key来得到value的方法,如果不存在可以返回默认的None:dd.get('Thomas') 或自己定义的值: dd.get('Thomas','不存在')

    

    BTW:

        dict是根据key来计算value存储位置的,所以要求key不可变且不能相同。  

        与list相比,dict具有插入查找速度快的特点,相应的占用内存会多一些。

        dict也有比较抽象的表达形式:{'Aran':90,'Bon':100,'Lee':98}['Aran'],这也是一个dict,'Aran'是索引,结果是90。


set:

    set可以看作dict的简化版:只存储key而没有value。也可以看作list的严格版:只能存储无序的无重复的数据集。类似数学意义上的集合。

    set是通过一个list进行构建的。

ss = set(['Allen','Bob','Crystal','David','Eclipse'])

    add(key):    添加元素

    remove(key): 删除元素


    再议不可变之Hash

    dict和set容器都要求key值不可变不重复无序的,是因为他们的储存是要根据key值进行Hash的。所以它们要求元素或者其指向一定能够Hash,且容器中记录的永远是内存中的具体值。

    栗子1:

a = ['a1','a2']
b = ['b1','b2']
ss = set([a,b])

    会提示TypeError,因为a、b是无法hash的。

    栗子2:

b = ['b1','b2']
ss = set(b) 
b[0] = 'B1'
b[1] = 'B2'
print ss

    跟上面tuple的栗子一样,但是结果却不一样:set(['b1', 'b2'])

    因为set直接记录的是具体的值,而tuple记录了指向。


  • 判断与循环

判断:

if A:
    print A
elif B:
    print B
else:
    print 'others'

    判断条件:非零数值、非空字符串、非空list等,就判断为True,否则为False

    BTW:

        Python没有类似java的a>b?a:b三目运算符,但是它提供了xx if xx else xx.举例:a if a>b else b 和java那个三目运算符一样。

    

循环:

    for x in list/tuple/dict/set:

        print x

    把每个元素(dict是key值)代入变量x,然后执行缩进块的语句。

    函数range():

        range(100):生成[0,100)这100个元素的list

        range(5,99):生成[5,99)的list

      

  • 函数

调用:

    举例数据类型转换函数:int(), float(), str(), unicode(), bool()

    bool(2231) --> True


引用:

    函数名其实就是一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”。

    xxx = bool

    print xxx(214312)  --> True


定义:

    举绝对值函数例子:

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

    return后函数就结束了(废话)。如果没有写return,或者只写return,默认返回值是None

    Python中可以return多个值:

    def test(nx,ny):

        return nx,ny

    接受时:

    x,y = text(1,2) 

    其实就是返回个tuple,python简化了。

    BTW*2:

        有些语句函数不知道写什么,可以先写个pass。

        isinstance函数可以进行数据类型检查。

    

参数:

    默认参数:

        类似C++的默认参数(Java不支持,通过重载也可以达到类似效果):

        def power(x,n=2):

        如果只用一个参数,则n默认为2,如果有两个参数则n为第二个参数。

        那么问题来了,多个默认参数,只想指定其中一个该怎么做:

            def power(x,y,n=2,m=3):

            调用时可以:power(1,2,m=9),从而n就是默认的2,m是9。

        Att:默认参数必须指向不变对象,不然可以看下面的例子:

def add_end(L=[]):
    L.append('END')
    return L

        相当于默认参数就是空List,在空List后面追加一个‘END’

        执行print add_end() --> ['END']    结果对的。

        再使用一次:print add_end() --> ['END', 'END']  纳尼??

        解释:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

        相当于:

kk = []
def add_end4(L = kk):
    L.append('END')
    return L
print kk
print add_end4()
print kk
print add_end4()
print kk

        kk的值一直在改变。

        Ps~经测试当我们的默认参数是固定字符串,set数据时,没有上述问题的。

    

    可变参数:

        可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple:

        def calc(*numbers):

        可以采用calc(1,2,3,4)的方式。

        如果有一个list,也可以直接传list:

        L = [1,2,3,4]  calc(*L)

   

    关键字参数:

        而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。         

        def person(name, age, **kw):

        可以传入任意个数的关键字参数:

        >>> person('Bob', 35, city='Beijing')

        name: Bob age: 35 other: {'city': 'Beijing'}

        >>> person('Adam', 45, gender='M', job='Engineer')

        name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}


    参数的组合运用:

        上面的四种参数可以混合使用,注意参数定义的顺序是:必选参数、默认参数、可变参数和关键字参数。    看栗子:

def func(a, b, c=0, *args, **kw):
    print 'a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw

        结果:

>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> func(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

        所有的参数都可以通过一个tuple和dict来传递:

>>> args = (1, 2, 3, 4)
>>> kw = {'x': 99}
>>> func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 99}

    参数总结:

        *args是可变参数,args接收的是一个tuple;

        **kw是关键字参数,kw接收的是一个dict。

        可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));

        关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。

def func14(name, sex, age, job ='student', *args, **kwargs):
    return name,sex,age,job,args,kwargs
func14('Aran','Male','23','studnet',*('java','C++'),**{'college':'USTC','major':'Software'})
func14('Aran','Male','23','student','java','C++',college='USTC',major='Software')

        上面代码中两者是一样的。

        使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。


递归函数:

    不要再return中才调用递归,这样会导致栈溢出。    

        

切片:

    可以非常方便的取得list和tuple的部分元素。

    L是一个list,L[m:n]就是取得[m,n)位置的元素。(Python中经常前闭后开。)L[:]就是全部取。

    Python支持倒序,L[-10:0]就是最后十个元素,而且0和最大位都可以省略。

    Interesting的是Python支持间隔取数:L[::5]就是全部元素间隔5个取一个数。


迭代:

    Python中for循环抽象程度要高于Java的for循环,它可以作用在任何可以迭代的对象上。

    判断一个对象是否可以迭代,可以使用:

isinstance(Object,Iterable)    #返回bool

    传统的List&Tuple遍历语法很简单:

for x in L:
    print x

    重点说下dict的遍历:

def func18():
    dd = {'a':1,'b':2,'c':3,'d':4,'e':5}
    for kk in dd:
        print kk, dd[kk]
    for vv in dd.itervalues():
        print 'value: ',vv
    for kk,vv in dd.iteritems():
        print kk,vv

    其中直接遍历dict是得到key,遍历dict.itervalues()是得到value,遍历dict.iteritems()是得到一个tuple条目。

    enumerate函数可以把list和tuple对象变成位置索引-数值对:

def func19():
    tt = ('b','a','c','e','g')
    for i in enumerate(tt):
        print i

    结果是:    

    (0, 'b')

    (1, 'a')

    (2, 'c')

    (3, 'e')

    (4, 'g')

    上文中我们也知晓,可以用两个变量来直接得到tuple的值。


列表生成式:

    直接看例子:

def func20():
    print [x*x for x in range(1,11)]
    print [x*x for x in range(1,11) if x%2 == 0]
    print [s.lower() for s in ['Hello','World','IBM','Apple']]
    print [m+n for m in 'ABC' for n in 'XYZ']
    print [m+n+z for m in 'AB' for n in 'XY' for z in 'PQ']

    结果:

    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    [4, 16, 36, 64, 100]

    ['hello', 'world', 'ibm', 'apple']

    ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

    ['AXP', 'AXQ', 'AYP', 'AYQ', 'BXP', 'BXQ', 'BYP', 'BYQ']


以上例子在位于PythonLearn的2015-4-7.py。


生成器:

    列表生成式在方便之余,还有一点小问题:如果创建一个几百万的大列表,而我们只需要访问其中的几个元素,那就太浪费空间了。这时候我们需要这种一边循环一边计算的机制,称为生成器(Generator)

g = (m+n+z for m in 'ABC' for n in 'XYZ' for z in '123')

    与列表生成式类似,这样我们定义了一个生成器。

    获取里面的数值需要使用g.next()来得到下一个值。而且生成器会自己记录上次next到什么位置:(生成器也是可以迭代的)

print g.next()
    for n in g:
    print n

    自定义生成器函数 - yield:

    在函数中,函数是调用就顺序执行遇到return或者最后一行函数语句就返回。而在生成器函数中,yield代替了return,而且是在每次调用next()的时候执行函数,遇到yield语句返回,再次执行时是从上次返回的yield处继续执行。

    举个栗子:

#Yield for Generator
def func5():
    print 'Step 1'
    yield 1
    print 'Step 2'
    yield 2
    print 'Step 3'
    yield 3

    直接调用函数是没有效果的。采用:

print func5().next()
print func5().next()
print func5().next()

    输出结果:

    Step 1

    1

    Step 1

    1

    Step 1

    1

    说明直接调用函数next()是无法保留yield记录的,需要将这个函数交给一个变量。

g= func5()
print g.next()
print g.next()
print g.next()

    这样就输出正确的结果了:





Step 1









1









Step 2









2









Step 3









3





    注意!不是g=func5,这个是指向这个函数的变量。


  • 高阶函数(map/reduce, filter, sorted)

通过上文我们已经明白变量可以指向函数(f=func),函数名也是变量在Python中的意义。这一意义使得回调函数变得异常简单。

#callback func
def func6(x, y, f):
    return f(x)+f(y)

调用的话直接print func6(-10,-20,abs)。结果就是30。


map/reduce :

    map函数接受两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列(不一定是list,只要可迭代)的每一个元素,并把结果作为新的list返回。(返回一定是list)

    举例将list中的每个数字当做字符串返回:

print map(str,[11,22,33,44,55,66,77,88,99,100])

    结果为:['11', '22', '33', '44', '55', '66', '77', '88', '99', '100']


    reduce函数接受两个参数,一个是函数,一个是序列,reduce把函数作用在序列上的元素上,并和下一个元素做累积计算。其效果就相当于:

    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

    举个栗子:

def fun8(x,y):
    return x*10+y

print reduce(fun8,[3,5,7,1,6,0,1,0])

    结果为:35716010

    上面我们实现了intTostr,下面我们实现strToint:(虽然这两个函数都可以用自带函数str()和int()直接实现)

def fun8(x,y):
    return x*10+y

def fun9(s):
    return {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}[s]

def fun10(str):
    return reduce(fun8,map(fun9,str))

print fun10('9231297'),type(fun10('9231297'))

    结果为: 9231297 <type 'int'>


filter:

    filter函数用于过滤序列。和map()类似,filter()也接受一个函数和一个序列。不过fliter()把传入的函数依次作用于每个元素,然后根据返回值是True或False决定保留还是丢弃该元素。

    举个栗子:

def func11(n):
    return n%2 == 0

print filter(func11,range(1,101))

    就把[1,101)的偶数保留了下来。

    完成了个素数保留的练习:

#Excerise Time
def primeJudege(n):
    if n == 1:
        return False
    elif n == 2:
        return True
    else:
        p = n/2
        for i in range(2,p+1):
            if n%i == 0:
                return False
        return True

print filter(primeJudege,range(1,101))


sorted:

    排序函数在python中也是抽象的高阶函数。

    sorted函数默认从小到大排序:

    print sorted([36, 5, 12, 9, 21]) ————> [5, 9, 12, 21, 36]

    它可以接受一个函数,作为排序的依据:f(x,y)。函数必须返回-1作为依据:必须要返回-1来指明不调换的情况。(调换的情况可以不指明)如果没有返回-1,则不会对序列做操作。

    如我重写教程中的例子:根据名字首字母从小到大排序,忽略大小写:

def func13(s1,s2):
    ss1 = s1.lower();
    ss2 = s2.lower();
    if ss1<ss2:
        return -1
    else:
        return 1
print sorted(['bob', 'about', 'Zoo', 'Credit'], func13)

    结果:['about', 'bob', 'Credit', 'Zoo']


以上例子在位于PythonLearn的2015-4-8.py。


  • 返回函数及闭包

返回函数:

    当我们调用一个函数,但并不需要立刻得到结果时,我们可以把函数当做一个变量返回。在需要时再计算结果。

def func2(*args):
    def func2i1():
        ans = 0
        for n in args:
            ans = ans + n
        return ans
    return func2i1

    在func2中定义了一个函数func2i1,它可以对func2函数的可变参数进行求和并返回结果。其实func2i1相当于一个闭包函数了(参见:JS函数&闭包)。而在fun2中,直接返回了函数。

    f = func2(1,2,3,4,5)时,f还只是一个函数,令f()可以得到计算结果。

    BTW:令f1 = func2(1,2,3,4,5)且f2 = func2(1,2,3,4,5)。f1和f2并不等。

闭包:

    闭包的属性我们已经很清晰了:

def func3():
    print 'inFunc3'
    def func3i1():
        print 'InFunc3i1'
    print 'outFunc3'

    在调用func3()时,闭包是不会执行了。除非func3()中再调用func3i1():

def func3():
    print 'inFunc3'
    def func3i1():
        print 'InFunc3i1'
    func3i1()
    print 'outFunc3'

    这样就可以打印'InFunc3i1'了。理解了闭包是不会自己执行的,我们看这个例子:

def func4():
    l=[]
    for i in range(0,100):
        def func4i1():
            l.append(i)
    func4i1()
    return l

    func4i1虽然在循环中,但是可以理解为pass。循环结束后i=99,这时执行了一次func4i1(),所以l最后结果就是[99]

    如果,把func4i1()加入循环中,就是[0,1,2,3.....99]。

    理解了上述例子,我们看教程中的例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs
print [x() for x in count()]

    append虽然在循环内,但是并没有执行函数f(不是f()),所以理解成pass。当函数指向都添加(append)完毕了,x()执行函数时,i为3,返回9。所以答案是:[9, 9, 9]

    如果改成如下代码:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f())
    return fs
print [x for x in count()]

    答案就是:[1,4,9]

    教程上采用了更牛逼的方法:再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。

def count():
    fs = []
    for i in range(1,4):
        def f(j):
            def g():
                return j*j
            return g
        fs.append(f(i))
    return fs
print [x() for x in count()]

    相当于把f(1)、f(2)、f(3)放入list中。这样结果就没有什么异议了:[1,4,9]

 

  • 匿名函数

采用lambda关键字。冒号前面的x表示函数参数。匿名函数只能有一个表达式,不用写return,返回值就是该表达式的结果。

lambda x:x*x  就相当于 def f(x): return x*x

匿名函数可以没有参数,也可以当做返回值返回。

def build(x):
    return lambda: x


以上例子在位于PythonLearn的2015-4-9.py。



  • 装饰器

可以参看Java 装饰器模式这一篇。

装饰器,简而言之就是给函数增加一些功能而不影响函数本身。

在Python可以方便的返回函数,装饰器的实现也比java简单很多。

假设有下面两个函数:

import datetime
def func1(mood):
    print mood ,
    print datetime.date.today()
def func2(name,score):
    print name,score
func1('happy')
func2('Aran',99)


func1打印心情和日期。func2打印姓名和分数。

现在我们想让调用函数的时候,打印出执行这个函数的名称,可以采用装饰器模式:

def func2c1(func):
    def wrapper(*args):
        print 'execute %s()....' %func.__name__
        return func(*args)
    return wrapper

这是python中装饰器的格式,教程上的更加标准。

执行一下:

func2c1(func1)('happy')
func2c1(func2)('Aran',99)

结果:

execute func1()....

happy 2015-04-10

execute func2()....

Aran 99

与此同时python还提供了一种更简单的方式:在函数定义前@装饰器,以后执行函数,相当于执行装饰器:

import datetime

def func2c1(func):
    def wrapper(*args):
        print 'execute %s()....' % func.__name__
        return func(*args)
    return wrapper

@func2c1
def func1(mood):
    print mood,
    print datetime.date.today()

@func2c1
def func2(name, score):
    print name, score

func1('happy')
func2('Aran', 99)

结果跟上面是一样的。(ps,函数定义上面可以加多个装饰器,多行@)

如果要让装饰器支持参数,需要将两重函数嵌套增加为三重:

def func2cc1(text):
    def decorator(func):
        def wrapper(*args):
            print '%s %s()...' %(text,func.__name__)
            func(*args)
        return wrapper
    return decorator


@func2cc1('call')
def func1(mood):
    print mood,
    print datetime.date.today()


@func2cc1('call')
def func2(name, score):
    print name, score


func1('happy')
func2('Aran', 99)


  • 偏函数

把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

int()函数可以带一个参数,决定参数是多少进制来返回十进制。int('111',2)的结果是7。

如果我们经常使用2进制,不想每次都输入参数,可以定义一个函数:

def int2(x, base=2):
    return int(x, base)

python也提供了更简单的偏函数方法:int2 = functools.partial(int, base=2)。和上面的函数是一个意思。



  • 模块

每一个python文件都是一个模块。python的模块搜索路径包含当前目录,可以直接import。(注意import不带.py)

Python中还有Java中包的概念,当前目录下需要import 包名.模块名。调用的时候也需要包名.模块名。(可以用别名来免去包名)

注意的是每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是包名。对于这个模块,导入的时候只需要有import 包名

import sys
def func3():
    args = sys.argv
    print 'Hello, your argv is:',
    print args[1:]

上面例子是使用命令行运行Python时,就可以看到带参数符了。


别名:

    导入模块时还可以使用别名。import aa as bb: 这样以后就要使用别名bb。

    Tips:

    Python标准库一般会提供StringIO和cStringIO两个库,这两个库的接口和功能是一样的,但是cStringIO是C写的,速度更快。所以经常会有这样的写法:

try:
    import cStringIO as StringIO
except ImportError: # 导入失败会捕获到ImportError
    import StringIO


作用域:

    正常的函数和变量名都是public的,可以直接引用。

    类似__author__的是特殊变量(表示这个py的作者),可以被直接引用,但一般都是特殊用途。不要使用这种变量名。

    类似_xxx或者__xxx的函数和变量是非公开的,不应该被直接引用。注意:不应该不是不能,python并没有一种方法可以完全限制访问private函数或变量,只是从编程习惯上不应该这么做。

    

安装第三方模块:

    java中第三方依赖包管理可以用maven,而在python中封装了包管理工具:pip。(安装python时,确保勾选了pip和Add python.exe to Path。如果在cmd中输入pip没反应,就要重新运行安装程序添加pip)

    第三方库的名称可以在官网的pypi上搜索,安装只需要在cmd中输入pip install xxx


模块搜索路径:

    默认情况下,python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中。可以把它打印出来查看。

    我们要添加自己的所搜目录,有两种方法:

    一.直接append变量sys.path,运行时修改,运行结束后失效。

    二.设置环境变量pythonpath,该环境变量的内容会被自动添加到模块搜索路径中。


__future__:

要想在当前版本中使用新版的特性,需要使用__future__模块:

    from __future__ import division

    这样把新版本的除法特性移植过来了。

    试一试不同版本中除法:10/3, 10.0/3, 10//3

import module 和 from module import * :

    后者可以在调用时省略


  • 面向对象编程

类与实例:

class Student(object):
    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:

aran = Student()
print aran

结果:<__main__.Student object at 0x10a67a590>。可以看到,变量bart指向的就是一个Student的object,后面的0x10a67a590是内存地址。

可以自由地给一个实例变量绑定属性:aran.name = 'aran'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:

class Student(object):
    def __init__(self,name,score):
        self.name = name
        self.score = score
aran = Student('test',90)
aran.name = 'aran'

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

ps:python类中的成员函数的第一个参数一般都是self。


以上例子在位于PythonLearn的2015-4-10.py。


数据封装:

    类内可以直接定义函数,函数要包含一个参数就是self,类似初始化函数,这个参数在函数调用的时候不需要写。

    前面的例子中,类的变量可以直接在外部进行访问赋值。我们要想把它封装起来需要私有变量:__name,__score。这样就和java类似了,需要get/set方法,外部无法访问。

    严格意义上,即使是私有变量python在外部也可以访问。python解释器只是替换了变量的名称,不过不要这么做。


继承:

    跟Java继承差不多,定义类的时候()里面的是父类。

    python中也有super()方法,不过和java不一样,使用super(class,self)来获得class的父类。

多态:

    子类可以覆盖父类的同名函数,和Java的多态差不多。

    ps:采用instance函数判断一个子类的实例和父类,返回的结果是True。判断一个父类实例和子类,结果肯定是False。

过于继承多态带来的的‘对扩展开放,对修改封闭’参见教程。


对象类型的判断:

    type和isinstance前面已经讲过。其中isinstance参数支持tuple来判断是不是其中一个:isinstance(u'a', (str, unicode))


dir(object):

    返回object的所有属性和方法的list。

    注意到字符串变量含有方法__len__。在Python中,调用len()实际上是自动调用字符串的__len__()方法。

    仅仅把属性方法列出来还是不够的,python提供了getattr()、setattr()、hasattr()方法让我们可以直接的操作一个对象。

    getattr()是得到属性,getattr(obj, 'z'):得到obj对象名称为z的属性或者方法。如果不存在会报AttributeError的错误,也可以设置成不存在返回默认值:getattr(obj,'z',404)

    setattr需要三个参数:对象,属性,值。hasattr只需要两个,返回True或者False。


  • 面向对象高级编程

MethodTypes方法:动态增加成员函数

    前面介绍了如何动态给类绑定属性(直接调用赋值)。位于types模块中的MethodTypes函数可以给类动态的增加成员函数。

    给类的实例增加成员函数:    

from types import MethodType
class Cla2(object):
    pass
def set_name(self,name):
    self.name = name

c = Cla2()
c.ssname = MethodType(set_name,c,Cla2)
c.ssname('Bom')
print c.name
print hasattr(c,'ssname')

    三个参数分别是待绑定函数,实例名,类名。注意绑定到实例的成员函数是ssname而不是set_name。结果是:

    Bom

    True

    给类增加成员函数:

from types import MethodType
class Cla2(object):
    pass
def set_name(self,name):
    self.name = name
Cla2.ssname = MethodType(set_name,None,Cla2)
c = Cla2()
c.ssname('Bom')
print c.name
print hasattr(c,'ssname')

    结果和上面一样,注意中间的参数是None,也可以不写。


type():动态创建类

    动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。clas的定义是运行时动态创建的,而创建class的方法就是使用type()函数。我们也可以通过type()函数创建类:

Cla4c = type('Cla4',(object,),dict(hello=func1))
h =Cla4c()
h.hello('aran')

    以上代码等同于:

class Cla4(object):
    hello = func1
Cla4c = Cla4
h = Cla4c()
h.hello('aran')

    其中type传入的3个参数分别是:

    class的名称;

    继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;

    class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

    ps, 通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。


metaclass:

    直译为元类,我们可以把类看成metaclass的‘实例’。有关metaclass的代码较为复杂且不太常用,在此忽略。


多重继承及Mixin:

    多重继承就是在继承的基础上,多加一个类:Class Cla3(Cla1,Cla2):。(PS,Java是单一继承)

    在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin(掺和模式)。

    为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixin和FlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin。

    Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。


  • 定制类

    在上文中我们看到了类中__len__有着特殊用途。python中,还有一些特殊用途的属性函数和装饰器:

__slots__属性:

    在class中,我们可以通过__slots__来限制class能够添加的属性:

class Cla3(object):
    __slots__ = ('name','age')

    这样这个类只能有这两个属性,否则会报AttributeError的错误。

    ps,父类的__slot__属性子类默认不会继承。如果子类中也定义了__slots__,那么子类允许定义的属性就是自身的__slots__加上父类的__slots__。

__str__:函数,返回类本身的print的结果。

__iter__:函数。如果一个类想要实现for...in的循环,就需要实现__iter__()方法。该方法返回一个迭代对象,然后Python的for循环就会不断地调用该迭代对象的next()方法拿到循环的下一个值,知道遇到StopIteration错误时退出循环。

        举个例子,定制__iter__实现斐波那契数列迭代:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def next(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration();
        return self.a # 返回下一个值

__getitem__:函数。如果一个类想要按照下标取出元素,需要__getitem__实现,在上面类中加入如下函数:

def __getitem__(self, n):
    a, b = 1, 1
    for x in range(n):
        a, b = b, a + b
     return a

        但是此时Fib还不支持切片,因为__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,改进:

def __getitem__(self, n):
    if isinstance(n,int):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a
    if isinstance(n,slice):
        start = n.start
        stop = n.stop
        a,b = 1,1
        L =[]
        for x in range(stop):
            if x >= start:
                L.append(a)
            a,b = b,a+b
        return L

        但是这样的切片还不支持省略,可见实现一些基础功能并不是so easy,利用三目运算符改进一下:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n,int):
            a,b = 1,1
            for x in range(n):
                a,b = b,a+b
            return a
        if isinstance(n,slice):
            start = 0 if n.start == None else n.start
            stop = 100 if n.stop == None else n.stop
            a,b =1,1
            L=[]
            for x in range(stop):
                if x>= start:
                    L.append(a)
                a,b = b,a+b
            return L

ff = Fib()
print ff[:]

除了__getitem__外还有__setitem__():把对象视作list或者dict来对集合进行赋值。__delitem__():删除某个元素。在此不再详述。


__getattr__:在调用类的成员变量函数中,如果类中没有这个属性,python默认就会再调用__getattr__()函数。(如果有的话不会)

class Cla1(object):

    @property
    def score(self):
        return  self._score

    def __getattr__(self, item):
        if item == 'score':
            return 100
        raise AttributeError("\'Cla1\' has no attribute: \'%s\'" %item)
cc = Cla1()
print cc.score

     print的时候执行的就是__getattr__('score')


__call__:定义类的__call__方法可以直接执行实例本身的调用:

class Cla3(object):
    def __call__(self):
        print '__call__'
dd = Cla3()
dd()

    执行的就是__call__函数。判断一个对象是否可以被调用,可以使用callable(object)来判断。如上文中的cc返回False,dd返回True。


@property:设置默认赋值时调用函数的装饰器

    在上面类的变量属性中,直接赋值而不检查参数,是不符合逻辑的。我们采用get/set方法改进:

class Cla4(object):
    def get_score(self):
        return self._score

    def set_score(self,value):
        if not isinstance(value,int):
            raise ValueError('score must be an integer!')
        elif value <0 or value >100:
            raise ValueError('score must between 0 ~ 100!')
        else:
            self._score = value

    参数不符合格式则抛出错误。(注意此时score是private,否则会出问题)

    但是,我们每次都调用get/set略显麻烦,此时需要python内置的@property装饰器来把一个方法变成属性调用直接赋值。如我们想针对类中属性变量score进行装饰:

class Cla5(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!')
        elif value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        else:
            self._score = value

    其中属性的get方法只用@property装饰,set方法需要score.setter装饰。

    此后可以直接给调用得到score的值还是赋值给score都是默认调用上面的方法。


以上例子在位于PythonLearn的2015-4-13.py。

  • 错误

错误处理:

    python支持try...except...finally...,举例:

try:
    print 'try...'
    r = 10/0
    print 'result:',r
except ZeroDivisionError,e:
    print 'except:',e
finally:
    print 'finally...'

    当错误发生时,后续语句print 'result:', r不会被执行,except由于捕获到ZeroDivisionError,因此被执行。最后,finally语句被执行。然后,程序继续按照流程往下走。

python也支持多个except和else:

try:
    print 'try...'
    r = 10/int('a')
    print 'result:',r
except ValueError,e:
    print 'ValueError',e
except ZeroDivisionError,e:
    print 'ZeroDivisionError:',e
else:
    print 'no error!'
finally:
    print 'finally...'

    需要注意的是python的错误其实也是class,所有的错误类型都继承自BaseException,所以使用except时需要注意,它们不但捕获该类型的错误,还把其子类“一网打尽”。会导致子类永远捕获不到错误。

    Exception的继承关系:https://docs.python.org/2/library/exceptions.html#exception-hierarchy


记录错误:

        python运行出错后,我们会在console中看到错误的层层堆栈信息,程序也终止运行。如果想让程序继续运行,可以使用python内置的logging模块把错误堆栈打印出来,并让程序继续执行直至正常退出。看例子:

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except StandardError, e:
        logging.exception(e)
main()
print 'END'

    console输出的结果:

C:\Python27\python.exe C:/Aran_Files/Git/PythonLearn/2015-04-14.py
ERROR:root:integer division or modulo by zero
aran
Traceback (most recent call last):
aran
END
  File "C:/Aran_Files/Git/PythonLearn/2015-04-14.py", line 87, in main
    bar('0')
  File "C:/Aran_Files/Git/PythonLearn/2015-04-14.py", line 83, in bar
    return foo(s) * 2
  File "C:/Aran_Files/Git/PythonLearn/2015-04-14.py", line 80, in foo
    return 10 / int(s)
ZeroDivisionError: integer division or modulo by zero

Process finished with exit code 0

以上例子在位于PythonLearn的2015-4-14.py。


抛出错误:

        在实际代码编写中,我们except到一个错误,打印错误信息后,可以直接raise(raise不带参数就把当前错误原样抛出)。因为捕获错误的目的只是记录一下,由于当前函数不知道应该怎么处理,最恰当的方式就是继续往上抛,以便顶层调用者去处理。

        btw,我们还可以except到一个错误类型,抛出另外一个错误类型,只要是合理的转换逻辑即可。


  • 调试

print:

    打印出来错误,简单粗暴有效。


assert:

    可以用print的地方都可以用assert代替,assert后接一个表达式和一个字符串。如果表达式为假,则抛出AssertionError并打印字符串,为真则没有异常。

def func2(s):
    assert s!=0,'n is zero'
    return s
func2(0)

    assert可以设置忽略,运行py时添加参数-O即可。


logging:

    logging可以指定输出文件的位置,而且可以设置等级,根据等级来确定哪些起作用。

    logging的等级从低到高有:debug, info, warning, error。采用logging.info('')的方式。

    设置级别的方式是:logging.basicConfig(level = logging.INFO)

    ps,需要import logging


pdb.set_trace():设置断点

    需要import pdb。程序会在pdb.set_trace()处暂停并进入pdb调试环境,使用‘p 变量名‘ 查看变量值,使用命令c继续运行。


IDE调试工具:

    Eclipse的调试无需赘述,pycharm的调试:


以上例子在位于PythonLearn的2015-4-20.py。


  • IO编程

IO在计算机中值Input和Output。由于程序和运行时的数据都是在内存中由CPU来执行,涉及到数据交换的地方也就是磁盘、网络等,需要IO接口。

IO编程中,Stream(流)的概念很重要,Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是流出。每个只能单向流动。

由于CPU和内存的速度远远高于外设,所以在IO编程中就有速度不匹配的问题,解决办法两个:

第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

同步IO和异步IO的区别在于是否等待IO执行结果,异步IO要比同步IO的效率更高,但是编程复杂度也会更高。因为,磁盘写入数据完毕时,如何通知CPU会有不同的设计模式。

每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外,本章主要讨论同步IO。


读文件:

对于ASCII编码文件可以直接读:

f = open('Stream.txt','r')
print f.read()
f.close()

open的第一个参数是路径,默认是py当前文件夹,'r'表示只读。read()函数是一次性读取全部内容。最后要关闭stream。

按着java的逻辑,打开文件要trycatch确保文件不存在也不影响后续的操作:

try:
    f = open('Streasm.txt','r')
    print f.read()
except IOError,e:
    pass
finally:
    if f:
        f.close()

这样代码显得比较臃肿,python给我们提供了with方法来替代上述代码:

with open('Stream.txt','r') as f:
    print f.read()

不用自己手动关闭Stream了。此外read()函数可以加参数确定每次最多读取的字节数。readline()可以每次读取一行。readlines()可以一次性读取所有内容并按行返回list。

ps,文本中每一行都有'\n',可以通过line.strip()删掉。


对于非ASCII编码文件,需要以二进制模式打开,再编码:

with open('text.txt','rb') as f:
    print f.read().decode('gbk')

参数rb是打开二进制的方式,decode是按照编码格式解码。python觉得每次read都要解码还是很麻烦,提供了codecs快捷方法:

import codecs
with codecs.open('text.txt','r','gbk') as f:
    print f.read()


写文件:

了解了读文件,写文件就比较简单了:

with open('Stream.txt','w') as f:
    f.write('Hello,world')

其中参数w、wb表示删除原文本写入(再次write时不会覆盖,因为是同一个f)。a表示在原文本上追加。

btw,写文件时操作系统都是先缓存数据,再写入磁盘,只有当stream close时才全部写入。so~最好使用with语句。


查看系统:

python内置的os模块可以直接调用系统提供的接口函数:

os.name:操作系统,如果是windows是nt

os.environ:环境变量

os.getenv():得到某个环境变量


操作文件和目录:

os.path.abspath('.'):得到当前目录下的绝对路径(也可以使用sys.argv[0])

查看当前目录下的所有文件:(参数也可以使用上面的那个绝对路径,而非'.')

for file in os.listdir('.'):

    print file

过滤查看当前目录下文件:

for file in glob.glob('*.py'):

    print file

路径拼接:(由于不同操作系统路径表示不一样,python提供方法省去操作麻烦)

os.path.join(os.path.abspath('.'),'testdir','test.txt')

结果:C:\Aran_Files\Git\PythonLearn\testdir\test.txt

创建删除路径:

os.mkdir()    os.rmdir()

拆分路径和文件:(把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名):

os.path.split(sys.argv[0])

拆分文件拓展名:

os.path.spilitext(sys.argv[0])

重命名和删除文件:

os.rename()和os.remove()

拷贝文件的功能在os模块中并没有提供,可以使用shutil模块中额copyfile函数。


综上我们可以用上面的组合实现一些快捷功能:

当前目录下所有子目录:[x for x in os.listdir('.') if os.path.isdir(x)]

当前目录下所有py文件:[x for x in os.listdir('.') if os.path.splitext(x)[1]=='.py']


练习:编写一个能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件的函数:

def search(str,path=os.path.abspath('.')):
    for x in os.listdir(path):
        if os.path.isdir(x):
            search(str,os.path.join(path,x))
        else:
            if str in x:
                print os.path.join(path,x)


  • 序列化

变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

Python提供cPickle模块和pickle模块来实现序列化pickle.dumps()和反序列化pickle.loads():

try:
    import CPickle as pickle
except ImportError:
    import pickle
d = dict(name='Bob',age=20,score=88)
dp = pickle.dumps(d)
print dp
d = pickle.loads(dp)
print d

也有针对文件的pickle.dump()和pickle.load():

d = dict(name='Bob',age=20,score=88)
with open('dump.txt','wb') as f:
    pickle.dump(d,f)
with open('dump.txt','rb') as f:
    d = pickle.load(f)
print d


上面序列化的格式只能够在python中使用显然不够通用,python内置了json模块来提供转换:

JSON类型        Python类型

{}         dict

[]         list

"string" 'str'或u'unicode'

1234.56         int或float

true/false True/False

null         None

采用json.dumps()和json.loads()来实现。


在序列化当中,默认形式都是以dict传输的。如果不是dict对象(比如类),我们需要手动的将其转换为dict再进行序列化。好在通常类的实例中都有一个__dict__属性,它就是该类的一个dict:

json.dumps(s.__dict__)

有少数类除外,比如定义了__slots__的类,这种情况下只有自己手写转换函数了。dumps()支持很多参数,其中关键字参数default就是一个转换函数。

在反序列化中,我们必须手动实现DictToClass的函数了,用于替代loads里面的object_hook参数:

import json
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
s = Student('Bob', 88)
#序列化
print(json.dumps(s.__dict__))
#反序列化
print(json.loads(json.dumps(s.__dict__),object_hook=lambda d:Student(d['name'],d['score'])))


  • 多进程和多线程

python既支持多进程又支持多线程。

多进程:

    Linux下可以通过fork()调用实现多进程,详见教程。跨平台使用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 'Process will start.'
    p.start()
    p.join()
    print 'Process end.'

    run_proc就是一个打印函数,os.getpid是进程的pid。

    if __name__ == '__main__'这个语句很常见,用来判断当前模块是直接运行的还是import的。import进来的模块的__name__是模块名,缺省名称的当前模块下__name__为__main__。

    Process()函数就是用来创建进程的构造函数,支持很多参数,可以查看Declaration。target传入的函数会在进程启动时执行,该函数的参数就是args传入的tuple。

    start时才启动进程,join用于等待子进程结束后再继续往下运行。

    要启动大量进程的话就考虑进程池:

from multiprocessing import Pool
import os,time,random
def time_task(name):
    print 'Run task %s(%s)...' %(name,os.getpid())
    start = time.time()
    time.sleep(random.random()*10)
    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(time_task,args=(i,))
    print 'Waiting for all subprocesses done...'
    p.close()
    p.join()
    print 'All subprocesses done.'
运行结果:
Parent process 11232.
Waiting for all subprocesses done...
Run task 0(10824)...
Run task 1(8960)...
Run task 2(8216)...
Run task 3(7340)...
Task 2 runs 0.24 seconds.
Run task 4(8216)...
Task 3 runs 2.15 seconds.
Task 1 runs 5.85 seconds.
Task 0 runs 8.99 seconds.
Task 4 runs 9.88 seconds.
All subprocesses done.

    使用方法和上面的差不多。注意import的是Pool,而且Pool()中参数表示同时执行的进程数。Pool对象在调用join()后就开始执行并等待所有子进程执行完毕,调用join()前必须先调用close(),来让进程池不能添加新的进程了。

    多进程就要考虑进程之间的通信:Queue()类

from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
    for value in ['A', 'B', 'C']:
        print 'Put %s to queue...' % value
        q.put(value)
        time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
    while True:
        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()
运行结果:
Put A to queue...
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

以上例子在位于PythonLearn的2015-4-23.py和2015-04-23_process.py。


多线程:

python提供了封装好的threading高级模块进行多线程开发。    

每个进程都有一个主线程:MainThread。创建线程方法:

print threading.current_thread().name
def namePrint(ID):
    print threading.current_thread().name , ID
t = threading.Thread(target=namePrint, name = 'Threadtest',args=(1,))
t.start()
t.join()


结果:
MainThread
Threadtest 1

使用threading.current_thread().name得到线程名,如果自己没有指定线程名,python会以Thread-1方式命名。创建线程的参数和进程相同,在start()时执行线程。


线程锁:

多线程和多进程最大的不同在于,多进程中,同一变量各自有一份备份在于每个进程中,互相不影响。而多线程中,所有变量由所有线程共享,所以没有线程锁的机制内容很容易被改乱。(原因详见教程)

使用锁的方法就是先定义锁,再获得锁,在try中执行修改,在finally中释放掉锁:

balance = 0
lock = threading.Lock()

def changeBalance(n):
    global balance
    balance =  n

def run_thread(n):
    lock.acquire()
    try:
        changeBalance(n)
    finally:
        lock.release()

t1 = threading.Thread(target=run_thread,args=(100,))
t2 = threading.Thread(target=run_thread,args=(20,))
t1.start()
t2.start()
t1.join()
t2.join()
print balance

第2句定义了锁,第5句表示变量是全局的那个变量,第9句获得锁然后执行变量改变再释放掉。

ps,由于python解释器有GIL全局锁的问题,在python的多线程中无法充分利用cpu的每个核,多进程可以。


ThreadLocal:

进程自己的数据该如何传输存放呢?

一种方式是线程的数据就在线程中,不对外共享可见:

import threading
def process_print(name):
    print 'Hello, %s (in %s)' % (name, threading.current_thread().name)
def process_thread(name):
    process_print(name)
t1 = threading.Thread(target= process_thread, args=('Alice',))
t2 = threading.Thread(target= process_thread, args=('Bob',))
t1.start()
t2.start()
t1.join()
t2.join()

这样的想法最自然,但是实现起来有些不简练:线程的数据要当作参数在不同的函数之间传输。(因为函数是共享的)


不通过传参的方法,就是定义全局的dict,将线程数据全部放入其中,这样的话数据相当于透明而且共享:

import threading
dict = {}
def process_print():
    print 'Hello, %s (in %s)' % (dict[threading.current_thread().name], threading.current_thread().name)
def process_thread(name):
    dict[threading.current_thread().name] = name
    process_print()
t1 = threading.Thread(target= process_thread, args=('Alice',))
t2 = threading.Thread(target= process_thread, args=('Bob',))
t1.start()
t2.start()
t1.join()
t2.join()

python给我们提供了另一种办法,既可以简练又可以数据隐藏- ThreadLocal:

import threading
local = threading.local()
def process_print():
    print 'Hello, %s (in %s)' % (local.name, threading.current_thread().name)
def process_thread(name,):
    local.name = name
    process_print()
t1 = threading.Thread(target= process_thread, args=('Alice',))
t2 = threading.Thread(target= process_thread, args=('Bob',))
t1.start()
t2.start()
t1.join()
t2.join()

结果都和第一个结果一样,这样的处理需要考虑不同线程args和函数参数匹配的问题。

以上例子在位于PythonLearn的2015-4-27.py。


分布式进程:

教程讲的略粗糙,在这里略过,有空再深究。


  • 正则表达式

详见正则表达式入门教程和正则表达式速查表,简单的说明下:

\d    表示一个数字

\w    表示一个字母或者数字

\s    表示一个空格或者Tab符

*    表示任意个字符(包括0个)

+    表示至少一个字符

?    表示0个或者1个字符

{n}    表示n个字符

{n,m}    表示n-m个字符

举个栗子:    \d{3}\s+\d{3,8}

3个数字 空格 3-8个数字,表示任意个空格分隔开的带区号的电话号码

ps,特殊字符可以使用 \特殊字符 进行转义。


更精准的匹配需要使用[]和-表示范围:

[0-9]    可以匹配一个0-9的数字

[0-9]+    可以匹配至少一个数字

[0-9a-zA-Z]    可以匹配一个数字或者字母

A|B    可以匹配A或者B

^        表示行的开头,^\d表示以数字为开头

$        表示行的结束,\d$表示以数字结束


正则表达式还可以设置分组,用()表示一个分组。

贪婪匹配:

    正则分组默认的是前面贪婪匹配,如果我们用(^\d+)(0*$)去匹配2312020000则第二个分组就匹配不到,我们必须让第一个不进行贪婪匹配,加一个?就可以了:(^\d+?)(0*$)匹配得到的分组是231202,0000。

python中的正则表达式:

    由于python字符串也需要\进行转义,所以特别注意\\:  print 'ABC\\-001' 结果就是ABC\-001。加上r可以防止转义,详见下文。

    re模块:

    re模块提供match()函数,匹配则返回一个Match对象,否则返回None:(加r防止字符转义)

import re
test = 'QQ1041149156'
s = r'^[Q|q]{2}\d*'
if re.match(s,test):
    print True
else:
    print False

    字符串切分:

    普通的字符切分很简单:'a b c'.split(' ')输出切分后的list。如果复杂一点,a b c之间空格不定,则需要使用正则表达式进行切分:

test = 'a b  c    d'
s = r'\s+'
print re.split(s,test)

    分组:

    正则匹配有分组的话,在python中也可以实现:

test = '091-23123123'
s = r'(^\d{3})-(\d{3,8}$)'
m = re.match(s,test)
print m.groups() 
结果:
('091', '23123123')

     编译:

    python中使用正则表达式,re模块的执行顺序是:先编译正则表达式(如果不合法会报错),再去匹配。如果一个正则要重复使用,处于效率的考虑,可以预先编译,省去重复编译的环节:

test = 'qq1041149156'
s = r'^[Q|q]{2}\d*'
re_s = re.compile(s)
if re_s.match(test):
    print True
else:
    print False

    

  • 常用内建模块

collections:

   namedtuple:可以给tuple添加属性名称,便于方便的引用:

from collections import namedtuple
Point = namedtuple('Point',['x','y'])
p = Point(1,2)
print p.x,p.y

   deque:由于list是线性存储的,所以插入和删除显得比较麻烦,deque就是为了高效实现插入和删除操作的双向列表:append,appendleft,pop,popleft

from collections import deque
L = ['a','b','c']
q = deque(L)
q.append('y')
q.appendleft('x')
print q
结果:
deque(['x', 'a', 'b', 'c', 'y'])

   defaultdict:使用dict时,如果引用的key不存在就会keyError,使用defaultdict则会返回一个默认值。

   OrderedDict:实现dict按照key值插入的顺序排序

   Counter:是一个简单的计数器,统计字符出现的个数:

from collections import Counter
c= Counter()
for ch in 'Programmer':
    c[ch] = c[ch] + 1
print c
结果:
Counter({'r': 3, 'm': 2, 'a': 1, 'e': 1, 'g': 1, 'o': 1, 'P': 1})


Base64:

    一种任意二进制到文本字符串的编码方式。在此略过。


struct:

    pyhton中用来解决str和其它二进制数据类型的转换。在此略过。


hashlib:

    python中的hashlib提供了常见的摘要算法,MD5和SHA1等等:

import hashlib
md5 = hashlib.md5()
md5.update("My name is AranLiu.")
print md5.hexdigest()

    算法主要用于验证和防篡改,详情参见教程。


itertools:

    python的内建模块提供了非常有用的用于操作迭代对象的函数。在此略过。

以上例子在位于PythonLearn的2015-4-28.py。



XML、HTMLParser、PIL和图形界面编程在此略过。


python基础知识点到此结束。

教程随后的项目及实战模块,会以单独笔记的形式进行一周一更。