python学习笔记

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>

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

推荐阅读更多精彩内容