美文网首页我爱编程
python学习笔记

python学习笔记

作者: renyangfar | 来源:发表于2017-10-12 19:39 被阅读0次

    python学习笔记

    声明:学习笔记主要是根据廖雪峰官方网站python学习学习的,另外根据自己平时的积累进行修正和完善,仅作个人笔记所用,无商业用途。
    如需获取更好的体验,请移步python学习笔记

    python基础

    dict

    字典(dict)使用key和value的形式方便进行查找。

    • 创建dict
    >>> D = {'a' : 1,'b' : 2,'c' : 3}
    >>> D1 = dict(name='Bob', age=20, score=88)
    >>> D2 = dict([('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6)])
    >>> D
    {'a': 1, 'b': 2, 'c': 3}
    >>> D1
    {'name': 'Bob', 'age': 20, 'score': 88}
    >>> 
    >>> D2
    {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
    
    • 判断字典是否存在
    d = dict()
    for i in xrange(100):
        key = i % 10
        if key in d:
            d[key] += 1
        else:
            d[key] = 1
    
    • 给字典默认value
    d = dict()
    
    for i in xrange(100):
        key = i % 10
        d[key] = d.get(key, 0) + 1
    
    • 给所有key赋默认value
    from collections import defaultdict
    
    d = defaultdict(lambda: 0)
    
    for i in xrange(100):
        d[i % 10] += 1
    

    str

    判断字符串是否包含子字符串

    • find()
    >>> str = 'hello world'
    >>> str.find('world')
    6
    >>> str.find('xx')
    -1
    >>> str.find('rld',4,len(str))
    8
    >>> 
    
    • in
    >>> str = 'hello world'
    >>> 'he'in str
    True
    >>> 'xx'in str
    False
    >>> 
    

    切片

    Python的切片功能可以对list,tuple,str等进行元素截取操作。

    L = list(range(10))#定义一个10个元素的list
    a = L[2:5]#截取从位置2到5位置的元素,包括位置2不包括5位置的元素
    b = L[:3]#从位置0到位置3的元素
    c = L[8:]#从位置8到位置末尾的元素
    d = L[-4:-1]#从倒数位置4到倒数位置1的元素
    e = L[1:6:2]#从位置1到位置6间隔为2的元素
    f = L[:]#复制list
    

    迭代

    在python中,str,dict,list,tuple等可迭代对象都可以通过迭代遍历元素,我们可以通过collections模块中的Iterable类型判断是不是可迭代对象。

    • 迭代对象判断
    from collections import Iterable
    
    isinstance('abc',Iterable)
    
    isinstance(('a','b','c'),Iterable)
    
    isinstance(['a','b','c'],Iterable)
    
    • list迭代

    1、只输出元素:

    for i in ['a','b','c']:
      print i
    

    2、输出位置和元素:

    for i,value in enumerate(['a','b','c']):
      print i,value
    
    • dict迭代

    1、输出key:

    D = {1:'a',2:'b',3:'c'}
    for key in D:
      print key
    

    2、输出value:

    D = {1:'a',2:'b',3:'c'}
    for value in D.values():
      print value
    

    3、同时输出key和value:

    D = {1:'a',2:'b',3:'c'}
    for key,value in D.items():
      print key,value
    

    小结

    从以上可以看出,python中可以用几个变量迭代,如:

    for x,y in ([1,'a'],[2,'b'],[3,'c']):
      print x,y
    

    只要满足迭代条件,用Iterable判断,就可以用for语句进行迭代,也包括自定义的类型。

    补充

    可迭代对象(Iterable)和迭代器(Iterator)是不一样的,可迭代对象可以是list,dict ,set,tuple,可以使用for循环进行遍历,对象里面的元素是固定的,所以可以得到可迭代对象所占的空间和大小;迭代器不能计算大小,只能通过next()函数获取下一个元素而不能使用for循环。也就是凡是可作用于for循环的对象都是Iterable类型;凡是可作用于next()函数的对象都是Iterator类型,使用Iterator函数进行判断:

    from collections import Iterator
    isinstance((x for x in range(10)), Iterator)
    

    它们表示一个惰性计算的序列;可以使用iter()函数把可迭代对象变成迭代器:iter(['a','b'])

    列表生成式

    python中可以使用列表生成式(List Comprehensions)生成具有一定规则的列表,如需要生成list:L = [1,4,9,16,25,36,49,64,81],使用一条语句即可生成:
    [x*x for x in range(1,10)]
    在两个列表中做两层循环运算:
    [x + y for x in ['a','b','c'] for y in ['d','e','f']]
    输出:
    ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf']

    生成器

    列表生成式可以生成一个完整的列表,有时会因为列表过大而造成存储空间的浪费。生成器不会一次性生成整个列表,而是在需要的时候从生成器总取出,它会记录上一次生成的数据。

    创建生成器的方式一般有2种:

    • 把列表生成式的[]改成():
      D = (x*x for x in range(1,10))
    • 用函数实现:
    def getList():
      for x in range(1,10):
        yield x*x
    

    取出生成器元素的方式一般也有2种,一种是使用函数next(),另一种是通过for循环。

    • 通过next():
    next(getList())#取出第一个
    next(getList())#再次调用取出第二个
    

    值得注意的是当没有更多元素时,会抛出StopIteration的错误。

    • 使用for循环:
    for i in getList():
      print i
    

    通过for循环不需要关心StopIteration的错误。在python3中,如果需要取出函数的返回值,需要捕获StopIteration错误,返回值包含在StopIteration的value中:

    while True:
      try:
        for i in getList():
          print i
       except StopIteration as e:
               print('Generator return value:', e.value)
               break
    

    小结

    取出生成器元素一般使用for循环。

    map/reduce

    map

    map接收一个函数(带一个参数)和一个iterable,返回把iterable作用于这个函数的iterator。

    def multiply(x):
      return x*x
    
    list(map(multiply,[1,3,5,7]))
    

    返回:[1, 9, 25, 49]

    reduce

    reduce是functools模块中的函数,接收两个参数,一个是函数(带两个参数),另一个是iterable。函数会依次与iterable里的上一个元素的运算结果返回同下一个元素进行运算,最终返回一个运算结果。

    from functools import reduce
    def multiply2(x,y):
      return x * y
    reduce(multiply2,[1,3,5,7])
    

    返回:105
    reduce(multiply2,[1,3,5,7])的运算可以等价为:
    reduce(multiply2,[1,3,5,7]) = multiply2(multiply2(multiply2(1, 3), 5), 7)

    filter

    filter可以过滤一个iterable中不需要的元素。filter接收两个参数,一个是函数,另一个是iterable,返回一个iterator。函数会把iterable中的元素当做参数,返回为True的元素组成iterator。

    def isOdd(x):
      return x%2
    
    list(filter(isOdd,range(10)))
    

    返回:[1, 3, 5, 7, 9]

    sorted

    sorted函数可对iterable进行排序。#sorted(iterable[, cmp[, key[, reverse]]])

    • 对数字排序默认从小到大:
      sorted([2,3,1,6,4])
      返回:[1, 2, 3, 4, 6]
      可以反向排序:
      sorted([2,3,1,6,4],reverse = True)
      返回:[6, 4, 3, 2, 1]
    • 对str排序按照ASCII:
      sorted(['Hello','world','google'])
      返回:['Hello', 'google', 'world']
      忽略大小写:
      sorted(['Hello','world','google'],key = str.lower)
      返回:['google', 'Hello', 'world']

    错误,调试,测试

    异常处理(try...except...finally...)

    • 捕获异常
      写程序难免遇到错误,如把0作为除数,内存溢出,用户输入的不规范等。python中可以利用try...except...finally...处理这些错误。如:
    try:
      a = input('Please input a integer:')
      b = int(a)    #字符转数字
      print('you input is:%d' % b)
    except ValueError as e:
      print('except:',e)
    finally:
      print('Finally...')
    

    分别输入数字和字符测试:

    Please input a integer:3
    you input is:3
    Finally...
    
    Please input a integer:a
    except: invalid literal for int() with base 10: 'a'
    Finally...
    

    输入数字3不会进入except ,会进入finally。
    输入字符a会进入except,也会进入finally,但会在执行b = int(a) #字符转数字这行后,直接跳入except,不会打印you input is:。当然,异常有很多种,except也可以捕获多种异常。如果要捕获所有异常则写成:except Exception as e:

    • 抛出异常
      抛出异常有raise唯一的标识符确定:
    raise NameError('HiThere')
    

    异常也是类,可以抛出自带的异常,也可以抛出自己编写的一个异常建议使用自带异常。

    • 打印异常信息
      异常信息可以有logging模块打印出来。
    import logging
    
    try:
      raise NameError('HiThere')
    except Exception as e:
      logging.exception(e)
    

    调试

    当程序的运行达不到自己想要的效果时,就需要调试来发现程序的错误了。python总常用的调试方式有种:

    • print()
    • assert
    • logging
    • pdb

    print()

    熟悉的打印语句,简单实用。但是需要再调试完成后删除。

    assert

    assert语法格式:
    assert Expression[, Arguments]
    Expression为True时,则不打印,为False时,Pyhotn会报一个AssertionError 异常。
    如:

    def KelvinToFahrenheit(Temperature):
       assert (Temperature >= 0),"Colder than absolute zero!"
       return ((Temperature-273)*1.8)+32
    

    测试:

    >>> print (KelvinToFahrenheit(273))
    32.0
    >>> print (int(KelvinToFahrenheit(505.78)))
    451
    >>> print (KelvinToFahrenheit(-5))
    Traceback (most recent call last):
      File "<pyshell#371>", line 1, in <module>
        print (KelvinToFahrenheit(-5))
      File "<pyshell#367>", line 2, in KelvinToFahrenheit
        assert (Temperature >= 0),"Colder than absolute zero!"
    AssertionError: Colder than absolute zero!
    >>> 
    

    当不需要执行断言语句时,可以使用-O参数来关闭assert:
    $ python3 -O err.py

    logging

    logging不会打印异常,可以分级别打印信息,分为DEBUG,INFO,WARNING,ERROR,CRITICAL五个级别。

    import logging
    logging.basicConfig(filename='example.log',level=logging.INFO)
    
    logging.debug('This message should go to the log file')
    logging.info('So should this')
    logging.warning('And this, too')
    

    logging.basicConfig()可以指定打印的级别,只有在指定打印级别之上的logging信息才可以打印出来,还可以输出到文件。

    pdb

    让程序单步执行或者执行到断点。为了方便,大多会用到IDE,推荐使用pycharm。

    单元测试

    验证一个python模块是否能正常工作,就需要进行单元测试。如编写mydict.py模块,验证其是否具有达到功能,需要编写mydict_test.py测试模块进行测试。
    直接贴代码:
    mydict.py文件如下:

    class Dict(dict):
        def __init__(self,**kw):
            super().__init__(**kw)
            
        def __getattr__(self,key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'"% key)
                
        def __setattr__(self,key,value):
            self[key] = value
    

    mydict_test.py文件如下:

    import unittest
    
    from mydict import Dict
    class TestDict(unittest.TestCase):
    
        def test_init(self):
            d = Dict(a=1, b='test')
            self.assertEqual(d.a, 1)
            self.assertEqual(d.b, 'test')
            self.assertTrue(isinstance(d, dict))
    
        def test_key(self):
            d = Dict()
            d['key'] = 'value'
            self.assertEqual(d.key, 'value')
    
        def test_attr(self):
            d = Dict()
            d.key = 'value'
            self.assertTrue('key' in d)
            self.assertEqual(d['key'], 'value')
    
        def test_keyerror(self):
            d = Dict()
            with self.assertRaises(KeyError):
                value = d['empty']
    
        def test_attrerror(self):
            d = Dict()
            with self.assertRaises(AttributeError):
                value = d.empty
    
        def setUp(self):
            print('setUp')
    
        def tearDown(self):
            print('tearDown')
                
    if __name__ == '__main__':
            unittest.main()
    

    单元测试继承unittest.TestCase,每个测试方法需要以test开头,setUp()方法与tearDown()方法是运行在测试方法之前和之后的方法,可以做特殊用途,如打开和关闭数据库。
    常用的验证方法如:

    self.assertEqual(d.a, 1)   #是否相等
    
    self.assertTrue('key' in d)  #是否True
    
    with self.assertRaises(KeyError):   #是否为指定的异常
                value = d['empty']
    

    运行mydict_test.py:

    Launching unittests with arguments python -m unittest mydict_test.TestDict in C:\renyanliu\python
    setUp
    tearDown
    setUp
    tearDown
    setUp
    tearDown
    setUp
    tearDown
    setUp
    tearDown
    
    
    Ran 5 tests in 0.004s
    
    OK
    
    Process finished with exit code 0
    

    文档测试

    python中的文档注释可以让读者更容易的理解代码,如复制在python交互环境下的运行效果,能清晰的表明代码的功能,那这部分文档的注释本身就起到一个验证代码的功能。python中可以使用文档的形式来验证代码,一举两得。如下,编写mydict2模块:

    # mydict2.py
    class Dict(dict):
        '''
        Simple dict but also support access as x.y style.
    
        >>> d1 = Dict()
        >>> d1['x'] = 100
        >>> d1.x
        100
        >>> d1.y = 200
        >>> d1['y']
        200
        >>> d2 = Dict(a=1, b=2, c='3')
        >>> d2.c
        '3'
        >>> d2['empty']
        Traceback (most recent call last):
            ...
        KeyError: 'empty'
        >>> d2.empty
        Traceback (most recent call last):
            ...
        AttributeError: 'Dict' object has no attribute 'empty'
        '''
        def __init__(self, **kw):
            super(Dict, self).__init__(**kw)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
    
        def __setattr__(self, key, value):
            self[key] = value
    
    if __name__=='__main__':
        import doctest
        doctest.testmod()
    

    运行代码:

    Process finished with exit code 0
    

    无报错,表明代码功能正常。如果删除setattr()这个函数,再次运行:

    **********************************************************************
    File "C:/renyanliu/python/mydict2.py", line 11, in __main__.Dict
    Failed example:
        d1['y']
    Exception raised:
        Traceback (most recent call last):
          File "C:\Users\User\AppData\Local\Programs\Python\Python36\lib\doctest.py", line 1330, in __run
            compileflags, 1), test.globs)
          File "<doctest __main__.Dict[4]>", line 1, in <module>
            d1['y']
        KeyError: 'y'
    **********************************************************************
    1 items had failures:
       1 of   9 in __main__.Dict
    ***Test Failed*** 1 failures.
    
    Process finished with exit code 0
    

    就会报KeyError。
    文档测试严格按照python的交互环境的输入与执行,具有理解代码与检验代码的双重功能。无需担心的是在导入有文档测试的模块不会执行文档测试。

    编码

    • ASCII:只能表示128个字符。如:A - 65,a - 97
    asciifull.gif
    • GB2312:GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个。编码表见:GB2312编码表或者GB2312编码2
    • Unicode:记录全球所有语言,一般用两个字节表示。
    • UTF-8:UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。
    编码对比

    从上表可以看出:UTF-8对比Unicode节省了内存空间,且ASCII可以看做是UTF-8编码的一部分。

    • 补充:在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
      用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件。

    python应用

    整数与Unicode(ASCII)相互转换

    • 整数->Unicode:chr(i)
      给出整数i,返回对应的Unicode码,整数i的范围是0~1114111。超出范围会抛出ValueError的异常。
    >>> chr(38)
    '&'
    >>> chr(97)
    'a'
    >>> chr(1114111)
    '\U0010ffff'
    >>> chr(1114112)
    Traceback (most recent call last):
      File "<pyshell#278>", line 1, in <module>
        chr(1114112)
    ValueError: chr() arg not in range(0x110000)
    
    • Unicode->整数:ord(c)
      与chr(i)恰好相反,给出Unicode码c,返回对应的整数。
    >>> ord('a')
    97
    >>> ord('#')
    35
    
    • 下图为ASCII码表:
    asciifull.gif

    json

    json数据类型使用与保存于传输数据,python中的数据可以与json数据做互相转换,标准的json编码是utf-8。

    • dict转json
    >>> import json
    >>> D = dict(width = 20,heigh = 40,lenth = 60)>>> json.dumps(D)
    >>> json.dumps(D)
    '{"width": 20, "heigh": 40, "lenth": 60}'
    >>> 
    
    • json转dict
    >>> import json
    >>> json.loads('{"width": 20, "heigh": 40, "lenth": 60}')
    {'width': 20, 'heigh': 40, 'lenth': 60}
    >>> 
    

    默认的python与json的转换只支持dict,list,tuple。但是如果需要把类也序列化一个json时,需要编写default参数的函数。具体参考:json进阶

    正则表达式

    匹配规则

    • 匹配字符
      \d——匹配一个数字
      \w——匹配一个数字或字母
      \s——匹配一个空格或tab键
      .——可以匹配任意个字符(包括0个)

    • 匹配个数
      +——至少匹配一个字符
      *——匹配任意个字符
      {n}——匹配n个字符
      ?——匹配0个或1个字符
      {n-m}——匹配n-m个字符

    • 特殊匹配
      |——或匹配
      ^——行开头匹配
      $——行结束匹配
      []——精准匹配
      ()——分组匹配

    re模块

    re模块包含正则表达式的所有功能,基本语法:re.match(pattern, string)
    匹配正常,返回一个Match对象;匹配失败,返回none

    >>> import re
    >>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
    <_sre.SRE_Match object; span=(0, 9), match='010-12345'>
    >>> re.match(r'^\d{3}\-\d{3,8}$', '010-a12345')
    >>> 
    

    编译:如果同一个表达式同多组字符串进行匹配,更方便的是利用正则表达式的编译功能。如:

    >>> import re
    # 编译:
    >>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
    # 使用:
    >>> re_telephone.match('010-12345').groups()
    ('010', '12345')
    >>> re_telephone.match('010-8086').groups()
    ('010', '8086')
    

    字符串切分

    >>> 'a b   c'.split(' ')
    ['a', 'b', '', '', 'c']
    >>> re.split(r'\s+', 'a b   c')
    ['a', 'b', 'c']
    >>> 
    

    分组

    要提取字符串中的指定字符串,可以利用分组功能来实现,用'()'表示的就是要提取的分组(Group)。

    >>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
    >>> m
    <_sre.SRE_Match object; span=(0, 9), match='010-12345'>
    >>> m.group(0)
    '010-12345'
    >>> m.group(1)
    '010'
    >>> m.group(2)
    '12345'
    

    group(0):表示原字符串
    group(1):第1个分组提取的字符串
    group(n):第n个分组提取的字符串

    贪婪匹配

    先看一个例子:

    >>> re.match(r'^(\d+)(0*)$', '102300').groups()
    ('102300', '')
    >>> re.match(r'^(\d+?)(0*)$', '102300').groups()
    ('1023', '00')
    >>> 
    

    正则表达式中有两个分组匹配(\d+)(0*),但在第一个表达式中没有第二组((0*))的匹配结果,原因是都被第一组((\d+))给匹配了,第一组的匹配就叫做贪婪匹配,如果在表达式后加入?如:((r'^(\d+?)),就表示非贪婪匹配,如表达式二,第二组也有了匹配结果。

    大端模式与小端模式

    • 大端模式( Big-Endian):高位字节排放在内存的低地址端,低位字节排放在内存的高地址端
    • 小端模式(Little-Endian):低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
    • 网络字节序:TCP/IP各层协议将字节序定义为Big-Endian。

    OpenPyXl学习手册

    参考OpenPyxl官方文档
    在使用openpyxl之前,需要安装openpyxl,打开命令行,输入以下命令并回车(安装python时勾选pip命令):
    openpyxl
    pip install openpyxl

    工作簿(workbook)

    • 打开已有工作簿
    >>> from openpyxl import Workbook,load_workbook
    >>> wb = load_workbook('test.xlsx')
    
    • 创建工作簿
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> 
    
    • 保存到文件
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> wb.save('balances.xlsx')
    
    • 作为模板文件
    >>> from openpyxl import Workbook,load_workbook
    >>> wb = load_workbook('document.xlsx')
    >>> wb.template = True
    >>> wb.save('document_template.xltx')
    
    注意

    1、打开文件为.xlsx格式文件时,保存文件也需要以.xlsx格式文件保存
    2、要以keep_vba=True保存.xlsm格式文件

    工作表(sheet)

    • 获取创建的默认工作表
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> wb.active
    <Worksheet "Sheet">
    
    • 创建新的工作表
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws1 = wb.create_sheet('Mysteet')    #在末尾插入
    >>> ws2 = wb.create_sheet('Mysheet2',0)  #插入到第一个
    >>> 
    
    • 重命名工作表
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws = wb.active
    >>> ws.title = 'sheet1'
    >>> 
    
    • 改变标题卡颜色
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws = wb.active
    >>> ws.sheet_properties.tabColor = "1072BA"  #RRGGBB color code
    >>> 
    
    • 根据工作表名字获取工作表
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws3 = wb['sheet1']
    >>>
    
    • 打印工作簿中的工作表名
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws1 = wb.create_sheet('Mysteet')
    >>> ws2 = wb.create_sheet('Mysheet2',0)
    >>> print(wb.sheetnames)
    ['Mysheet2', 'Sheet', 'Mysteet']
    >>> for sheet in wb:
        print(sheet.title)
    
        
    Mysheet2
    Sheet
    Mysteet
    >>> 
    
    • 复制一份工作表
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> source = wb.active
    >>> target = wb.copy_worksheet(source)
    >>> 
    
    • 获取最大的行与列
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws = wb.active
    >>> for row in range(1, 40):
        ws.append(range(600))
    
    >>> ws.max_column
    600
    >>> ws.max_row
    39
    >>> 
    

    单元格(cell)

    • 访问单元格
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws = wb.active
    >>>
    >>>#访问一个单元格
    >>> c1 = ws['A2']
    >>> c2 = ws.cell(row = 4,column = 2,value = 10)
    >>>
    >>>#访问多个单元格
    >>> cell_range = ws['A1':'C2']
    >>> colC = ws['C']
    >>> col_range = ws['C:D']
    >>> row10 = ws[10]
    >>> row_range = ws[5:10]
    >>>
    >>>#iter_rows方式访问
    >>> for row in ws.iter_rows(min_row=1, max_col=3, max_row=2):
    ...    for cell in row:
    ...        print(cell)
    <Cell Sheet1.A1>
    <Cell Sheet1.B1>
    <Cell Sheet1.C1>
    <Cell Sheet1.A2>
    <Cell Sheet1.B2>
    <Cell Sheet1.C2>
    >>>
    >>>#打印所有单元格
    >>> tuple(ws.rows)
    ((<Cell 'Sheet'.A1>, <Cell 'Sheet'.B1>), (<Cell 'Sheet'.A2>, <Cell 'Sheet'.B2>), (<Cell 'Sheet'.A3>, <Cell 'Sheet'.B3>), (<Cell 'Sheet'.A4>, <Cell 'Sheet'.B4>))
    >>>
    >>> tuple(ws.columns)
    ((<Cell 'Sheet'.A1>, <Cell 'Sheet'.A2>, <Cell 'Sheet'.A3>, <Cell 'Sheet'.A4>), (<Cell 'Sheet'.B1>, <Cell 'Sheet'.B2>, <Cell 'Sheet'.B3>, <Cell 'Sheet'.B4>))
    >>> 
    
    
    • 单元格赋值
    >>> from openpyxl import Workbook
    >>> wb = Workbook()
    >>> ws = wb.active
    >>> ws['A2'] = 2
    >>> c = ws['A3']
    >>> c.value = 5
    >>> print(c.value)
    5
    >>> 
    

    xlutils学习手册

    安装

    pip install xlutils

    各模块简介

    xlutils是xlrd和xlwt两个的集合包,即可读可写Excel文件,它分为以下几个模块。

    • xlutils.copy
      将xlrd.Book对象转变成xlwt.Workbook对象以便于操作的工具。
    • xlutils.display
      用友好和安全的方式显示有关xlrd的信息的使用功能
    • xlutils.filter
      一个用于拆分和过滤Excel文件为新的Excel文件的框架
    • xlutils.margins
      一个工具,它能找出存在有用数据的Excel文件的数量
    • xlutils.save
      将xlrd.Book对象序列化为Excel文件的工具。
    • xlutils.styles
      使用格式化信息的工具表达了Excel文件中的样式。
    • xlutils.view
      易于表现工作表中的数据的视图

    xlrd

    能提取Excel的数据,Excel格式可以是xls,xlsx,可运行于任何平台上。可以用python2.7或者python3.4+开发。详细请参考xlrd文档

    Excel文件
    • 打开Excel文件
      book = xlrd.open_workbook("myfile.xlsx")
    Excel工作表
    • 获取工作表数量
      book.nsheets
    • 获取工作表名字
      book.sheet_names()
    • 获取第一个工作表
      sh = book.sheet_by_index(0)
      table=data.sheets()[0]
    • 通过表名获取工作表
      table=data.sheet_by_name('mysheet')
    • 获取工作表行数
      sh.nrows
    • 获取工作表列数
      sh.ncols
    Excel单元格
    • 获取某行单元格
      sh.row_values(m)
    • 获取单元格
      sh.cell_value(rowx=m, colx=n)
      sh.row(m)[n].value

    操作数据库

    数据库可以用来存储数据,而不是存储在内存中。并且通过sql语句可以方便的操作数据库,十分方便。
    一个数据库中可以存放多个表,表中就是存储数据的地方,不同的表可以通过外键进行关联。操作数据库之前,需要先连接到数据库。
    付费的数据库就不说了,说一下开源免费的,有SQLite,mysql等。

    SQLite

    python内置的轻量级数据库,无需安装。适合桌面和移动应用。

    • 打开数据库,没有则会创建:
    >>> import sqlite3
    >>> conn = sqlite3.connect('test.db')
    
    • 创建cursor,操作数据库
    >>> cursor = conn.cursor()
    
    • 执行sql语句
    cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
    
    • 返回结果
    >>> values = cursor.fetchall()
    
    • 关闭cursor
    >>> cursor.close()
    
    • 提交
    >>> conn.commit()
    
    • 关闭连接
    >>> conn.close()
    

    MySQL

    MySQL是使用最为广泛的数据库,在利用python使用MySQL之前,需要先安装MySQL,见:MySQL数据库community下载。安装MySQL后,还需要安装python的MySQL数据库的库,见mysql-connector python。自此,就可以使用python来操作MySQL啦。

    • 导入MySQL驱动:
    >>> import mysql.connector
    
    • 建立连接, 注意把password设为你的root口令(需要先建立test数据库):
    >>> conn = mysql.connector.connect(user='root', password='password', database='test')
    
    • 创建cursor,操作数据库
    >>> cursor = conn.cursor()
    
    • 创建user表:
    >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
    
    • 插入一行记录,注意MySQL的占位符是%s:
    >>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])
    
    • 提交事务:
    >>> conn.commit()
    
    • 关闭cursor
    >>> cursor.close()
    
    • 运行查询:
    >>> cursor = conn.cursor()
    >>> cursor.execute('select * from user where id = %s', ('1',))
    >>> values = cursor.fetchall()
    >>> values
    [('1', 'Michael')]
    # 关闭Cursor和Connection:
    >>> cursor.close()
    True
    >>> conn.close()
    
    • 关闭连接
    >>> conn.close()
    

    IO编程

    文件读写

    新建一个hello.txt文件,文件内容如下:

    hello.txt
    hello2.txt

    打开文件

    • 以文本模式打开文件,默认
    >>> f = open('C:/renyanliu/python/hello.txt','r')
    >>> f.close()
    
    • 以二进制模式打开文件
    >>> f = open('C:/renyanliu/python/hello2.txt','rb')
    >>> f.close()
    

    读文件:

    • f.read():返回文件的所有内容
    >>> f = open('C:/renyanliu/python/hello.txt','r')
    >>> f.read()
    'hello-1\nhello-2\nhello-3\n\n'
    >>> f.close()
    
    • f.readlines():返回list,文件的每一行为一个元素
    >>> f = open('C:/renyanliu/python/hello.txt','r')
    >>> f.readlines()
    ['hello-1\n', 'hello-2\n', 'hello-3\n', '\n']
    >>> f.close()
    
    • f.readline():每次读取文件中的一行
    >>> f = open('C:/renyanliu/python/hello.txt','r')
    >>> f.readline()
    'hello-1\n'
    >>> f.readline()
    'hello-2\n'
    >>> f.close()
    

    写文件:

    • 写入文本文件
    >>> f = open('C:/renyanliu/python/hello.txt','w')
    >>> f.write('hello-4')
    7
    >>> f.close()
    >>> 
    

    写文件后要记得关闭文件,确保文件的写入。 f.write('hello-4')会清空文本的内容,重新写入。写后的hello.txt文件如下:

    hello.txt
    • 写二进制文件
    >>> f = open('C:/renyanliu/python/hello2.txt','wb')
    >>> f.write(b'\xc4\xe3\xba\xc3')
    4
    >>> f.close()
    

    注意

    文件读写很容易发生异常,如文件路径找不到等,可以使用try...except...finally...,确保文件正常关闭。

    try:
        f = open('/path/to/file', 'r')
        f.read()
    finally:
        if f:
            f.close()
    

    更方便的是推荐使用with open,在操作文件后会自动关闭:

    with open('/path/to/file', 'r') as f:
      f.read()
    

    StringIO和BytesIO

    读写数据的对象不仅可以是文件,还能是内存,StringIO和BytesIO的使用和文件使用差不多。

    • str写入StringIO:
    >>> from io import StringIO
    >>> f = StringIO()
    >>> f.write('hello')
    5
    >>> f.write(' ')
    1
    >>> f.write('world')
    5
    >>> print(f.getvalue())
    hello world
    
    • 读取StringIO的str:
    >>> from io import StringIO
    >>> f = StringIO('hello\nworld\n')
    >>> while True:
        line = f.readline()
        if line == '':
            break
        print(line.strip())
    
        
    hello
    world
    >>> 
    
    • 从内存中读取bytes:
    >>> from io import BytesIO
    >>> f = BytesIO()
    >>> f.write('你好'.encode('utf-8'))
    6
    >>> print(f.getvalue())
    b'\xe4\xbd\xa0\xe5\xa5\xbd'
    >>>
    
    • 写入bytes到内存中:
    >>> from io import BytesIO
    >>> f = BytesIO(b'\xe4\xbd\xa0\xe5\xa5\xbd')
    >>> f.read()
    b'\xe4\xbd\xa0\xe5\xa5\xbd'
    >>> 
    

    操作文件和目录

    python操作文件和目录一般用到os,os.path,shutil三个模块。

    • 获取操作系统:
    >>> import os
    >>> os.name
    'nt'
    >>> 
    

    nt:windows系统
    posix:Linux、Unix或Mac OS X

    • 获取环境变量:
    >>> import os
    >>> os.environ.get('PATH')
    'C:\\ProgramData\\Oracle\\Java\\javapath;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python36\\Scripts\\;C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python36\\'
    >>> 
    
    • 获取当前绝对目录:
    >>> import os
    >>> os.path.abspath('.')
    'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python36'
    >>> os.getcwd()
    'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python36'
    >>> 
    
    • 合并目录
    >>> import os
    >>> os.path.join('C:\\user','test.txt')
    'C:\\user\\test.txt'
    
    • 拆分目录
    >>> import os
    >>> os.path.split('C:\\user\\test.txt')
    ('C:\\user', 'test.txt')
    
    • 创建和删除目录
    >>> import os
    >>> os.mkdir('C:\\test') #创建
    >>> os.rmdir('C:\\test') #删除
    >>> 
    
    • 获取文件扩展名
    >>> import os
    >>> os.path.splitext('C:\\test\\test.txt')
    ('C:\\test\\test', '.txt')
    >>>
    
    • 文件重命名
    >>> import os
    >>> os.rename('C:\\test\\test.txt','C:\\test\\test1.txt')
    >>>
    
    • 删除文件
    >>> import os
    >>> os.remove('C:\\test\\test1.txt')
    >>> 
    
    • 复制文件
    >>> import shutil
    >>> shutil.copyfile('C:\\test\\test.txt','C:\\test\\tese1.txt')
    'C:\\test\\tese1.txt'
    >>> 
    
    • 列出当前目录的所有目录
    >>> import os
    >>> [x for x in os.listdir('.') if os.path.isdir(x)]
    ['DLLs', 'Doc', 'include', 'Lib', 'libs', 'Scripts', 'tcl', 'Tools']
    >>> 
    
    • 列出当前目录的指定扩展名文件
    >>> import os
    >>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
    ['test - 副本.py', 'test.py']
    >>> 
    
    • 返回到上级目录
    >>> import os
    >>> import sys
    >>> os.path.dirname(os.getcwd())
    'C:\\Users\\User\\AppData\\Local\\Programs\\Python'
    >>> 
    

    序列化

    把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。

    >>> import pickle
    >>> D = {'length':80,'width':50,'heigh':60}
    >>> pickle.dumps(D)
    b'\x80\x03}q\x00(X\x06\x00\x00\x00lengthq\x01KPX\x05\x00\x00\x00widthq\x02K2X\x05\x00\x00\x00heighq\x03K<u.'
    >>> 
    

    直接写入文件:

    >>> import pickle
    >>> D = {'length':80,'width':50,'heigh':60}
    >>> f = open('C:\\renyanliu\\test.txt','wb')
    >>> pickle.dump(D,f)
    >>> f.close()
    >>> 
    

    如图,写入的数据是一堆看不懂的东西。


    test.txt

    现在我们把数据度出来:

    >>> import pickle
    >>> f = open('C:\\renyanliu\\test.txt','rb')
    >>> d = pickle.load(f)
    >>> f.close()
    >>> d
    {'length': 80, 'width': 50, 'heigh': 60}
    >>> 
    

    又变成熟悉的样子。
    注意:Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据,不能成功地反序列化也没关系。

    异步IO

    多线程解决了多任务的并发执行,而异步IO可以在一个线程中执行几个任务。
    先来了解几个概念:

    协程

    英文名Coroutine,协程在执行过程中,会执行其他的程序,有点像中断,待执行到满足条件后再返回,之后很可能再次跳到之前执行的程序中接着执行。

    • 优势:相对于多线程多进程来说,协程不需要切换线程,节省了cpu切换线程的时间,大大提高了运行效率,也不需要线程的锁机制,不存在变量写冲突。

    Python对协程的支持是通过generator实现的。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。

    举例:

    def consumer():
        r = ''
        while True:
            n = yield r
            if not n:
                return
            print('[CONSUMER] Consuming %s...' % n)
            r = '200 OK'
    
    def produce(c):
        c.send(None)
        n = 0
        while n < 5:
            n = n + 1
            print('[PRODUCER] Producing %s...' % n)
            r = c.send(n)
            print('[PRODUCER] Consumer return: %s' % r)
        c.close()
    
    c = consumer()
    produce(c)
    

    运行结果:

    C:\Users\renyangfar\AppData\Local\Programs\Python\Python35\python.exe D:/python/test.py
    [PRODUCER] Producing 1...
    [CONSUMER] Consuming 1...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 2...
    [CONSUMER] Consuming 2...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 3...
    [CONSUMER] Consuming 3...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 4...
    [CONSUMER] Consuming 4...
    [PRODUCER] Consumer return: 200 OK
    [PRODUCER] Producing 5...
    [CONSUMER] Consuming 5...
    [PRODUCER] Consumer return: 200 OK
    

    produce(c)中通过c.send(None)启动协程,用n来控制运行的次数,r = c.send(n)可以发送带参数的信息给consumer(),在consumer中接着n = yield r这句运行,知道再次运行到这条语句,返回到produce(c)中。直到运行完毕用c.close()关闭协程。

    函数式编程

    函数式编程(Functional Programming)与函数编程是两个不同的概念。函数编程允许变量,是有副作用的;纯函数式编程没有变量,对应的输出是确定的,是没有副作用的。Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

    函数参数用法

    python中函数有四类参数-必选参数、默认参数、可变参数、关键字参数。

    • 必选参数

    定义函数时直接定义参数:

    def sum(a,b):
           return a + b
    

    调用函数时需要给出参数:

    sum(1,2)
    
    • 默认参数

    定义函数时给默认参数赋值,赋值参数要求不可变:

    def sum(a,b = 2):
           return a + b
    

    调用函数时可以不给默认参数,默认参数若为多个,只要给出参数名可以不按定义顺序赋值:

    sum(1)
    
    • 可变参数

    定义函数时参数前加‘*’:

    def sum(*a):
           sum = 0
           for i in a:
               sum = sum + i
           return sum
    

    调用函数时给出一个列表(list)或元组(tuple):

    sum(1,2) 
    

    或者:

    a = [1,2]
    sum(*a)
    
    • 关键字参数

    定义函数时参数前加‘**’:

    def printSum(**ps):
       print ps
    

    调用函时给出任意个关键字参数:

    printSum(a = 1,b = 2,sum = 1+2) 
    

    或者给出字典(dict):

    ps = {'a':1,'b':2,'sum':1+2}
    printSum(**ps)
    

    小结:

    python函数的参数具有灵活的应用,以上四种参数可以进行组合,但参数的组合需要按照必选参数、默认参数、可变参数、关键字参数的顺序。通过组合参数能实现复杂灵活的函数调用。
    

    汉诺塔递归调用

    • 问题描述
      有三根柱子a、b、c,在a柱子上有n个盘子,盘子由大到小叠放,要求借助b柱子把A柱子上的盘子移到c柱子上,同样由大到小叠放。请用函数输出移动过程。


      汉诺塔
    • 函数实现如下:
    def move(n,a,b,c):
        if n!=0:
            move(n-1,a,c,b)
            print 'move',a,'->',c
            move(n-1,b,a,c)
    
    • 分析
      先不要考虑函数的实现细节,从移动盘子的思路去分析问题,把move(n,a,b,c)当做是已经实现了函数,能有神奇的力量把n个盘子顺利的从a柱子移到c柱子。要把这叠盘子从a柱子移到c柱子,需要先把最大的盘子移到c柱子,可是最大的盘子在最下面,所以需要把上面的盘子移到柱子b,即:move(n-1,a,c,b),然后把最大盘子移到c柱子,即输出:print 'move',a,'->',c。最后利用递归的思想把剩下的b柱子的盘子移到c柱子,即:move(n-1,b,a,c)

    高阶函数

    高阶函数指函数的参数可以是函数,这是由于函数名也是 一个变量。如:

    a = abs
    print a(-10)
    

    把a变量指向abs这个函数,a即拥有了abs函数的功能。
    高阶函数实例如下:

    def add(x,y,z):
      return z(x) + z(y)
    

    调用:
    add(-3,-5,abs)
    结果:8

    返回函数

    python中允许返回一个函数,返回的函数不会立即执行,而是在调用的时候执行。

    def sum_lazy(*args):
        def getSum():
            sum = 0
            for i in args:
                sum = sum + i
            return sum
        return getSum
    
    f = sum_lazy(1,2,3,4)
    f()
    #返回:10
    

    注意:由于函数只会在调用的时候执行,如果函数内用了共同的变量,容易出现问题,如:

    def getFunc():
      func = []
      for i in range(1,4):
          def retFunc():
            return i*i
          func.append(retFunc)
      return func
    
    a,b,c = getFunc()
    a()#返回9
    b()#返回9
    c()#返回9
    

    如果需要使用循环变量,可以在函数内层再新建一个函数,使其与变量进行绑定:

    def count():
        def f(j):
            def g():
                return j*j
            return g
        fs = []
        for i in range(1, 4):
            fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
        return fs
    f1, f2, f3 = count()
    f1()#返回1
    f2()#返回3
    f3()#返回9
    

    匿名函数

    lambda [arguments]: expression
    如:lambda x : x*x
    匿名函数可以赋值给一个变量:y = lambda x : x*x
    调用时:y(5)
    常见的应用:list(map(lambda x : x * x,[1,2,3,4,5]))

    装饰器

    如果不想改变函数的内部结构,而需要在函数前后加某些功能,如打印日志信息,就可以用到装饰器(Decorator),如有一个函数love(who):

    def love(who):
      print('I love %s'%who)
    

    需要在前面加入一条输出语句:I am xx,可以这样实现:

    def log(func):
      def retFunc(*args,**kw):
        print('I am xx')
        return func(*args,**kw)
      return retFunc
    

    借助python的log语法,把log写在函数定义处:

    @log
    def love(who):
      print('I love %s'%who)
    

    如果Decorator本身需要传入参数,如传入text,则需要再加一层函数:

    def log(text):
      def myFunc(func):
        def retFunc(*args,**kw):
          print('I am %s'%text)
          return func(*args,**kw)
        return retFunc
      return myFunc
    
    @log('lucy')
    def love(who):
      print('I love %s'%who)
    
    

    偏函数

    偏函数可以使可以更改默认参数的值,使新的函数具有新的默认参数值。如int('123')默认使用十进制得到123,如需要把默认的十进制改为二进制,则可以利用functools中的partial函数:

    import functools
    int2 = functools.partial(int,base = 2)#定义
    int2('1000')#调用
    

    模块

    • 优点:使用模块可以减少代码的冗余,可维护性强,在不同的模块中可以使用相同的变量名。
    • 命名:如果自己创建的模块名字与系统模块名字有冲突,可以使用包名进行区分,而包名不可以冲突。
    • 引用:系统如何把一个文件夹当成一个包?需要在文件夹下面新建一个init.py的文件,这个文件可以为空。具有包名的模块引用方法:包名+'.'+文件名。如:sys.path。

    使用模块

    标准模块编写如下:

    #!/usr/bin/env python3  #表示可以直接让这个文件在Unix/Linux/Mac上运行
    # -*- coding: utf-8 -*-  #使用标准UTF-8编码
    
    ' a test module '  #模块文档注释,模块代码第一个字符串
    
    __author__ = 'renyangfar' #作者名字
    
    import sys #导入使用的模块
    
    def test():
       print(sys.version)#输出python版本
    
    if __name__=='__main__': #测试运行时成立,当导入到其他模块时不成立
        test()
    

    作用域

    分为公开域(public)和私有域(private)。

    • public
      普通:abc
      特殊:xxx
      其中特殊变量形如:name,anthor
    • private
      _abc
      __abc
      私有变量或函数可以在模块内部使用,不希望其他模块调用,也不暴露出去。

    安装第三方模块

    通过python自带的pip命令安装第三方模块,在命令行输入pip指令,如果报错,则在安装python的时候没有安装pip指令,需要重新勾选安装pip并勾选加入环境变量。
    安装python图形库:
    pip install Pillow or easy_install Pillow

    常用内置模块

    datetime

    datetime模块是与时间相关的函数,它可以获取时间,时间格式转换,加减时间等。

    • 获取当前时间
    >>> from datetime import datetime
    >>> now = datetime.now()
    >>> print(now)
    2017-09-11 11:40:43.306640
    >>> print(type(now))
    <class 'datetime.datetime'>  
    >>> 
    
    • 获取指定时间
    >>> from datetime import datetime
    >>> date = datetime(2017,9,11,12,00)
    >>> print(date)
    2017-09-11 12:00:00
    >>> 
    
    • datetime转换为timestamp
      在计算机中,时间是以数字表示的(timestamp)。timestamp是以1970年1月1日 00:00:00 UTC+00:00时区的时刻为0的秒数。
    >>> from datetime import datetime
    >>> date = datetime(2017,9,11,12,00)
    >>> datetime.timestamp(date)
    1505102400.0
    >>> date.timestamp()
    1505102400.0
    >>> 
    
    • timestamp转换为datetime
    >>> from datetime import datetime
    >>> t = 1505102400.0
    >>> print(datetime.fromtimestamp(t))
    2017-09-11 12:00:00
    >>> 
    
    • str转换为datetime
    >>> from datetime import datetime
    >>> d = '2017-09-11 12:00:00'
    >>> date = datetime.strptime(d, '%Y-%m-%d %H:%M:%S')
    >>> date
    datetime.datetime(2017, 9, 11, 12, 0)
    >>> print(date)
    2017-09-11 12:00:00
    >>> 
    
    • datetime转换为str
    >>> from datetime import datetime
    >>> now = datetime.now()
    >>> print(datetime.strftime(now,'%Y-%m-%d %H:%M:%S'))
    2017-09-11 14:53:18
    >>>
    
    • datetime加减
    >>> from datetime import datetime,timedelta
    >>> now = datetime.now()
    >>> now
    datetime.datetime(2017, 9, 11, 16, 25, 54, 824218)
    >>> now - timedelta(hours = 10)
    datetime.datetime(2017, 9, 11, 6, 25, 54, 824218)
    >>> now + timedelta(days = 2)
    datetime.datetime(2017, 9, 13, 16, 25, 54, 824218)
    >>> now + timedelta(minutes = 2,seconds = 8)
    datetime.datetime(2017, 9, 11, 16, 28, 2, 824218)
    >>> 
    
    • 本地时间转换为UTC时间与时区转换略

    collections

    • namedtuple
      namedtuple继承自tuple类,具有tuple的属性,同时可以给数据定义名称,访问数据时可以以.直接访问。
    >>> from collections import namedtuple
    >>> Point = namedtuple('Point',['x','y'])
    >>> P = Point(2,3)
    >>> P.x
    2
    >>> P.y
    3
    >>> 
    
    • deque
      把list等线性存储的队列或栈转成双向列表存储的结构,提高修改元素效率。
    >>> from collections import deque
    >>> d = deque([1,2,3])
    >>> d.append(3)
    >>> d
    deque([1, 2, 3, 3])
    >>> d.appendleft(0)
    >>> d
    deque([0, 1, 2, 3, 3])
    >>> d.pop()
    3
    >>> d
    deque([0, 1, 2, 3])
    
    • defaultdict
      当访问不存在的key时,会抛出keyerrot异常,如果希望返回默认的值,defaultddict就用上了。
    >>> from collections import defaultdict
    >>> myDict = defaultdict(lambda:'N/A')
    >>> myDict[1] = 'first'
    >>> myDict[1]
    'first'
    >>> myDict[2]
    'N/A'
    >>> 
    

    当访问不存在的key时,返回lambda:'N/A'返回值,可以用其他函数代替。

    • OrderedDict
      dict里面的元素是无序的,如果需要按顺序排列dict中的元素,可以用OrderedDict模块。
    >>> from collections import OrderedDict
    >>> d = {'a':1,'b':2,'c':3}
    >>> d
    {'a': 1,'c': 3, 'b': 2,}
    >>> OrderedDict({'a':1,'b':2,'c':3})
    OrderedDict([('a', 1), ('b', 2), ('c', 3)])
    >>> d1 = OrderedDict()
    >>> de['x'] = 1
    >>> d1['x'] = 1
    >>> d1['y'] = 2
    >>> d1['z'] = 3
    >>> list(d1.keys())
    ['x', 'y', 'z']
    >>> 
    

    OrderedDict是按添加的顺序排列的,它继承自dict。

    >>> isinstance(d1,dict)
    True
    >>> 
    

    OrderedDict可以实现先进先出的dict,当数量超出限制时,会删除最先添加的元素。

    from collections import OrderedDict
    
    class LastUpdatedOrderedDict(OrderedDict):
    
        def __init__(self, capacity):
            super(LastUpdatedOrderedDict, self).__init__()
            self._capacity = capacity
    
        def __setitem__(self, key, value):
            containsKey = 1 if key in self else 0
            if len(self) - containsKey >= self._capacity:
                last = self.popitem(last=False)
                print('remove:', last)
            if containsKey:
                del self[key]
                print('set:', (key, value))
            else:
                print('add:', (key, value))
            OrderedDict.__setitem__(self, key, value)
    

    运行一下代码:

    import OrderedDict
    
    od = OrderedDict.LastUpdatedOrderedDict(3)
    od[1] = 'a'
    od[2] = 'b'
    od[3] = 'c'
    print(od)
    od[4] = 'd'
    print(od)
    

    结果为:

    C:\Users\User\AppData\Local\Programs\Python\Python36\python.exe C:/renyanliu/python/test.py
    add: (1, 'a')
    add: (2, 'b')
    add: (3, 'c')
    LastUpdatedOrderedDict([(1, 'a'), (2, 'b'), (3, 'c')])
    remove: (1, 'a')
    add: (4, 'd')
    LastUpdatedOrderedDict([(2, 'b'), (3, 'c'), (4, 'd')])
    
    Process finished with exit code 0
    
    • Counter
      Counter可以统计字符出现的次数,它继承自dict。
    >>> from collections import Counter
    >>> c = Counter()
    >>> for ch in 'I like Programmers':
        c[ch] = c[ch] + 1
    
        
    >>> c
    Counter({'r': 3, ' ': 2, 'e': 2, 'm': 2, 'I': 1, 'l': 1, 'i': 1, 'k': 1, 'P': 1, 'o': 1, 'g': 1, 'a': 1, 's': 1})
    >>> 
    

    或者:

    >>> Counter('Programmers')
    Counter({'r': 3, 'm': 2, 'P': 1, 'o': 1, 'g': 1, 'a': 1, 'e': 1, 's': 1})
    >>> 
    

    base64

    base64可以把二进制数据转为字符。base64定义了一个有64个字符的数组,默认为:[A,B...a,b...1,2,3...+,/]。它把24位二进制(3个字符)数据分为4组,每组6位二进制数,然后把对应的数字在定义好的数组中查找,比对出的字符即为编码后的字符。

    >>> import base64
    >>> base64.b64encode(b'Hello Wrold')
    b'SGVsbG8gV3JvbGQ='
    >>> base64.b64decode(b'SGVsbG8gV3JvbGQ=')
    b'Hello Wrold'
    >>> 
    

    如果被编码的二进制数据对应的字符不是3的倍数,则编码出来的字符依然是4的倍数,它会自动在末尾补1个或2个‘=’,表示补的字节数,解码的时候会自动去掉。
    由于url中的‘+’和‘/’会造成歧义,所有还有一种url safe的base64编码:

    >>> base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
    b'abcd++//'
    >>> base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
    b'abcd--__'
    >>> base64.urlsafe_b64decode('abcd--__')
    b'i\xb7\x1d\xfb\xef\xff'
    

    它会把'+'变成'-',把'/'变成'_'。

    struct

    在python中,struct模块可以把二进制str与字节数组进行相互转换。

    • 二进制str——>字节数组(bytes):pack
    >>> import struct
    >>> struct.pack('>I', 10240099)
    b'\x00\x9c@c'
    
    • 字节数组(bytes)——>二进制str:unpack
    >>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
    (4042322160, 32896)
    

    I:4字节无符号整数
    H:2字节无符号整数。

    hashlib

    摘要算法:

    又称哈希算法,散列算法;通过函数把任意长度的数据转换为长度固定的数据(16进制,二进制),一般不可逆,一般用在敏感信息加密上,如用户登陆密码,保存的不是原密码,而是转换的摘要。

    分类:MD5,SHA1,SHA256,SHA512,CRC等。
    • MD5:
      产生16字节(128位)的校验值,一般用32位十六进制数表示。速度快,安全性高,被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。
    • SHA:
      SHA1为20字节(160位)、SHA256为32字节(256位)、 SHA384为48字节(384位)、SHA512为64字节(512位);产生的摘要长度更长,更安全,但转换速度就相对较慢。主要应用于CA和数字证书中。
    • CRC:
      出现时间较长,产生一个4字节(32位)的校验值,一般是以8位十六进制数。特点:简便,速度快,多用于文件校验,如简单文件校验(Simple File Verify – SFV),检测文件的完整性。

    hashlib提供了几种常用的摘要算法,如MD5,SHA1等等。

    • hashlib MD5
    >>> import hashlib
    >>> md5 = hashlib.md5()
    >>> md5.update('today is goog day'.encode('utf-8'))
    >>> md5.update('so lets learn python haha'.encode('utf-8'))
    >>> md5.hexdigest()
    'f766659f2862e8d4b3db9853c9e7dedb'
    >>>  
    
    • hashlib SHA1
    >>> import hashlib
    >>> sha1 = hashlib.sha1()
    >>> sha1.update('today is goog day'.encode('utf-8'))
    >>> sha1.update('so lets learn python haha'.encode('utf-8'))
    >>> sha1.hexdigest()
    '2443a8c66f2098996114fb4a589e32fb82e070d1'
    >>> 
    

    itertools

    itertools提供了对操作可迭代对象的方法,返回一个Iterator。

    • count(x):返回从x开始的步长为1的Iterator
    >>> import itertools
    >>> count1 = itertools.count(1)
    >>> for i in count1:
        print(i)
    
        
    1
    2
    3
    ...
    
    • cycle():把传入的序列无线重复下去
    >>> import itertools
    >>> C = itertools.cycle('AB')
    >>> for n in C:
        print(n)
    
        
    A
    B
    A
    B
    A
    B
    A
    
    • repeat():无限循环一个元素,也可以传入循环次数。
    >>> import itertools
    >>> R = itertools.repeat('A',3)
    >>> for i in R:
        print(i)
    
        
    A
    A
    A
    >>> 
    
    • takewhile(predicate, iterable) :用条件截取itertor中的一段
    >>> import itertools
    >>> C = itertools.count(1)
    >>> takew = itertools.takewhile(lambda x:x<5,C)
    >>> for n in takew:
        print(n)
    
        
    1
    2
    3
    4
    >>> 
    
    • chain(*iterables) :可以将几个序列当作重组的单个序列
    >>> import itertools
    >>> I = itertools.chain([1,2,3],'ABC')
    >>> for i in I:
        print (i)
    
        
    1
    2
    3
    A
    B
    C
    >>> 
    
    • groupby(iterable, key=None) :把迭代器中相邻的重复元素挑出来放在一起
    >>> for key, group in itertools.groupby('AAABBBCCAAA'):
    ...     print(key, list(group))
    ...
    A ['A', 'A', 'A']
    B ['B', 'B', 'B']
    C ['C', 'C']
    A ['A', 'A', 'A']
    

    图像处理

    PIL:Python Imaging Library是python处理图像的标准库,仅支持python2.7。pyhton3用到的库是Pillow。
    安装:pip install pillow

    from PIL import Image
    
    # 打开一个jpg图像文件,注意是当前路径:
    im = Image.open('dog.jpg')
    # 获得图像尺寸:
    w, h = im.size
    print('Original image size: %sx%s' % (w, h))
    # 缩放到50%:
    im.thumbnail((w // 2, h // 2))
    print('Resize image to: %sx%s' % (w // 2, h // 2))
    # 把缩放后的图像用jpeg格式保存:
    im.save('thumbnail.jpg', 'jpeg')
    
    • 模糊效果:
    from PIL import Image,ImageFilter
    
    im = Image.open('dog.jpg')
    im2 = im.filter(ImageFilter.BLUR)
    im2.save('dog_blur.jpg','jpeg')
    

    原始图:


    dog.jpg
    dog_blur.jpg
    • 绘图(生成字母验证图片)
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    
    import random
    
    # 随机字母:
    def rndChar():
        return chr(random.randint(65, 90))
    
    # 随机颜色1:
    def rndColor():
        return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))
    
    # 随机颜色2:
    def rndColor2():
        return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
    
    # 240 x 60:
    width = 60 * 4
    height = 60
    image = Image.new('RGB', (width, height), (255, 255, 255))
    # 创建Font对象:
    font = ImageFont.truetype('Arial.ttf', 36)
    # 创建Draw对象:
    draw = ImageDraw.Draw(image)
    # 填充每个像素:
    for x in range(width):
        for y in range(height):
            draw.point((x, y), fill=rndColor())
    # 输出文字:
    for t in range(4):
        draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())
    # 模糊:
    image = image.filter(ImageFilter.BLUR)
    image.save('code.jpg', 'jpeg')
    
    code.jpg

    图形界面

    • Tkinter
      Tkinter时python内置模块,无需安装任何包。
    from tkinter import *
    import tkinter.messagebox as messagebox
    
    class Application(Frame):
        def __init__(self, master=None):
            Frame.__init__(self, master)
            self.pack()
            self.createWidgets()
    
        def createWidgets(self):
            self.nameInput = Entry(self)
            self.nameInput.pack()
            self.alertButton = Button(self, text='Hello', command=self.hello)
            self.alertButton.pack()
    
        def hello(self):
            name = self.nameInput.get() or 'world'
            messagebox.showinfo('Message', 'Hello, %s' % name)
    
    app = Application()
    # 设置窗口标题:
    app.master.title('Hello World')
    # 主消息循环:
    app.mainloop()
    

    面向对象编程

    自然界的一切都可以看做对象,如苹果,葡萄,香蕉等水果;长方形,正方形,圆形等图形;我们可以把这些事物进行归类,如水果,他们都有颜色,大小等属性,我们可以吃水果,存储水果等动作。在面向对象编程中,具有相同属性名称和方法我们叫做类,具体的东西称作实例。

    • 类:创建实例的模板,它封装了数据和方法,具有通用性。如要管理学生的信息,学生有姓名,性别,成绩等相关属性,这些属性名称都是一样的,因此具有通用性。而输出学生的姓名,成绩这些动作也有一样的,也具有通用性。不同的是具体的属性,如每个学生都有自己唯一的名字,成绩等。
    • 实例:根据类来创建,是具体的,唯一的。如创建学生“小明”这个实例,一旦创建,“小明”就拥有学生这个类的属性和方法。
    • 举例:
    class Student(object):  #类
        def __init__(self, name, score):
            self.name = name
            self.score = score
    
        def print_score(self):
            print('%s: %s' % (self.name, self.score))
    
    xiaoming = Student('xiaoming',90)  #实例
    
    xiaoming.print_score() #调用实例的方法
    

    私有方法和属性

    不想暴露给外部的变量和方法可以在前面加两个下划线__,如变量__name,方法__print_score(self),这样其他模块就不能访问了。这种私有变量和方法一般是只限内部调用时使用,防止外部随意修改,保证安全性。如果需要访问私有变量和方法,可以在模块内部创建set()和get()方法。

    class Student(object):  #类
        def __init__(self, name, score):
            self.__name = name  #__name变成私有变量
            self.score = score
    
        def __print_score(self):
            print('%s: %s' % (self.name, self.score))
    
        def getName(self):  #创建get方法,获取名字
          return self.__name
    
        def setName(self,name):  #创建set方法,设置名字
          self.__name = name
    
    xiaoming = Student('xiaoming',90)  #实例
    
    xiaoming.getName()  #调用get方法
    xiaoming.setName('cuihua')  #调用set方法
    xiaoming.getName()  #查看是否设置成功
    xiaoming.__print_score() #调用私有方法,会报错
    

    继承和多态

    • 继承
      子类继承父类后子类就拥有父类的全部属性和方法:
    class Animals():  #父类
      def run(self):
        print('Animal is running')
     
    class Dog(Animals): #子类继承父类
      pass
    
    dog = Dog()  #实例化
    dog.run()  #调用继承过来的方法
    

    子类也可以重写父类的方法:

    class Animals():  #父类
      def run(self):
        print('Animal is running')
     
    class Dog(Animals): #子类继承父类
      def run(self):  #重写父类的方法
        print('Dog is running')
    
    dog = Dog()  #实例化
    dog.run()  #调用继承过来的方法
    
    • 多态
      子类继承了父类,子类就属于父类,在需要父类做参数的方法中,子类也可以做参数。
    def runTwice(animals):
      animals.run()
      animals.run()
    
    runTwice(Animals()) #调用
    runTwice(Dog())
    
    • 静态语言与动态语言
      在python中,只要该类中存在run()方法,都是可以把此类作为参数传入runTwice()方法中,此类称为:鸭子类型。而在Java中,必须传入Animals或继承于Animals的类。

    获取对象信息

    获取对象信息的方法这里介绍有三种:

    • type
    • isinstance
    • dir

    type

    type('abc') #返回:<class 'str'>
    type(123) #返回:<class 'int'>
    type(abs) #返回:<class 'builtin_function_or_method'>
    type('abc')==str #返回:True
    

    使用types模块中的常量判断是否为函数:

    import types
    def sum():
      return 1 + 2
    
    type(sum)==types.FunctionType #返回True
    type(abs)==types.BuiltinFunctionType #返回True
    type(lambda x: x)==types.LambdaType #返回True
    type((x for x in range(10)))==types.GeneratorType #返回True
    

    isinstance

    使用isinstance判断:

    isinstance('abc',str)
    isinstance(123,int)
    isinstance(123,(int,str))
    

    也可以判断一个类:

    class Animals():  #父类
      def run(self):
        print('Animal is running')
     
    class Dog(Animals): #子类继承父类
      pass
    
    dog = Dog()  #实例化
    isinstance(dog,Animals) #返回True
    isinstance(dog,Dog) #返回True
    

    dir

    使用dir返回一个list,这个list包含全部的属性和方法。

    dir(Dog)  #Dog()见isinstance
    

    返回:

    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'run']
    

    由上可见:除了run方法,还有很多以'__'开头和结尾的特殊方法。
    配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:

    hasattr(Dog,'run')      #True
    getattr(Dog,'run')     #<function Animals.run at 0x0000000002F041E0>
    setattr(Dog,'a',10)   #增加一个属性
    getattr(Dog,'a')   #获取刚增加的属性
    
    getattr(Dog,'b')   #无此属性,会报错
    getattr(Dog,'b',5)   #增加默认  运行正常
    

    slots

    python是一种动态语言,可以在类定义之后,增加属性或方法:

    • 增加属性:
    class calcu():  #创建一个类
      def sum(self,a,b):
        return a + b
    
    myCalcu = calcu()
    myCalcu.c = 5   #增加一个属性
    myCalcu.c   #返回 5 
    
    • 增加方法:
      通过types模块的MethodType可以增加方法:
    def multi(self,a,b):  #定义一个方法
       return a * b
    from types import MethodType
    myCalcu.multi = MethodType(multi,myCalcu)  #给实例增加方法
    myCalcu.multi(2,3)   #调用方法
    

    这样增加的方法只能针对当前实例,对于类是无效的。如果想增加类的方法和属性,可以直接条用类:

    calcu.d = 5    #增加类的属性
    calcu.d   #返回5
    
    def divide(self,a,b):
        return a / b
    
    calcu.divide= divide  #给实例增加方法
    myCalcu2 = calcu()
    myCalcu2.divide(4,2)
    
    • 固定绑定的属性和方法,在定义类时使用slots进行限定:
    class calcu():  
      __slots__ = ('e','f')   #__slots__绑定
      def sum(self,a,b):
        return a + b
    myCalcu3 = calcu()
    myCalcu3.e = 6  
    myCalcu3.g = 7   #报错
    

    注意:使用__slots__仅对当前类起作用,对子类无效

    @property

    @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
    

    调用:

    >>> s = Student()
    >>> s.score = 89
    >>> s.score
    89
    >>> 
    

    通过property的就可以达到和get,set一样的效果,且调用简单方便。

    多重继承

    python允许一个类继承多个类,这样这个类就拥有了几个类的属性和方法,如:

    class Runnable():
      def run(self):
        print('I can run')
    
    class Mammal():
      def mamal(self):
        print('I am make baby')
    
    class Dog(Runnable,Mammal):  #多重继承
      pass
    

    定制类

    类中默认有些以'__'开头和结尾的特殊方法和属性,如__str__,__repr__,__len__,他们能完成特定的用途。

    • __str__
    class Helloworld():
      pass
    

    看看直接打印这个类会得到什么:

    >>> print(Helloworld())
    <__main__.Helloworld object at 0x0000000002F065C0>
    

    返回一串带地址的字符串。那如果想要返回一些能看懂的信息呢?就需要用到__str__这个特殊方法。

    class Helloworld():
      def __str__(self):
        return 'Hello World'
    

    打印:

    >>> print(Helloworld())
    Hello World
    

    如果去掉print函数:

    >>> Helloworld()
    <__main__.Helloworld object at 0x0000000002F065C0>
    

    打印的和不添加__str__Helloworld()是一样样的,其实在函数内部它打印的是__repr__这个特殊方法。修改这个方法:

    class Helloworld():
      def __str__(self):
        return 'Hello World'
      __repr__ = __str__
    

    打印:

    >>> Helloworld()
    Hello World
    

    结果打印的就是__str__返回的字符串。

    • __iter__
      通过__iter__方法可以让一个类使用for···in···循环。它返回一个迭代对象,然后循环调用__next__方法,直到遇到StopIteration错误时退出循环。
    class counter():
      def __init__(self):
        self.a = 0
    
      def __iter__(self):
        return self
    
      def __next__(self):
        self.a = self.a + 1
        if(self.a == 10):
          raise StopIteration()
        return self.a
    

    使用for循环看看效果:

    >>> for i in counter():
        print(i)
    
        
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    • __getitem__
      如果想获取类循环中某一个特定的值,如counter()[5],__getitem__()方法能实现这个功能:
    class counter():
      def __init__(self):
        self.a = 0
    
      def __getitem__(self,n):
        return n
    

    测试:

    >>> counter()[5]
    5
    >>> 
    

    如果需要类提供切片等其他功能,借助动态类的功能依旧可以实现。

    • __getattr__
      调用类中不存在的属性或方法一般会返回异常:
    class Hello():
      def __init__(self):
        self.name = 'hello'
    

    调用属性:

    >>> Hello().name
    'hello'
    >>> Hello().sex  #返回异常
    Traceback (most recent call last):
      File "<pyshell#202>", line 1, in <module>
        Hello().sex
    AttributeError: 'Hello' object has no attribute 'sex'
    >>> 
    

    'getattr`就是可以调用为定义的属性和方法的。

    class Hello():
      def __init__(self):
        self.name = 'hello'
    
      def __getattr__(self,attr):
        if attr == 'sex':
          return 'sex'
        return 'error attr'
    

    再次调用:

    >>> Hello().sex
    'sex'
    >>> Hello().max
    'error attr'
    >>> 
    

    调用sex属性是有返回,而调用max属性时,返回'error attr'。如果没有return 'error attr',则不报异常,返回为none
    注意:除了调用属性,也可以调用方法;如果类中已存在属性或方法,就不会去调用__getattr__中的属性方法。

    枚举

    枚举类通常用在固定的数据中,如每年的十二个月,一个星期的周几等,相对于使用变量,枚举类型可以让数据变得安全,也可以更方便的访问。
    可以这样创建一个Enum:

    from enum import Enum
    
    Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
    

    通常,会创建一个class:

    from enum import Enum
    
    class Color(Enum):
      Red = 'red'
      Green = 'green'
      Black = 'black'
    

    访问:

    >>> Color.Red
    <Color.Red: 'red'>
    >>> print(Color.Red)
    Color.Red
    >>> print(repr(Color.Red))
    <Color.Red: 'red'>
    >>> for color in Color:
        print(color)
    
        
    Color.Red
    Color.Green
    Color.Black
    >>>
    
    ### 元类
    
    理解:在python中一切皆为对象。
    元类就是创建类的类,元类也是对象,它可以创建类,所以类也是元类的实例。通俗理解:元类——>类——>实例。
    通过元类可以动态的创建一个类,而不需要先定义,再使用。
    通常,type用来查看对象的属性,元类也使用type关键字,type就是创建元类的类。创建格式:
    ```python
    type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
    

    创建一个Hello类:

    def oHello(self):
      print('Hello')
    
    Hello = type('Hello',(object,),{'oHello':oHello})
    

    使用元类创建的类与普通类一样:

    >>> h = Hello()
    >>> h.oHello()
    Hello
    

    除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。由于metaclass很少用到,作者水平有限,无法解释。可以访问:
    深刻理解Python中的元类(metaclass)

    进程和线程

    多进程

    • 使用multiprocessing的Process类创建子进程:
    from multiprocessing import Process
    import os
    
    #在子进程中执行的代码
    def createProcessing(name):
        print('this is a child process:%s(%d)' %(name,os.getpid()))
    
    if __name__ == '__main__':
        print('this is a parent process:%d' %os.getpid())
        #创建子进程,传函数名和参数
        P = Process(target= createProcessing,args=('test',))
        print('start child process')
        #打开子进程
        P.start()
        #主进程与子进程的同步
        P.join()
        print('Child process END!!!')
    

    Processing能单独的创建子进程,如果需要启动很多子进程呢?

    • Pool
      Pool能同时启动多个进程。
    from multiprocessing import Pool
    import time
    import os
    
    def createProcess(name):
        print('Child process start:%s(%d)' %(name,os.getpid()))
        time.sleep(0.2)
        print('Child process end:%s(%d)' % (name, os.getpid()))
    
    if __name__ == '__main__':
        print('Parent process :%d' %os.getpid())
        P = Pool(processes=4)  #同时跑4个进程,默认电脑内核数
        for i in range(5):
            P.apply_async(createProcess,args=(i,))
        print('Waiting for all subprocess done...')
        P.close()#添加进所有的子进程并启动子进程
        P.join()#同步
        print('END')
    
    • 子进程
      有时候需要启动带输入输出的外部子进程,并获得返回码。可以使用subprocess模块。
      建议使用run()方法,run()方法是从python3.5开始设立的。更高级的可以使用Popen接口。
      subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, encoding=None, errors=None)
      举例:
    import subprocess
    
    print('$ nslookup')
    p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
    print(output.decode('utf-8'))
    print('Exit code:', p.returncode)
    

    上面的代码相当于在命令行执行命令nslookup,然后手动输入:

    set q=mx
    python.org
    exit
    
    • 进程间通信
      python提供Queue、Pipes等多种方式来交换数据。我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
    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.
    

    子线程

    同时执行多条任务不仅可以开启多进程,也可以在进程中开启子线程。
    threading模块可以创建子线程。

    import threading
    import time
    
    def loop():
        print('%s is running...'% threading.current_thread().name)
        time.sleep(2)
        print('%s is END...' % threading.current_thread().name)
    
    print('%s is running...'% threading.current_thread().name)  #主线程开始
    t = threading.Thread(target= loop,name = 'loopthread')  #创建子线程
    t.start()  #开始子线程
    t.join()  #同步
    print('%s is END...'% threading.current_thread().name)
    

    运行如下:

    C:\Users\renyangfar\AppData\Local\Programs\Python\Python36\python.exe F:/Python/PythonStudy/Project/Thread.py
    MainThread is running...
    loopthread is running...
    loopthread is END...
    MainThread is END...
    

    开启的主线程名字为:MainThread,在开启子线程时可以提供子线程名称,如:loopthread,如果不提供,默认为:Thread-1,Thread-2,Thread-2...

    • lock
      多线程可以同时访问同一个变量,这样会造成变量的不可预测性。如:
    import time, threading
    
    # 假定这是你的银行存款:
    balance = 0
    
    def change_it(n):
        # 先存后取,结果应该为0:
        global balance
        balance = balance + n
        balance = balance - n
    
    def run_thread(n):
        for i in range(100000):
            change_it(n)
    
    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)
    

    结果不一定是0。
    而要想在同一个时刻只有一个子线程可以访问balance 变量,需要加一把锁lock。

    balance = 0
    lock = threading.Lock()
    
    def run_thread(n):
        for i in range(100000):
            # 先要获取锁:
            lock.acquire()
            try:
                # 放心地改吧:
                change_it(n)
            finally:
                # 改完了一定要释放锁:
                lock.release()
    

    这样,当几个现场执行到lock.acquire()时,只有一个线程能获取到。其他的线程要继续等待,知道获得锁的线程释放锁lock.release()

    另外:Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。

    ThreadLocal

    多线程中,如果一个属性需要绑定一个线程,线程在调用自己的属性。python中的Thread Local很好的解决了这样问题。假设每个线程管理一种花,需要设置花的颜色并打印出来。

    import threading
    flower = threading.local()  #folwer绑定了线程
    
    def print_flower():
        clor = flower.clor #直接调用
        print('%s\'s clor is %s'% (threading.current_thread().name,clor)) 
    
    def manager_flower(clor):
        flower.clor = clor  
        print_flower()
    
    t1 = threading.Thread(target= manager_flower,args=('RED',),name = 'rose')
    t2 = threading.Thread(target= manager_flower,args=('BLUE',),name = 'lavender')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    

    运行结果:

    C:\Users\renyangfar\AppData\Local\Programs\Python\Python36\python.exe F:/Python/PythonStudy/Project/ThreadLocal.py
    rose's clor is RED
    lavender's clor is BLUE
    

    程序中flower是在全局变量定义的,但是它会根据不同的子线程而有不同的属性。

    web学习

    基于python开发web的框架有很多,简要介绍几种常用的框架:

    • Flask
      轻量级框架,适合初学者学习,只提供常用的核心组件,可以自定义模块。开发的网站:果壳网
    • Django
      全能型框架,能开发大型网站,但涉及知识广,自带Admin管理后台,可重用模块众多。
    • Tornado
      高性能框架,支持异步 处理的功能,可以做长连接。开发的网站:知乎
    • 还有web.py web2.py bottle Pyramid等。

    涉及知识:

    • WSGI:Web服务网关接口(Web Server Gateway Interface,简称“WSGI”),是一种在Web服务器 和Python Web应用程序或框架之间的标准接口。
    • WSBI服务器:
      1:Gunicorn:是一个纯Python WSGI服务器,Gunicorn是如今新Python web应用程序的推荐选择。
      2:Waitress:Waitress 是一个纯Python WSGI服务器,声称具备“非常可接受的性能”。

    网络编程

    网络通信:两个进程间的通信
    进程确定:IP地址+端口号

    TCP客户端

    TCP是可靠传输协议,需要先建立连接,然后传输数据,最后释放连接。主动发起连接的是客户端,等待连接的是服务器。

    建立连接

    要建立连接,需要创建一个socket,而常见socket,需要知道目标计算机的ip地址和端口号,还需要指定协议类型。

    import socket
    
    # 创建一个socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 建立连接
    s.connect(('www.baidu.com', 80))
    

    socket.AF_INET表示IPv4协议,如果使用IPv6,则为socket.AF_INET6socket.SOCK_STREAM表示使用面向流的TCP协议。
    建立连接时制定ip地址和端口号,域名服务器会把'www.baidu.com'自动转为ip地址,80是web服务器默认的端口。SMTP服务是25端口,FTP服务是21端口。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。

    发送请求

    连接建立后,就可以请求数据了。

    # 发送数据:
    s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
    

    接受数据

    # 接收数据:
    buffer = []
    while True:
        # 每次最多接收1k字节:
        d = s.recv(1024)
        if d:
            buffer.append(d)
        else:
            break
    data = b''.join(buffer)
    
    关闭连接
    # 关闭连接:
    s.close()
    

    获取到数据后,根据http协议即可获取服务器返回的数据,包括网页本身和http头。

    header, html = data.split(b'\r\n\r\n', 1)
    print(header.decode('utf-8'))
    # 把接收的数据写入文件:
    with open('baidu.html', 'wb') as f:
        f.write(html)
    

    TCP服务器

    创建socket

    服务器接收客户端的请求,所以需要先建立一个socket

    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    

    绑定ip地址和端口号

    s.bind(('127.0.0.1',9999))
    

    其中127.0.0.1为本机地址,只有运行在本机的客户端才可以访问。另外每台电脑可以有几块网卡,多个ip地址,可以用0.0.0.0绑定到所有的网络地址。

    运行监听客户端访问

    s.listen(5)
    

    5表示最多接受5个客户端连接。

    服务器程序

    while True:
        sock,addr = s.accept()
        t = threading.Thread(target= clent_connec,args=(sock,addr))
        t.start()
    

    while True:永久运行服务器,知道用ctrl+c关闭服务。s.accept()会等待连接客户端,返回客户端的socket和address。因为每个服务可以同时接受好几个客户端请求,所以用多线程或多进程接受请求。

    def clent_connec(sock, addr):
        print('start new client named %s' % sock)
        sock.send(b'hello')
        while True:
            data = sock.recv(1024)
            time.sleep(1)
            if not data or data.decode('utf-8') == 'exit':
                break
            sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
        sock.close()
    

    在服务器响应程序中,先返回“hello”给客户端,接下来接受客户端数据并在数据前加”hello“返回给客户端,最后关闭socket。

    UDP

    UDP不同于TCP,不需要建立连接,客户端直接可以向服务器传输数据,数据传输快,但不保证传输的有效性。
    服务器端程序:

    import socket
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    s.bind(('127.0.0.1',9999))
    while True:
        data,addr = s.recvfrom(1024)
        print('receive data form client based udp:%s' %data,addr)
        s.sendto(b'hello,%s'%data)
    

    客户端程序:

    import socket
    
    s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    l = [b'good',b'ok',b'thanks']
    for i in l:
        s.sendto(i,('127.0.0.1',9999))
        print(s.recv(1024).decode('utf-8'))
    
    s.close()
    

    Flask学习笔记

    前言

    • 特点:保持核心,简单而易于扩展。数据库集成、表单验证、上传处理、各种各样的开放认证技术等需要扩展实现。对pyton2的支持优于python3
    • Flask依赖两个库- Jinja2 模板引擎和 Werkzeug WSGI 工具集。
      WSGI:在 Web 应用和多种服务器之间的标准 Python 接口
      Jinja2:负责渲染模板

    安装Flask

    打开cmd,运行:

    pip install flask
    

    上代码:

    from flask import Flask
    from flask import request
    
    app = Flask(__name__)
    
    @app.route('/', methods=['GET', 'POST'])
    def home():
        return '<h1>Home</h1>'
    
    @app.route('/signin', methods=['GET'])
    def signin_form():
        return '''<form action="/signin" method="post">
                  <p><input name="username"></p>
                  <p><input name="password" type="password"></p>
                  <p><button type="submit">Sign In</button></p>
                  </form>'''
    
    @app.route('/signin', methods=['POST'])
    def signin():
        # 需要从request对象读取表单内容:
        if request.form['username']=='admin' and request.form['password']=='password':
            return '<h3>Hello, admin!</h3>'
        return '<h3>Bad username or password.</h3>'
    
    if __name__ == '__main__':
        app.run()
    

    运行结果:

    * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    

    说明web服务已经启动成功,等待请求:
    于是打开浏览器输入网址:

    http://localhost:5000/
    

    显示:

    image.png

    输入网址:

    http://localhost:5000/signin
    

    显示:


    输入用户名:admin,密码:password,点击sigin in
    显示:

    image.png

    使用模板

    开发web,服务器需要给browser返回页面,如return '<h1>Home</h1>',如果有大量的页面内容需要返回,这样写就很容易出错,普遍的方法是使用模板,当需要返回页面时,返回一套页面模板,这样就把业务逻辑和显示区分开来,加上模型,就是常用的mvc编程模型。
    这里以jinja2模板为例实现上一节的登陆功能:
    先用pip安装jinja2:

    pip install jinja2
    

    在app.py中代码:

    from flask import Flask, request, render_template
    
    app = Flask(__name__)
    
    @app.route('/', methods=['GET', 'POST'])
    def home():
        return render_template('home.html')
    
    @app.route('/signin', methods=['GET'])
    def signin_form():
        return render_template('form.html')
    
    @app.route('/signin', methods=['POST'])
    def signin():
        username = request.form['username']
        password = request.form['password']
        if username=='admin' and password=='password':
            return render_template('signin-ok.html', username=username)
        return render_template('form.html', message='Bad username or password', username=username)
    
    if __name__ == '__main__':
        app.run()
    

    同级目录下新建templates文件夹,并在此文件夹下新建home.html,form.html,signin-ok.html,如图:

    image.png
    • home.html
      用来显示首页的模板:
    <html>
    <head>
      <title>Home</title>
    </head>
    <body>
      <h1 style="font-style:italic">Home</h1>
    </body>
    </html>
    
    • form.html
      用来显示登录表单的模板:
    <html>
    <head>
      <title>Please Sign In</title>
    </head>
    <body>
      {% if message %}
      <p style="color:red">{{ message }}</p>
      {% endif %}
      <form action="/signin" method="post">
        <legend>Please sign in:</legend>
        <p><input name="username" placeholder="Username" value="{{ username }}"></p>
        <p><input name="password" placeholder="Password" type="password"></p>
        <p><button type="submit">Sign In</button></p>
      </form>
    </body>
    </html>
    
    • signin-ok.html
      登录成功的模板:
    <html>
    <head>
      <title>Welcome, {{ username }}</title>
    </head>
    <body>
      <p>Welcome, {{ username }}!</p>
    </body>
    </html>
    

    重新运行程序,可以达到很好的效果。

    相关文章

      网友评论

        本文标题:python学习笔记

        本文链接:https://www.haomeiwen.com/subject/skioyxtx.html