Python基础知识笔记

文章目录

Python基础知识

教程:https://www.liaoxuefeng.com/wiki/1016959663602400

内置函数:https://docs.python.org/3/library/functions.html

官方库:https://pypi.org/

理解可迭代对象、迭代器和生成器:https://zhuanlan.zhihu.com/p/82787357

1 Python基础

1.1 数据类型和变量

整数
浮点数
  • 即小数,之所以叫浮点数是因为按照科学计数法表示时,一个浮点数的小数点位置是可变的
  • 1.23e10与12.3e9一样结果
字符串
布尔值
空值
变量
  • 除法
    • /:取全部
    • //:取整数部分
    • %:取余数

1.2 字符串和编码

  • 编码:数字/字符编程字节的过程
  • 解码:字节变成数字/字符的过程

编码方式历史进程:

  • 一开始是ASCII,由美国发明
  • 接下来到各国的编码方式:中国GB2312、日本Shift_JIS、韩国Euc-kr
  • 为了统一各个国家的编码方式,Unicode编码方式诞生
  • 为了解决Unicode占用存储空间太大和传输不方便的问题
  • UTF-8诞生

编码方式解释:https://www.zhihu.com/question/52346583/answer/977492415

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzrx2lmz-1598977365406)(D:\Data\Desktop\image-20200717184859482.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uym5A0w-1598977365408)(D:\Data\Desktop\image-20200717184910019.png)]

  • ord()函数
    • 获取字符的整数表示
    • 获取字符对应的编码的编码号
    • ord(‘A’) ----65
  • chr()
    • 把编码数字转换成对应的字符形式
    • chr(65)-----A

1.3 list和tuple

list
  • 有序的集合,可以随时添加和删除其中的元素
  • 可变的有序表
  • append(obj):添加元素
  • extend(obj):添加元素,会将obj遍历一次才添加到列表中
  • insert(index,obj)
  • pop(index)
tuple
  • 有序列表,元组

  • 不可变有序列表

  • 因为是不可变,所以代码更安全,如果可能,就尽量用tuple代替list

  • 注意点:t = (1)这个输出是为1,为不是(1)

    • 因为这里会有歧义,会认为()是括号来的而不是代表元组,所以需要加逗号
    • t = (1,)

1.4 条件判断

  • if…elif…else
  • round(x [, n])
    • round() 方法返回浮点数x的四舍五入值
    • n为保留小数个数

1.5 循环

  • for…in循环

  • while循环

  • break语句,直接退出循环

    • n = 1
      while n <= 100:
          if n > 10: # 当n = 11时,条件满足,执行break语句
              break # break语句会结束当前循环
          print(n)
          n = n + 1
      print('END')
      
  • continue语句可以提前结束本轮循环

    • n = 0
      while n < 10:
          n = n + 1
          if n % 2 == 0: # 如果n是偶数,执行continue语句
              continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
          print(n)
      

1.6 dict和set

dict
  • key-value形式存储,key为不可变对象
  • in方法判断对象是否存在key中
    • d = {‘tom’:18,‘cat’:11}
    • ‘tom’ in d
  • get(key)方法获取指定的value,返回值为value,没有对应的key则返回None
  • pop(key)删除指定的key和value,返回值为value,没有对应的key则报错
  • keys()
  • values()
  • items()
set
  • set和dict类似,也是一组key的集合,但不存储value,key也不能重复,也为不可变对象

  • 要创建一个set,需要提供一个list作为输入集合:

    • s = set([5,2,3])
      
  • set中的元素不重复,重复元素会自动过滤

    • s = set([1,1,2,3,3,4])
      print(s)
      {1,2,3,4}
      
  • add(key)添加元素

  • remove(key)删除元素

  • set可以看成数学意义上的无序和无重复的集合,所以两个set可以做数学意义上的交集、并集

    • s1 = set([1, 2, 3])
      s2 = set([2, 3, 4])
      print(s1 & s2)   # {2,3}
      print(s1 | s2)	#{1,2,3,4}
      

2 函数

函数就是最基本的一种代码抽象的方式

2.1 内置函数

  • https://docs.python.org/3/library/functions.html

2.2 调用函数

  • int()

  • float()

  • str()

  • bool()

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

    • a = abs	#变量a指向abs函数
      print(a(-1)) # 1,所以也可以通过a调用abs函数
      

2.3 定义函数

  • 函数名+参数+返回值
  • 空函数:pass
  • 如果有必要,可以先对参数的数据类型做检查
  • 函数体内部可以用return随时返回函数的结果,没有return语句时,会自动return None
  • 函数可以同时返回多个值,但其实就是一个tuple

2.4 函数的参数

  • 必须参数(位置参数)、默认参数、可变参数(*args)、关键字参数(**kw)、命名关键字参数

  • 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

  • Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数

  • 默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误

  • 要注意定义可变参数和关键字参数的语法

    • *args是可变参数,args接收的是一个tuple
    • **kw是关键字参数,kw接收的是一个dict
  • 以及调用函数时如何传入可变参数和关键字参数的语法

    • 可变参数既可以直接传入:func(1,2,3),又可以先组装list或tuple,再通过*args方式传入:
      • tuple = (1,2,3)
      • func(*tuple)
    • 关键字参数既可以:func(a=1,b=2),也可以先组装dict,再通过**kw方式传入:
      • dict = {‘a’:1,‘b’:2}
      • func(**dict)
  • 使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法

  • 命名的关键字参数是为了限制调用者传入的参数名,同时可以提供默认值

  • 定义命名的关键字参数再没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数

2.5 递归函数

  • 使用递归函数的有点是逻辑简单清晰,缺点是过深的调用是导致栈溢出

  • 针对尾递归优化的语言可以通过尾递归防止栈溢出。

  • 尾递归事实上适合循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

  • Python 标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

  • 练习:汉诺塔的移动

    • 请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法

    • def move(n, a, b, c):
          if n == 1:
              print(a, '-->', c)
          else:
              move(n-1, a, c, b)	#把前n-1块从柱子A移至柱子B
              print(a, '-->', c)	#把最大块从柱子A移至柱子C
              move(n-1, b, a, c)	#把前n-1块从柱子B移至柱子C
            
      move(3, 'A', 'B', 'C')
      #输入:
      A --> C
      A --> B
      C --> B
      A --> C
      B --> A
      B --> C
      A --> C
      

3 高级特性

一行代码能解决的事情,绝不写5行代码,代码越少,开发效率越高

3.1 切片

  • slice切片操作

  • 去空格函数

    • strip([char]):收尾去除char,不填则去除空格

      • str = '000123456000'
        str.strip('0') #收尾去除0
        # '123456'
        
      • str = "123abcrunoob321"
        print (str.strip( '12' ))  # 字符序列为 12
        # 3abcrunoob3,从结果来看 去除的12字符不分顺序
        
    • ltrip()

    • rtrip()

  • 利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:

    • def trim(s):
          while(s[:1] == ' '):
                  s = s[1:]
          while(s[-1:] == ' '):
                  s = s[:-1]
          return s
      s = '  hello'
      print(s[:1])
      # 测试:
      if trim('hello  ') != 'hello':
          print('测试失败!')
      elif trim('  hello') != 'hello':
          print('测试失败!')
      elif trim('  hello  ') != 'hello':
          print('测试失败!')
      elif trim('  hello  world  ') != 'hello  world':
          print('测试失败!')
      elif trim('') != '':
          print('测试失败!')
      elif trim('    ') != '':
          print('测试失败!')
      else:
          print('测试成功!')
      

3.2 迭代

  • 使用for循环去遍历可迭代对象

  • 判断对象是否是可迭代对象

    • from collections.abs import Iterable
      print(isinstance('abc',Iterable))  # True
      print(isinstance(123,Iterable))		# False
      print(isinstance([1,2,3],Iterable)) # True
      
  • 想通过for循环获取list的下标,使用内置函数enumerate

    • for index,value in enumerate(['a','b','c'])
      	print(index,value)
      # 0 a
      # 1 b
      # 2 c
      
  • for循环同时引用两个变量

    • dict = {'a':1, 'b':2, 'c':3}
      for x,y in dict.items():
      	print(x,y)
      # a 1
      # b 2 
      # c 3
      
  • 请使用迭代查找一个list中最小和最大值,并返回一个tuple:

    • 解题思路:

      • 一开始我没有去认真看题目的需求和认真分析
      • 其实就按结果导向思路来解题最好
      • 题目说只要最大值和最小值,就不要去搞什么排序操作(下面第一种方法就排了序),其实就定义两个变量去接收就好了
      • 做题目之前应该先列出所有参数的可能性,然后围绕这些列出的需要传入的参数去解题
        • 例如:这里是传递L,即可迭代对象,就以list为例,列出空列表,列表中只有一个元素,列表中只有两个元素甚至是多个元素的可能性,然后围绕这些可能性去写答案(即下面代码中先写出测试的内容,然后围绕测试的内容去写答案)
    • # 第一种:未使用for循环,使用while
      # def findMinAndMax(L):
      #     if L == []:
      #         return (None, None)
      #     if len(L) == 1:
      #         return (L[0],L[0])
      #     n = 0
      #     m = 0
      #     while m < len(L)-1:
      #         if L[n] > L[n+1]:
      #             L[n],L[n+1] = L[n+1],L[n]
      #         n += 1 
      #         m += 1 
      #     return (L[0], L[-1])
      
      #第二种:只使用for循环就可以解决
      def findMinAndMax(L):
          if len(L) == 0:
              return (None, None)
          min = L[0]
          max = L[0]
      
          for i in L:
              if i > max:
                  max = i
              if i < min:
                  min = i
      
          return (min, max)
      
      # 测试
      if findMinAndMax([]) != (None, None):
          print('测试失败!')
      elif findMinAndMax([7]) != (7, 7):
          print('测试失败!')
      elif findMinAndMax([7, 1]) != (1, 7):
          print('测试失败!')
      elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9):
          print('测试失败!')
      else:
          print('测试成功!')
      

3.3 列表生成式

  • 单层循环

    • [x * x for x in range(1, 11)]
      
  • 两层循环

    • [m + n for m in 'ABC' for n in 'XYZ']
      
  • if…else

    • [x for x in range(1, 11) if x % 2 == 0]
      
    • 错误写法:

      • [x if x % 2 == 0 for x in range(1, 11)]
        
  • for循环前面是一个表达式,而后面是一个筛选条件

    • 所以在列表生成式for循环前面使用if语句的话要加上else

      • [x if x % 2 == 0 else -x for x in range(1, 11)]
        
    • for循环后面则为筛选条件,使用if不能加上else

      • [x for x in range(1, 11) if x % 2 == 0]
        
  • 练习:

    • 请修改列表生成式,通过添加if语句保证列表生成式能正确地执行:

    • L1 = ['Hello', 'World', 18, 'Apple', None]
      
      # 答案----------------------------
      L2 = [i.lower() for i in L1 if isinstance(i,str)]
      # --------------------------------
      
      # 测试:
      print(L2)
      if L2 == ['hello', 'world', 'apple']:
          print('测试通过!')
      else:
          print('测试失败!')
      

3.4 生成器

  • 介绍:

    • 生成器也是一种迭代器,它内部存储的不是元素值,而是用于返回下一个迭代值得算法,它是一种根据某种算法边循环便计算的一种机制,采用惰性计算机制。
  • 优势:

    • 生成器相对于list来说,不需要占用很大的存储空间,从而节省大量的空间
  • 创建方法:

    • 生成器一般有两种创建方式,第一是生成器表达式,第二是生成器函数(包含yield)
  • 使用场景:

  • 实现多任务,协程

  • generator其中两种创建方式

    • 把一个列表生成式[]改为(),就创建了一个generator

      • L = [x * x for x in range(10)]	# L为list
        g = (x * x for x in range(10))  # g为generator
        
    • 在函数的定义中包含yield关键字即可,普通函数就变为generator

      • def fib(max):  # 斐波那契数列
            n, a, b = 0, 0, 1
            while n < max:
                yield b
                a, b = b, a + b
                n = n + 1
            return 'done'
        
  • 获取generator中的值

    • next(generator对象)
    • for循环遍历generator对象
  • 获取generator中的返回值

    • 通过捕获StopIteration错误,返回值包含在StopIteration中的value里

      f = fib(6)   # 斐波那契数列对象
      while True:
          try:
              x = next(f)
              print(x)
          except StopIteration as e:
              print(e.value)
              break
      
  • 练习:杨辉三角

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJiyK7g4-1598977365409)(D:\Data\Desktop\image-20200719175610008.png)]

    • 把每一行看做一个list,试写一个generator,不断输出下一行的list:

    • # 答案-----------------------------
      def triangles():
      	s = [1]
          while True:
              yield s
              l = []
              sum = 0
              for i in s:
                  l.append(sum+i)
                  sum = i
              l.append(s[len(s)-1])
              s = l
      # ---------------------------------
      
      
      # 期待输出:
      # [1]
      # [1, 1]
      # [1, 2, 1]
      # [1, 3, 3, 1]
      # [1, 4, 6, 4, 1]
      # [1, 5, 10, 10, 5, 1]
      # [1, 6, 15, 20, 15, 6, 1]
      # [1, 7, 21, 35, 35, 21, 7, 1]
      # [1, 8, 28, 56, 70, 56, 28, 8, 1]
      # [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
      n = 0
      results = []
      for t in triangles():
          results.append(t)
          n = n + 1
          if n == 10:
              break
      
      for t in results:
          print(t)
      
      if results == [
          [1],
          [1, 1],
          [1, 2, 1],
          [1, 3, 3, 1],
          [1, 4, 6, 4, 1],
          [1, 5, 10, 10, 5, 1],
          [1, 6, 15, 20, 15, 6, 1],
          [1, 7, 21, 35, 35, 21, 7, 1],
          [1, 8, 28, 56, 70, 56, 28, 8, 1],
          [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
      ]:
          print('测试通过!')
      else:
          print('测试失败!')
      

3.5 迭代器

  • 凡是可作用于for循环的对象都是Iterable类型

  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列

  • 集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数把Iterable对象变成Iterator对象

  • Python的for循环本质上就是通过不断调用next()函数实现的

    • for x in [1,2,3,4]
      	pass
      
    • 上面等价于下面

    • # 首先获得Iterator对象
      it = iter([1,2,3,4])
      # 循环
      while True:
      	try:
      		# 获得下一个值
      		x = next(it)
      	except StopIteration:
      		# 遇到StopIteration就退出循环
      		break
      

4 函数式编程

https://www.liaoxuefeng.com/wiki/1016959663602400/1017328525009056

4.1 高阶函数

  • 变量可以指向函数

    • 以abs()函数为例

    • f = abs
      f(-10)
      # 10
      
  • 函数名也是变量

    • 函数名是什么?函数名其实就是指向函数的变量

    • 以abs()为例,完全可以把函数名abs堪称变量,它指向一个可以计算绝对值的函数

    • 如果把abs指向其他对象

      • abs = 10
        abs(-10)
        # 会报错,因为abs变量指向了整数10,此时abs不是指向计算绝对值的函数
        # 要恢复abs函数,需要重启Python交互环境
        
      • 由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10

  • 传入函数

    • 既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

    • 简单的高阶函数

      • def add(a,b,f)
        	return f(a) + f(b)
        
        a = -5
        b = 6
        f = abs
        result = add(a,b,f)
        print(result)
        # 11
        

4.2 map/reduce

  • map()参数:

    • 第一个是函数
    • 第二个是Iterable对象
    • map是内置函数,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
  • reduce()参数:

    • 第一个:函数
    • 第二个:Iterable对象
    • reduce需要导入,reduce把一个函数作用在一个序列上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,把累积后的结果作为返回值
  • 练习:

    • 利用map和reduce编写一个str2float函数,把字符串‘123.456’转换成浮点数123.456

    • 最快的方法是用float()函数把字符串转为浮点数,但要探究,就不能用这个方法

    • from functools import reduce
      DIGITS = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'0':0}
      # 字符转化为数字
      def char2num(s):
      	retrn DIGITS[s]
      	
      # 字符串转化为整数
      def str2int(s):
      	return reduce(lambda x,y:x*10+y,map(char2num,s))
      
      # 字符串转化为浮点数
      def str2float(s):
          L = s.split('.')
          return reduce(lambda x,y:x*10+y,map(char2num,L[0])) + reduce(lambda x,y:x*10+y,map(char2num,L[1]))/(10**len(L[1]))
      

4.3 fliter

  • 内置函数

  • filter()接受一个函数和一个序列,返回值是一个Iterator,也就是一个惰性序列

  • 和map()不同点在于,filter()虽然也是把传入的函数依次作用在每个元素,但是根据函数的返回值是否是True还是False来决定保留还是丢弃该元素(即filter()会自动对参数一函数的返回值进行bool判断,是True则留下,False丢弃)

  • 例子:

    • 判断列表的元素是否是基数,基数则留下,偶数则删除

    • def is_odd(n):
      	return n%2 == 1
      list(filter(is_odd,[1,2,3,4,5,6,7]))
      # [1,3,5,7]
      
    • 把一个序列中的空字符串删掉

    • def not_empty(s):
      	return s and s.strip()
      
      list(filter(not_empty,['AB',' ',None,'C']))
      # ['AB','C']
      
  • 练习:用filter求素数(埃式筛法)

    • # 构造一个从3开始的奇数序列
      def _odd_iter():
      	n = 1
      	while True:
      		n += 2
      		yield n
      
      # 筛选函数
      def _not_divisible(n):
      	return lambda x: x % n > 0
      
      # 定义一个生成器,不断返回下一个素数
      def primes():
      	yield 2
      	it = _odd_oter()  #初始序列
      	while True:
      		n = next(it)
      		yield n 
      		it = filter(_not_divisible(n),it)
      
  • 练习:用filter筛选出回数(一个从左到右读和从右到左读都是一样的自然数)

    • 算法一:

      • def is_palindrome(n):
            s = str(n)
            if len(s) == 1:
                return 1
            if len(s)%2 == 0:
               return s[:len(s)//2] == s[len(s)//2:]
            return s[:len(s)//2] == s[(len(s)//2)+1:]
            
        # 测试:
        output = filter(is_palindrome, range(1, 1000))
        print('1~1000:', list(output))
        if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
            print('测试成功!')
        else:
            print('测试失败!')
        
    • 算法二:

      • def is_palindrome(n):
        	s = str(n)
        	l = len(s)+1
        	for i in range(l//2):
        		if s[i] != s[-i-1]
        			return False
        	return True
        	
        # 测试:
        output = filter(is_palindrome, range(1, 1000))
        print('1~1000:', list(output))
        if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
            print('测试成功!')
        else:
            print('测试失败!')
        

4.4 sorted

  • sorted(iterable, key=None, reverse=False)参数:

    • 第一个参数为可迭代对象
    • 第二个参数key可以接收一个函数,然后作用在第一个参数的每个元素上,再进行大小比较(整数就比较数字大小,字符串则比较Ascii的大小)
    • reverse默认从小到大排序,True则为从大到小
  • 练习

    • 假设我们用一组tuple表示学生名字和成绩:使用sorted()对列表分别按名字和姓名排序

    • L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
      
      # 对名字进行排序,名字是字符串即通过Ascii的大小比较
      def by_name(t):
          return t[0]
      L2 = sorted(L, key=by_name)
      print(L2)
      
      # 对成绩以倒序方式排序
      def by_score(t):
          return -t[1]	# 这里添加"-"号的效果和在sorted函数中添加reverse=True效果一样
      L3 = sorted(L, key=by_score)
      print(L3)
      

4.5 返回函数(闭包讲解)

https://www.liaoxuefeng.com/wiki/1016959663602400/1017434209254976

  • 1、高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回

  • 2、在一个函数(外部函数)中又定义了一个函数(内部函数),内部函数可以引用外部函数的参数和局部变量,当外部函数返回内部函数时,相关参数和变量都保存在返回的内部函数中,这种程序结构称为”闭包“

  • 3、注意:返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量(可以使用可变类型的数据例如list)

  • 4、如果一定要引用循环遍历,方法就是在创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何修改,已绑定到函数参数的值不变

  • 练习:利用闭包返回一个计数器函数,每次调用它返回递增整数

    • 下面代码闭包的返回值不能使用整数类型,因为整数是不可变数据类型(修改值的话,内存地址也会发生改变)(笔记第3点),而是使用list(可变),这样修改list中的value,对应的id也不会发生改变

    • def createCounter():
          n = [0]
          def counter():
              n[0] = n[0]+1
              return n[0]
          return counter
      
      # 测试:
      counterA = createCounter()
      print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
      counterB = createCounter()
      if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
          print('测试通过!')
      else:
          print('测试失败!')
      

4.6 匿名函数

  • 匿名函数:没有函数名,只是一个函数对象

  • 由于匿名函数是只是一个函数对象,没有函数名,所以不用担心函数名冲突问题,此外还可以将匿名函数赋值给一个变量,再利用变量来调用函数

  • 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果

    lambda x:  x*x   # 参数可以有多个
    相当于
    def f(x):
        return x * x
    

4.7 装饰器

  • 在代码运行期间动态增加功能的方式,称之为装饰器

  • 本质上,decorator就是一个返回函数的高阶函数

  • 通过functools.wrap()把原始函数的__name__等属性复制到wrapper()函数中,否则有些依赖函数签名的代码执行就会报错

  • 不带参数的decorator

    • import functools
      
      def log(func):
          @functools.wraps(func)  # 把原始函数的__name__等属性复制到wrapper()函数中,否则有些依赖函数签名的代码执行就会报错
          def wrapper(*args, **kw):
              print('call %s():' % func.__name__)
              return func(*args, **kw)
          return wrapper
      
  • 带参数的decorator

    • import functools
      
      def log(text):
          def decorator(func):
              @functools.wraps(func)
              def wrapper(*args, **kw):
                  print('%s %s():' % (text, func.__name__))
                  return func(*args, **kw)
              return wrapper
          return decorator
      
  • 练习1:请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

    • import time, functools
      def metric(fn):
          @functools.wraps(fn)
          def wrapper(*args, **kw):
              start_time = time.time()
              tmp = fn(*args, **kw)
              end_time = time.time()
              print('%s executed in %s ms' % (fn.__name__, end_time - start_time))
              return tmp
          return wrapper
      
      # 测试
      @metric
      def fast(x, y):
          time.sleep(0.0012)
          return x + y;
      
      @metric
      def slow(x, y, z):
          time.sleep(0.1234)
          return x * y * z;
      
      f = fast(11, 22)
      s = slow(11, 22, 33)
      if f != 33:
          print('测试失败!')
      elif s != 7986:
          print('测试失败!')
      
  • 练习2:请编写一个decorator,能在函数调用的前后打印出'begin call''end call'的日志,写出一个@log的decorator,使它即支持

    • @log
      def f():
          pass
      
    • 又支持

    • @log('execute')
      def f():
          pass
      
    • import functools,time
      def log(text):   # 注意这里的text,当不传入参数时,text相当于传入的函数名
          if isinstance(text,str):
              def decorator(fn):
                  @functools.wraps(fn)
                  def wrapper(*args, **kw):
                      print('begin call %s' % fn.__name__)
                      tmp = fn(*args, **kw)
                      print('end call %s' % fn.__name__)
                      return tmp
                  return wrapper
              return decorator
      
          @functools.wraps(text)  #注意这里的text,当不传入参数时,text相当于传入的函数名
          def wrapper(*args, **kw):
              print('begin call %s' % text.__name__)
              tmp = text(*args, **kw)
              print('end call %s' % text.__name__)
              return tmp
          return wrapper
          
      
      # 测试
      @log
      def f():
          print('f')
      f()
      
      print('------------------------')
      
      @log('im')
      def f1():
          print('f1')
      f1()
      

4.8 偏函数

  • 当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单

    • def int2(x ,base=2):
          return int(x, base)
      print(int2('1000000'))
      
    • import functools
      int2 = functools.partial(int,base=2) # partial()函数固定base参数
      print(int2('10000000'))
      

5 模块

  • 模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用
  • 创建自己的模块时,要注意:
    • 模块名要遵循Python变量命名规范,不要使用中文、特殊符号
    • 模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,内置模块:https://docs.python.org/3/library/functions.html
  • 包:为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MO1SNNzg-1598977365411)(D:\Data\Desktop\image-20200721151416816.png)]
    • 文件www.py的模块名是mycompany.web.www,web下的__init__.py必须要创建(包的结构),可以不写内容,__init__.py本身也是一个模块,它的模块名是mycompany.web(即web的模块名为__init.py__的模块名)

5.1 使用模块

  • import xxx

  • from xxx import xxx

  • 类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用(不应该不是不能)

  • private函数或变量的作用

    • def _private_1(name):
          return 'Hello, %s' % name
      
      def _private_2(name):
          return 'Hi, %s' % name
      
      def greeting(name):
          if len(name) > 3:
              return _private_1(name)
          else:
              return _private_2(name)
      
    • 在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数就不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public

5.2 安装第三方模块

  • https://www.liaoxuefeng.com/wiki/1016959663602400/1017493741106496
  • 推荐使用Anaconda,一个基于Python的数据处理和科学计算平台,其已经内置了许多非常有用的第三方库

6 面向对象编程

  • 面向对象编程-object Oriented Programming,简称OOP,是一种程序设计思想
  • OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数
  • 面向过程于面向对象的程序设计:
    • 面向过程的程序设计是把计算机程序视为一系列命令的集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度
    • 面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
  • 在Python中所有数据类型都可以视为对象,也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念
  • 采用面向对象的程序设计思想,首先思考的不是程序的执行流程,而是将待处理的数据视为一个对象,去思考对象中有哪些属性,可以有哪个方法
  • 面向对象的设计思考是抽象出Class,根据Class创建instance
  • 一个Class既包含数据,又包含操作数据的方法
  • 面向对象的三大特点:封装、继承、多态

6.1 类和实例

  • 面向对象最重要的概念是类和实例,类是抽象的模板,而实例是根据类创建出来的一个个具体的"对象",各个实例拥有的数据都相互独立,互不影响;

  • 方法就是于实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据

  • 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节

  • 数据封装:

    • 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))
      
    • 通过类的方法将数据封装起来,外部调用Student对象的实例的print_score方法很容易,但是不知道该方法内部的逻辑和数据。起到一个封装作用。

6.2 访问限制

  • 关于下划线:https://zhuanlan.zhihu.com/p/105783765?utm_source=com.miui.notes

  • 在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的如咋逻辑

  • 为了不让内部属性被外部访问,可以在属性的名称前添加__两条下划线,实例的变量如果以__开头,就变成了一个私有属性,只有内部可以访问,而外部不可以访问(实际上还是可以访问的,下面会说)

    • 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))
      
    • s = Student('tom',80)
      print(s.__name)  # 会报错
      print(s._Student__name)  #这种方法可以访问到私有属性
      
  • Python中,有些变量名是__xxx__的,这种是特殊变量,不是private,外部是可以直接访问的

  • 有些是_xxx这种一个下划线的变量,外部是可以直接访问的,但是,按照约定俗成的规定,当看到这种变量时,意思是:“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问我”

  • 私有属性“__name”在类中定义的时候,Python解释器会对外把__name变量改成_Classname__name,所以外部还是可以通过“ 实例名._Classname__name”的方式去访问私有属性

  • 如果外部代码要获取私有属性的值和修改私有属性的值的话,我们可以在类中增加get方法和set方法(也可以用装饰器@property把方法变成属性用于调用),外部可以通过函数去调用或修改属性

    • class Student(object):
          def __init__(self, name, gender):
              self.name = name
              self.__gender = gender
      
          def get_gender(self):
              return self.__gender
          
          def set_gender(self, gender):
              if not isinstance(gender,str):
                  raise 'TypeError'
              self.__gender = gender
      
      # 测试:
      bart = Student('Bart', 'male')
      if bart.get_gender() != 'male':
          print('测试失败!')
      else:
          bart.set_gender('female')
          if bart.get_gender() != 'female':
              print('测试失败!')
          else:
              print('测试成功!')
      

6.3 继承和多态

  • 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
  • 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的
  • https://www.liaoxuefeng.com/wiki/1016959663602400/1017497232674368

6.4 获取对象信息

  • type()

    • 使用type()判断对象类型,基本类型都可以用type()判断

    • 判断函数使用types(),这个函数不是builtin的,需要import types

    • >>> import types
      >>> def fn():
      ...     pass
      ...
      >>> type(fn)==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()

    • 对于class的继承关系来说,使用type()就很不方便。要判断class的类型,可以使用isinstance()函数

    • class Animal(object):
      	def run(self):
      		print('Animal is runing...')
      
      class Dog(Animal):
      	def run(self):
      		print('little dog is running...')
      a = Animal()
      d = Dog()
      print(isinstance(a,Animal))  # True
      print(isinstance(d,Animal))  # True
      
    • instance()还可以判断一个变量是否是某些类型中的一种

    • isinstance([1, 2, 3], (list, tuple))
      
  • dir()

    • 要获取一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list

      • dir('ABC')  #获取str对象的所有属性和方法
        # ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
        
    • 当使用len()函数去获取对象的长度时,其实内部是调用了__len__()这个特殊方法,所以我们也可以自己在class中重写这个方法(类似的其他内置函数道理也一样)

    • 获取class内的数据,还有getattr()、setattr()、hasattr()函数

      • class MyObject(object):
        	def __init__(self):
        		self.x = 9
        	def power(self):
        		return self.x * self.x
        obj = MyObject()
        
        print(hasattr(obj,'x'))  # True
        print(hasattr(obj,'y'))	 # False
        setattr(obj,'y',19)
        print(hasattr(obj, 'y')) # True
        print(getattr(obj, 'y')) # 19
        
      • getattr()试图获取不存在的属性时会报错,可以指定默认的返回值

        • getattr(obj,'z',404)  #若obj对象中没有z属性,则返回404
          

6.5 类属性和实例属性

  • 类属性可以通过“类名.属性名”方式或“实例对象.属性名”调用
  • 不要对实例属性和类属性使用相同的名字,实例属性优先级比类属性高,所以当类属性名和实例属性名相同时,外部调用时会调用实例属性
  • 实例属性属于各个实例所有,互不干扰
  • 类属性属于类所有,所有实例共享一个属性

7 面向对象高级编程

  • 数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有很多高级特性,允许我们写出非常强大的功能。
  • 我们会讨论多重继承、定制类、元类等概念。

7.1 使用__slots__

  • 正常情况下,我们可以给创建的class绑定属性或方法

    • 绑定属性有两种,第一种是创建实例化对象后绑定,另一种是直接类名.属性名绑定类属性

    • 绑定方法同样有两种情况

      • 1、实例化对象后,给对象绑定方法,不过只有此实例化对象可以使用该方法,另一个实例却不可以用

        • >>>	class Student(object):
          ... 	pass
          >>> def set_age(self, age): # 定义一个函数作为实例方法
          ...     self.age = age
          ...
          >>> from types import MethodType
          >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
          >>> s.set_age(25) # 调用实例方法
          >>> s.age # 测试结果
          25
          
          >>> s2 = Student() # 创建新的实例
          >>> s2.set_age(25) # 尝试调用方法
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
          AttributeError: 'Student' object has no attribute 'set_age'
          
      • 2、为了给所有实例都绑定方法,可以给class绑定方法:

        • >>> def set_score(self, score):
          ...     self.score = score
          ...
          >>> Student.set_score = set_score
          
  • 使用__slots__限定实例的属性(类属性不限制),规定实例只可以绑定哪些属性

    • class Student(object):
          __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
      
    • 使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,不过子类中也可以定义__slots__,这样的话子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

7.2 使用@property

  • 为了让外部使用类中的属性而不将真实属性名暴露出去,可以使用get()方法和set()或者使用@property装饰器

  • class Student(object):
    
        def get_score(self):
             return self._score
    
        def set_score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    
  • 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 = 60 # OK,实际转化为s.set_score(60)
    >>> s.score # OK,实际转化为s.get_score()
    60
    >>> s.score = 9999
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    

7.3 多重继承

https://www.liaoxuefeng.com/wiki/1016959663602400/1017502939956896

  • 继续是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能

  • 定义类时,继承多个不同的类,从而实现多种功能,这种设计思想通常称为MixIn

  • 只允许单一继承的语言(如Java)不能使用MixIn的设计

7.4 定制类

  • 这里说的定制类,其实就是重写类里面自带的特殊方法,像一些__call__、__getitem__、__str__、__repr__、__iter__、__next__、__getattr__等等

  • __str__和__repr__

    • __str__是指打印对象的时候会调用,像print(obj)方法其实也是调用obj对象中的__str__方法
      __repr__也是打印对象的时候会调用的一个方法
      区别在于:__repr__可以应用在所有场景,__str__只是用一些特定的场景,是因为那些场景在repr和str之间会有优先调用的区别,在命令行模式下,解释器会优先调用repr
      
  • __iter__和__next__

    • 一个类对象要想被用于for…in循环,首先这个对象是可迭代对象即类中有__iter__方法(collections.abc中的Iterable,使用isinstance来判断),确认是可迭代对象后,for循环会调用next()方法去获取值即类中也要有__next__方法

    • Fib类举例

      • class Fib(object):
            def __init__(self):
                self.a, self.b = 0, 1
            
            def __iter__(self): # 实现iter方法来判断是否是iterable
                return self
        
            def __next__(self): # for...in 循环遍历时,其实也是调用next()方法
                self.a, self.b = self.b, self.a + self.b    # 计算下一个值
                if self.a > 10000:  # 退出循环的条件
                    raise StopIteration 
                return self.a # 返回下一个值
        
        f = Fib()
        
        for i in f:
            print(i)
        
  • __getitem__这个方法是获取对象的索引、分片操作

    • 上文提到Fib类,虽然Fib实例能作用于for循环,但是不能当作list去进行slice操作

    • 下文的n是指传入进去的索引,下文没有对step进行判断操作,只判断了切片的start和stop参数

    • class Fib2(object):
          def __getitem__(self, n):  #n是指传入进去的索引
              if isinstance(n, int):
                  a, b = 1, 1
                  for _ in range(n):
                      a, b = b, a+b
                  return a
      
              if isinstance(n, slice):
                  start = n.start
                  stop = n.stop
                  if start is None:
                      start = 0
                  a, b = 1, 1
                  L = []
                  for x in range(stop):
                      if x >= start:
                          L.append(a)
                      a, b = b, a+b
                  return L
      
      f2 = Fib2()
      print(f2[0]) # n为0
      print(f2[3:5])	# n为3:5
      print(f2[:10])  # n为0:10
      
  • __getattr__

    • 当类去调用属性的时候,只有没有找到属性时,才调用__getattr__,已有的属性,不会去__getattr__中查找

    • class Student(object):
      
          def __init__(self):
              self.name = 'Michael'
      
          def __getattr__(self, attr):
              if attr=='score':
                  return 99
      s = Student()
      print(s.name)
      # 'Michael'
      print(s.score)  
      # 99
      
    • 实现链式调用

      • class Chain(object):
            
            def __init__(self, path= ''):
                self._path = path
            
            def __getattr__(self, path):
                return Chain('%s.%s' % (self._path, path))
            
            def __str__(self):
                return self._path
            
            __repr__ = __str__
        
        print(Chain('www').baidu.com)
        
  • __call__

    • 调用实例,可以使用callable()方法判断对象是否是Callable对象,是的话则可以被调用

    • class Student(object):
          
          def __init__(self, name):
              self.name = name
          
          def __call__(self):
              return ('my name is %s' % (self.name))
      
      s = Student('Michael')
      print(s)
      

7.5 枚举类

  • https://www.liaoxuefeng.com/wiki/1016959663602400/1017595944503424#0

  • from enum import Enum

  • Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较

7.6 元类metaclass

  • type()

    • 1、查看类型

    • 2、创建类

      • class Hello(object):
            def hello(self, name='world'):
                print('Hello %s' % name)
                print(self.__class__.__name__)
        
        H = Hello()
        H.hello()
        # 上面和下面一样
        def fn(self, name='world'):
            print('Hello %s' % name)
        
        Hello = type('Hello', (object,), dict(hello=fn))
        h = Hello()
        h.hello()
        
      • 要创建一个class对象,type()函数依次传入3个参数:

        • 1、class的名称;
        • 2、继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
        • 3、class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
  • metaclass

    • 除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass
    • 可以把类看成是metaclass创建出来的"实例"
    • 实例对象看成是类创建出来的实例
  • https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072

8 错误、调试和测试

  • 程序运行过程中总会遇到各种各样的错误(bug),所以在编写代码时我们需要不断的去调试(debug),编写测试也很重要,以便于更好的调试代码

8.1 错误处理

  • https://docs.python.org/3/library/exceptions.html#exception-hierarchy

  • try…except…finally

  • raise主动抛出异常

  • 使用logging模块记录错误

    • import logging
      
      def foo(s):
          return 10 / int(s)
      
      def bar(s):
          return foo(s) * 2
      
      def main():
          try:
              bar('0')
          except Exception as e:
              logging.exception(e)
      
      main()
      print('END')
      

8.2 调试

调试方式
  • print()

  • assert

    • 断言assert

    • assert n != 0, 'n is zero!'
      如果 n != 0 为True,则执行下一条语句,否则返回AssertionError:n is zero!
      
  • logging模块(主流)

  • pdb(python debugger)命令行调试

    • python -m pdb err.py
      
  • pdb.set_trace()

    • import pdb
      xxx
      pdb.set_trace() # 运行到这里会自动暂停
      xxx
      
  • IDE

    • 在一些IDE(VSCode、PyCharm)中集成了调试工具

8.3 单元测试

https://www.liaoxuefeng.com/wiki/1016959663602400/1017604210683936

  • 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作
  • unittest模块

8.4 文档测试

https://www.liaoxuefeng.com/wiki/1016959663602400/1017605739507840

9 IO编程(这里只涉及同步IO)

  • IO在计算机中指Input/Output即输入和输出
  • 程序和运行时的数据是在内存中驻留的,由CPU来执行,涉及到数据交换的地方,通常就是磁盘、网络等,就需要IO接口
    • 相对于内存来说,数据从内存到磁盘存储起来,相当于往外发数据,叫Output,从磁盘读取数据的话,即数据从磁盘到内存,叫Input
  • Stream(流),相当于水管,是单向流动。浏览器和服务器之间的交互需要两根水管,用于收发数据
  • 同步IO和异步IO
    • 同步和异步区别在于是否等待IO执行的结果
    • 异步IO编写程序的性能会远远高于同步IO,缺点在于异步IO的编程模型复杂
    • 举例:你想去买个汉堡然后去逛街,但当你去到麦当劳买汉堡时,服务员告诉你汉堡需要现做,需要等待,这时,你有两种选择分别对应同步IO和异步IO。
      • 一、同步IO:你就在麦当劳里面干等5分钟,拿到汉堡后去逛街
      • 二、异步IO:服务员告诉你你可以先去逛街,然后5分钟后通知你,这时相当于这5分钟用于逛街
    • 异步IO的编程模型复杂也可以在上面例子体现出来,首先得知道什么时候汉堡做好了,其次服务员通知你的方式也有很多种,是跑过来通知你(回调模式),还是手机短信通知你,你就得不停的检查手机(轮询模式)…。总之,异步IO的复杂度远远高于同步IO

9.1 文件读写

  • 知识点:磁盘上读写文件的功能都是由操作系统提供的,现代的操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(文件描述符),然后通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写数据)

  • file-like Object

  • open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

  • open(‘file_path’,‘r/w/a/rb/wb/ab’,encoding=’’,errors=’’)函数

    • read()
      • 一次性读取全部内容,返回的是str
    • readline()
      • 每次读取一行内容,返回的是str
    • readlines()
      • 将读取到的文件内容按行返回list
  • with语句

    • 使用with语句后就不需要手动关闭文件对象了

    • with open('/path/to/file', 'r') as f:
          print(f.read())
          
      # with语句自动完成了 f.close()
      

9.2 StringIO和BytesIO

https://www.liaoxuefeng.com/wiki/1016959663602400/1017609424203904

  • 很多时候,数据读写不一定是在文件上,也可以在内存中读写数据
  • StringIO就是在内存中读写str
  • BytesIO在内存中读写二进制数据

9.3 操作文件和目录

  • 在命令行中

    • 操作文件和目录使用的是操作系统提供的命令,比如dir、cp、mkdir
  • 在Python程序中

    • 使用os模块
      • os.name:获取操作系统类型
      • os.uname:更详细的操作系统类型信息(Windows上不支持)
      • os.environ:环境变量
      • os.mkdir(x):创建目录
      • os.rmdir(x):删除目录
      • os.rename(x,y):把x文件重命名为y
      • os.remove(x):删除x文件
      • os.walk(x):遍历x路径,会返回含有三个元素的元组,第一个是当前的路径,第二个是含有所有目录路径的list,第三个是含有所有文件路径的list
      • os.listdir(x):返回x路径下的所有目录名和文件名
    • os.path模块
      • os.path.abspath(x):查看x的绝对地址
      • os.path.isdir(x):判断x是否是目录
      • os.path.isfile(x):判断x是否是文件
      • os.path.join(x,y):将x和y的路径进行拼接
      • os.path.split(x):将x路径进行拆分,返回值是两个元素的元组
      • os.path.splitext(x):返回值也是元组,但第二个元素是文件后缀
      • os.
    • shutil模块
      • copyfile():复制文件的函数
  • 练习:

    • 编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。
      
    • 递归方法

      • import os
        def find_str_file(path, strname):
            L = []
            for f in os.listdir(path):
                relative_path = os.path.join(path,f)
                if os.path.isdir(f):
                    try:
                        find_str_file(relative_path,strname)
                    except:
                        print(relative_path,'拒绝访问')
                if strname.lower() in f.lower():
                    L.append(relative_path)
                else:
                    return '未找到对应的文件'
            return L
            
        l = find_str_file('D:\Data\Desktop', 'P')
        print(l)
        
    • 正常方法

      • import os 
        def find_str_file(path, strname):
            L = []
            for cur_path, alldir_path_list, allfile_path_list in os.walk(path):
                for file_name in allfile_path_list:
                    if strname.lower() in file_name.lower():
                        L.append(os.path.join(cur_path, file_name))
                    else:
                        return '未找到对应的文件'
            return L
            
        l = find_str_file('.','12345646')
        print(l)
        

9.4 序列化

https://www.liaoxuefeng.com/wiki/1016959663602400/1017624706151424

  • pickle
  • json
    • img
    • json模块的dumps()loads()函数是定义得非常好得接口的典范。当我们使用时,只需要传入一个必须的参数。但是,当默认的序列化或反序列化机制不满足我们的要求时,我们又可以传入更多的参数来定制序列化或反序列化的规则,既做到了接口简单易用,又做到了充分的扩展性和灵活性。
    • json.dumps(obj):将obj序列化为json格式数据并返回
    • json.dump(obj,file-like Object):将obj序列化为json格式数据并存储到file-like Object中
    • json.loads(json_str):将json_str反序列化为python对象
    • json.load(file-like Object):将file-like Object中json数据反序列化为Python对象

10 进程和线程

https://www.zhihu.com/question/346422354

https://www.cnblogs.com/resn/p/5591419.html

线程和进程只是程序的执行过程,而不是具体指某一个程序

  • 程序:一个按指定格式存储的一系列指令的编码序列。即一组指令序列

  • 操作系统:也是程序的一种,给程序员提供一个统一的访问界面,便于去管理其他程序

  • 进程:把一个被载入内存、正在执行的程序叫做进程

  • 线程:是操作系统中最小的执行单元,而进程由至少一个线程组成,一个程序的执行点,线程可以由操作系统实现,也可以自己写程序实现。

  • 多线程:一个程序的多个执行点

  • 线程和进程的区别

    • 进程拥有操作系统分配的资源,而线程只能直接使用进程的资源,不能有自己的资源
  • 如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间

  • 多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂

10.1 多进程

  • 在Unix/Linux下,可以使用fork()调用实现多进程
    • fork()函数会有两此返回值,操作系统会自动把当前进程(父进程)复制一份(子进程),然后分别在父进程和子进程中返回
  • 要实现跨平台的多进程,可以使用multiprocessing模块
  • 进程间通信是通过QueuePipes等实现的
Process
  • import os
    from multiprocessing import Process
    
    def run_proc(name):
        print('Run child process %s (%s)' % (name,os.getpid()))
    
    if __name__ == "__main__":
        print('Parent process %s' % os.getpid())
        p = Process(target=run_proc, args=('test',))
        print('Child process will start')
        p.start()
        p.join()
        print('Child process end')
    
Pool
  • from multiprocessing import Pool
    import os,time,random
    
    def long_time_task(name):
        print('Run task %s (%s)' % (name, os.getpid()))
        start = time.time()
        time.sleep(random.random() * 3)
        end = time.time()
        print('Task %s run %0.2f seconds' % (name, (end-start)))
    
    if __name__ == "__main__":
        print('Parent process %s' % os.getpid())
        p = Pool(4)
        for i in range(5):
            p.apply_async(long_time_task, args=(i,))
        print('Waiting for all subprocessing done...')
        p.close()
        p.join()
        print('All subprocessing done')
    
进程间通信Queue
  • from multiprocessing import Queue,Process
    import os,time,random
    
    def write(q):
        print('Process to write:%s' % os.getpid())
        for i in ['A','B','C']:
            print('Put %s to queue' % i )
            q.put(i)
            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__":
        q = Queue()
        pw = Process(target=write, args=(q,))
        pr = Process(target=read, args=(q,))
        pw.start()
        pr.start()
        pw.join()
        pr.terminate()
    

10.2 多线程

  • 一个程序(进程)的多个执行点
  • Python的线程是真正的Posix Thread,而不是模拟出来的线程
  • Python的标准库提供了_threadthreading_thread是低级模块,threading是高级模块
threading
import time, threading

def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n <5:
        n = n+1
        print('thread %s >>> %s' % (threading.current_thread().name,n))
        time.sleep(1)
        print('thread %s ended' % threading.current_thread().name)

if __name__ == "__main__":
    print('thread %s start'% threading.current_thread().name)
    t = threading.Thread(target=loop, name='LoopThread')
    t.start()
    t.join()
    print('thread %s ended'% threading.current_thread().name)
Lock
  • 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而在多线程中,所有变量都有所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,从而把内容给改乱了。

  • 为什么多个线程同时改一个变量会把变量内容给该乱了,因为Python是高级语言的原因,高级语言的一条语句在CPU执行时是若干条语句,例如

    • balance = balance + n 语句其实分为两步执行

      • 1、计算balance + n,存入临时变量中

      • 2、将临时变量的值赋值给balance

      • 1、x = balance + n
        2、balance = x
        
    • 改乱的例子:

      • import time, threading
        
        balance = 0
        
        def change_it(n):
        	# 先存后取,结果应该为0
            global balance
            balance = balance + n
            balance = balance - n
        
        def run_thread(n):
            for _ in range(1000000):
                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)
        # 结果balance不为0
        
      • 正常过程应该是

        • 初始值 balance = 0
          
          t1: x1 = balance + 5 # x1 = 0 + 5 = 5
          t1: balance = x1     # balance = 5
          t1: x1 = balance - 5 # x1 = 5 - 5 = 0
          t1: balance = x1     # balance = 0
          
          t2: x2 = balance + 8 # x2 = 0 + 8 = 8
          t2: balance = x2     # balance = 8
          t2: x2 = balance - 8 # x2 = 8 - 8 = 0
          t2: balance = x2     # balance = 0
              
          结果 balance = 0
          
      • 实际过程

        • 初始值 balance = 0
          
          t1: x1 = balance + 5  # x1 = 0 + 5 = 5
          
          t2: x2 = balance + 8  # x2 = 0 + 8 = 8
          t2: balance = x2      # balance = 8
          
          t1: balance = x1      # balance = 5
          t1: x1 = balance - 5  # x1 = 5 - 5 = 0
          t1: balance = x1      # balance = 0
          
          t2: x2 = balance - 8  # x2 = 0 - 8 = -8
          t2: balance = x2      # balance = -8
          
          结果 balance = -8
          
  • 为了解决多线程间调用全局变量的问题,需要引入概念,这样多个线程可以有序执行,有序的去调用共享的变量,但是也正是由于锁的存在,会影响执行速度,而且每个线程都有一个锁,可能会形成不同线程持有不同的锁且试图获取对方持有的锁的情况,导致死锁出现

    • import threading, multiprocessing
      balance = 0
      def change_it(n):
          # 先存后取,结果应该为0:
          global balance
          balance = balance + n
          balance = balance - n
      
      lock = threading.Lock()
      def run_thread(n):
          for i in range(1000000):
              lock.acquire()
              try:
                  change_it(n)
              finally:
                  lock.release()
      
      t1  = threading.Thread(target=run_thread, args=(5,))
      t2 = threading.Thread(target=run_thread, args=(8,))
      t1.start()
      t2.start()
      t1.join()
      t2.join()
      print(balance)
      
ThreadLocal
  • ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题,多个线程中使用全局变量的话就必须要加锁,而使用局部变量就不需要

  • 为了解决多线程中变量共享的问题,可以使用局部变量,可以定义一个dict,将变量名作为key,值作为value,这样只需要在每个线程对应的函数中去调用里面的值,然后赋值给函数中的局部变量即可。而ThreadLocal对象相当于作为中间对象

  • import threading
    # 创建全局的ThreadLocal对象
    local_school = threading.local()
    
    def process_student():
        # 获取当前线程关联的student
        std = local_school.student
        print('Hello, %s (in %s)' % (std, threading.current_thread().name))
    
    def process_thread(name):
        local_school.student = name
        process_student()
    
    t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-t1')
    t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-t2')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    

10.3 Python如何使用多核CPU

  • 对于Python来说,每个进程都会有一把GIL锁,而每个进程中的多个线程都会使用这把GIL锁,所以Python的多线程其实使用不了多核CPU,还是相当于单核CPU按顺序执行多个任务

  • Python解释器上的GIL全局锁会给所有线程的执行代码都上了锁,所以多线程在Python中其实只能交替执行,并不可以真正利用到多核CPU,多线程程序不能并发执行,这是历史遗留问题,除非重写一个不带GIL的解释器

  • 想要实现并发执行,也可以使用多进程实现,Python每个进程都会有一把GIL,互相之间独立,互不影响

10.4 进程 vs 线程

实现多任务
  • 实现多任务,通常会设计Master-Worker模式,Master负责分配调度任务,Worker负责执行任务,多任务环境下,通常是一个Master,多个Worker
  • 多进程实现多任务,主进程是Master,其他进程是Worker
    • 优点:稳定性高,一个子进程崩溃了,不会影响主进程和其他子进程(除非主进程挂掉)
    • 缺点:创建进程的代价大,在Unix/Linux下可以用fork调用就还行,但在Windows下采用多进程模式开销就会很大。另外操作系统同时能运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统会连调用都很难
  • 多线程实现多任务,主线程是Master,其他线程是Worker
    • 优点:会比多进程快一些,是因为CPU切换线程的开销比切换进程的开销小。调度器进行不同任务间调度的时候是需要保存当前任务上下文,然后在切换到另外一个任务去执行。由于多线程间是共享一个进程的资源的的,而多进程间的资源是相互独立的,所以多线程间切换会快一些
    • 缺点:由于多线程是共享同一个进程的内存空间,所以但凡有一个线程挂掉都可能导致整个进程崩溃,致使操作系统强制结束整个进程。

10.5 计算密集型 vs IO密集型

  • 计算密集型:
    • 特点:要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等,全靠CPU的运算能力,因此代码运行效率至关重要,最好用C语言编写。
    • 因为计算密集型主要是要怎么高效地利用CPU,而多任务模式是会经常做任务切换,这样时间就花在切换的过程中,所以多任务模式不适合计算密集型,而要最高效的利用CPU,计算密集型任务同时进行的数量应当等于CPU核心数。
  • IO密集型:
    • 涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。
    • 对于IO密集型任务来说,任务越多越好,CPU利用率也会也高。因此代码运行效率就比较不重要,最好是用开发效率最高(代码量最少)的语言编写,例如脚本语言,C语言最差

10.6 异步IO

  • 利用操作系统提供的异步IO支持,就可以用单进程单线程模式来执行多任务,这种全新的模型称为事件驱动模型
  • Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模式就可以高效地支持多任务。
  • 对Python而言,单线程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。

10.7 分布式进程

  • 在Thread和Process中,应当首选Process,因为Process更稳定,而且Process可以分布在多台机器上,而Thread最多只能分布到同一台机器的多个CPU上

  • Python 的multiprocessing模块中的managers子模块还支持把多进程分布到多台机器上,由于managers模块封装的很好,不必了解网络通信的细节,也可以很容易地编写分布式多进程程序

    • task_master.py

      • # task_master.py
        
        import random, time, queue
        from multiprocessing.managers import BaseManager
        
        # 发送任务的队列:
        task_queue = queue.Queue()
        # 接收结果的队列:
        result_queue = queue.Queue()
        
        def return_task_queue():
            global task_queue
            return task_queue
        
        def return_result_queue():
            global result_queue
            return result_queue
        
        # 从BaseManager继承的QueueManager:
        class QueueManager(BaseManager):
            pass
        
        if __name__ == "__main__":
            # 把两个Queue都注册到网络上, callable参数关联了Queue对象:
            QueueManager.register('get_task_queue', callable=return_task_queue)
            QueueManager.register('get_result_queue', callable=return_result_queue)
            # 绑定端口5000, 设置验证码'abc':
            manager = QueueManager(address=('127.0.0.1', 5001), authkey=b'abc')
            # 启动Queue:
            manager.start()
            # 获得通过网络访问的Queue对象:
            task = manager.get_task_queue()
            result = manager.get_result_queue()
            # 放几个任务进去:
            for i in range(10):
                n = random.randint(0, 10000)
                print('Put task %d...' % n)
                task.put(n)
            # 从result队列读取结果:
            print('Try get results...')
            for i in range(10):
                r = result.get(timeout=10)
                print('Result: %s' % r)
            # 关闭:
            manager.shutdown()
            print('master exit.')
        
    • 另一台机器上(可以是本机不同的IDE运行)

      • # task_worker.py
        
        import time, sys, queue
        from multiprocessing.managers import BaseManager
        
        # 创建类似的QueueManager:
        class QueueManager(BaseManager):
            pass
            
        if __name__ == "__main__":
            # 由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字:
            QueueManager.register('get_task_queue')
            QueueManager.register('get_result_queue')
        
            # 连接到服务器,也就是运行task_master.py的机器:
            server_addr = '127.0.0.1'
            print('Connect to server %s...' % server_addr)
            # 端口和验证码注意保持与task_master.py设置的完全一致:
            m = QueueManager(address=(server_addr, 5000), authkey=b'abc')
            # 从网络连接:
            m.connect()
            # 获取Queue的对象:
            task = m.get_task_queue()
            result = m.get_result_queue()
            # 从task队列取任务,并把结果写入result队列:
            for i in range(10):
                try:
                    n = task.get(timeout=1)
                    print('run task %d * %d...' % (n, n))
                    r = '%d * %d = %d' % (n, n, n*n)
                    time.sleep(1)
                    result.put(r)
                except Queue.Empty:
                    print('task queue is empty.')
            # 处理结束:
            print('worker exit.')
        
  • Python的分布式进程接口简单,封装良好,适合把繁重任务分布到多台机器的环境下

  • 注意Queue的作用是用来传递任务和接收结果的,每个任务的描述数据量要尽量小。比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志文件存放的完整路径,有Worker进程再去共享的磁盘上读取文件。

11 正则表达式

  • 字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。

  • 正则表达式是一种用来匹配字符串的强有力的武器,它的设计思想使用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”,否则,该字符串就是不合法的

  • 使用正则表达式步骤:1、创建一个用于匹配的正则表达式。2、用该正则表达式去匹配用户的输入来判断是否合法

  • re模块

    • re.match(正则表达式,待匹配字符串)

    • re.compile(正则表达式):编译一个正则表达式,然后用编译后生成的Regular Expression对象去匹配字符串

      • r_c = re.compile(xxx)
      • r_c.match(待匹配字符串)
    • re.spilt**(正则表达式,待切割的字符串)**:这个模块比字符串的split方法强大多了

      • r = re.split(r'[\s\,\;]+', 'a,b;; c  d')
        print(r)
        # ['a', 'b', 'c', 'd']
        
  • 练习:

    • # 请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
      import re
      
      # someone@gmail.com
      # bill.gates@microsoft.com
      
      def is_valid_email(addr):
          re_email = re.compile(r'^[a-zA-Z](.*?)\@(gmail|microsoft)\.(\w+)$')
          if not re_email.match(addr):
              return False
          return True
      
      
      # 测试:
      assert is_valid_email('someone@gmail.com')
      assert is_valid_email('bill.gates@microsoft.com')
      assert not is_valid_email('bob#example.com')
      assert not is_valid_email('mr-bob@example.com')
      print('ok')
      
    • # 版本二可以提取出带名字的Email地址:
      
      # <Tom Paris> tom@voyager.org => Tom Paris
      # bob@example.com => bob
      
      import re
      def name_of_email(addr):
          # re_email = re.compile(r'<?([\w\s]+)>?\s*(\w*)@(\w+\.\w+)$')
          # result = re_email.match(addr)
          result = re.match(r'<?([\w\s]+)>?\s*(\w*)@(\w+\.\w+)$', addr)
          print(result.groups())
          if not result:
              return False
          return result.group(1)
      
      # 测试:
      assert name_of_email('<Tom Paris> tom@voyager.org') == 'Tom Paris'
      assert name_of_email('tom@voyager.org') == 'tom'
      print('ok')
          
      

12 常用内建模块

  • Python之所以自称“batteries included”,就是因为内置了许多非常有用的模块,无需额外安装和配置,即可直接使用

datetime

https://www.liaoxuefeng.com/wiki/1016959663602400/1017648783851616

  • datetime是Python处理日期和时间的标准库

  • datetime转换为timestamp:

    • datetime对象.timestamp()
  • timestamp转换为datetime:

    • datetime.fromtimestamp(timestamp)
  • timestamp转换为utc时区的datetime:

    • datetime.utcfromtimestamp(timestamp)
  • str转换为datetime:

    • datetime.strptime(dt_str,str_format)
  • datetime转换为str:

    • datetime对象.strftime(str_format)
  • datetime加减:

    • datetime对象 +\- timedelta(hours=x,days=x,…)
  • 本地时间转换为UTC时间:

    • datetime对象.replace(tzinfo=timezone(timedelta(hours=x)))
  • 时区转换:

    • datetime对象.astimezone(timezone(timedelta(hours=x)))
  • datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。

  • 如果要存储datetime,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关

  • 练习

    • 假设你获取了用户输入的日期和时间如2015-1-21 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp:
      
    • import re
      from datetime import datetime, timezone, timedelta
      
      # def to_timestamp(dt_str, tz_str):
      #     dt_obj = datetime.strptime(dt_str,'%Y-%m-%d %H:%M:%S')
      #     tz_hours = int(re.split(r'[UTC\:]+',tz_str)[1])
      #     tz_utc = timezone(timedelta(hours=tz_hours))
      #     dt_obj_tz = dt_obj.replace(tzinfo=tz_utc)
      #     return datetime.timestamp(dt_obj_tz)
      
      def to_timestamp(dt_str, tz_str):
          re_tz = re.match(r'^UTC([\-\+]\d{1,2})\:(\d{1,2})$', tz_str)
          dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone(timedelta(hours=int(re_tz.group(1)),minutes=int(re_tz.group(2)))))
          return dt.timestamp()
      
      # 测试
      t1 = to_timestamp('2015-6-1 08:10:30', 'UTC+7:00')
      assert t1 == 1433121030.0, t1
      
      t2 = to_timestamp('2015-5-31 16:10:30', 'UTC-09:00')
      assert t2 == 1433121030.0, t2
      
      print('ok')
      

collections

  • collections是Python内建的一个集合模块,提供了许多有用的集合类
  • namedtuple(typename, field_names)
  • deque():双向队列
  • defaultdict(func):字典获取不到值,默认返回func中的返回值
  • OrderedDict():使dict中的Key有序,可以用OrderedDict实现一个FIFO(先进先出)的dict
  • ChainMap():用于合并多个字典,或者合并多个映射就一个单独的映射,增删改只能对第一个字典进行操作,查询的话则是遍历所有字典,当查到第一个则停止查询。
  • Counter():一个简单的计数器

base64

  • Base64是一种用64个字符来表示任意二进制数据的方法

  • Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义得编码表也不行

  • Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容

  • Base64是把3个字节变成4个字节,所以,Base64编码的长度永远是4的倍数,编码长度不够4的倍数时,会添加“=”号,但“=”用在URL、Cookie里面会造成歧义,所以很多Base64编码后会把“=”号去掉,但去掉“=”后的编码字节不够4的倍数时,需要编写函数去添加“=”,如下

  • 练习:

    • 请写一个能处理去掉=的base64解码函数

    • import base64
      def safe_base64_decode(s):
          # print(s)
          # print(type(s))
          remainder = len(s) % 4
          if remainder:
              for _ in range(4-remainder):      
                  s = s + b'='
      
          return base64.b64decode(s)
      
      
      assert b'abcd' == safe_base64_decode(b'YWJjZA=='), safe_base64_decode('YWJjZA==')
      assert b'abcd' == safe_base64_decode(b'YWJjZA'), safe_base64_decode('YWJjZA')
      print('ok')
      
  • Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据

strut

https://www.liaoxuefeng.com/wiki/1016959663602400/1017685387246080

  • 处理字节的数据类型

hashlib

https://www.liaoxuefeng.com/wiki/1016959663602400/1017686752491744

  • Python 的hashlib提供了常见的摘要算法,如MD5、SHA1等等

  • 练习

    • 根据用户输入的登录名和口令模拟用户注册,计算更安全的MD5:
      
    • import random, hashlib
      db = {}  # 用于存储用户名和口令
      
      # md5加密
      def get_md5(s):
          return hashlib.md5(s.encode('utf-8')).hexdigest()
      
      # 用户类
      class User(object):
          def  __init__(self, username, password):
              self.username = str(username)
              self.salt = ''.join([ chr(random.randint(30,200)) for i in range(20)])
              self.password = get_md5(str(password) + self.salt)
              
      # 注册时加密
      def register(username, password):
          # 实例化User对象
          s = User(username, password)
          # 将对象存入字典db
          db[username] = s
      
      # 添加用户口令信息
      register('bob', 'abc999')
      register('michael',123456)
      register('alice','alice2008')
      print(db)
      
      # 获取db字典中User对象中的password值
      # db_dict = {user:password.password for user,password in db.items()}
      # print(db_dict)
      
      # 登录
      def login(username, password):
          user = db[str(username)]
          return user.password == get_md5(str(password) + user.salt)
      
      # 测试:
      assert login('michael', '123456')
      assert login('bob', 'abc999')
      assert login('alice', 'alice2008')
      assert not login('michael', '1234567')
      assert not login('bob', '123456')
      assert not login('alice', 'Alice2008')
      print('ok')
      

hmac

  • hmac算法相当于封装了“哈希算法+salt”

  • 将上一节的练习使用hmac做修改

  • import random,  hmac
    db = {}  # 用于存储用户名和口令
    
    # hmac函数md5加密
    def hmac_md5(key, s):
        # 使用hmac函数
        return hmac.new(key.encode('utf-8'), str(s).encode('utf-8'),digestmod='MD5').hexdigest()
    
    # 用户类
    class User(object):
        def  __init__(self, username, password):
            self.username = str(username)
            self.key = ''.join([ chr(random.randint(30,200)) for i in range(20)])
            self.password = hmac_md5(self.key, password)
            
    # 注册时加密
    def register(username, password):
        # 实例化User对象
        s = User(username, password)
        # 将对象存入字典db
        db[username] = s
    
    # 添加用户口令信息
    register('bob', 'abc999')
    register('michael',123456)
    register('alice','alice2008')
    print(db)
    
    # 获取db字典中User对象中的password值
    # db_dict = {user:password.password for user,password in db.items()}
    # print(db_dict)
    
    # 登录
    def login(username, password):
        user = db[str(username)]
        return user.password == hmac_md5(user.key, password)
    
    # 测试:
    assert login('michael', '123456')
    assert login('bob', 'abc999')
    assert login('alice', 'alice2008')
    assert not login('michael', '1234567')
    assert not login('bob', '123456')
    assert not login('alice', 'Alice2008')
    print('ok')
    

itertools

  • itertools提供了非常有用的用于操作迭代对象的函数

  • itertools模块提供的全部是处理迭代功能的函数,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算

  • itertools.count(x,steps=n):会创建一个无限的迭代器,从x开始步长为n

  • itertools.repeat(x, y):无限的迭代器,创建y个x

  • itertools.cycle(x):无限迭代器,x为序列,无限重复序列中的值

  • itertools.takewhile(func, x):根据条件判断来截取出一个有限序列的迭代器,有点类似filter()但又不一样

  • itertools.chain(x,y):可以把一组迭代对象串联起来,形成一个更大的迭代器

  • itertools.groupby(x):把x中相邻的重复元素挑出来放在一起

  • 练习

    • 计算圆周率可以根据公式:
      
      利用Python提供的itertools模块,我们来计算这个序列的前N项和:
      
    • import itertools
      def pi(N):
          ' 计算pi的值 '
          # step 1: 创建一个奇数序列: 1, 3, 5, 7, 9, ...
          odd_iter = itertools.count(1,step=2)
          # step 2: 取该序列的前N项: 1, 3, 5, 7, 9, ..., 2*N-1.
          odd_n = itertools.takewhile(lambda x: x <= 2*N-1,odd_iter)
          # step 3: 添加正负符号并用4除: 4/1, -4/3, 4/5, -4/7, 4/9, ...
          tmp = map(lambda x: 4 / x if x % 4 ==1 else -4 / x, odd_n)
          # step 4: 求和:
          return sum(tmp)
      
      # 测试:
      print(pi(10))
      print(pi(100))
      print(pi(1000))
      print(pi(10000))
      assert 3.04 < pi(10) < 3.05
      assert 3.13 < pi(100) < 3.14
      assert 3.140 < pi(1000) < 3.141
      assert 3.1414 < pi(10000) < 3.1415
      print('ok')
      

contextlib

  • contextlib模块中提供了contextmanager、closing等函数,方便我们对资源做上下文管理

  • 读写文件资源时,读写完后要使用"文件对象.close()"方法关闭文件对象,还是挺麻烦的

  • @contextmanager

    • 可以使用with…as语句,with语句调用的是fp(file point文件指针)对象中__enter__和__exit__方法,为了不写这两个方法,可以使用contextmanager装饰器

    • from contextlib import contextmanager
      
      class Query(object):
      
          def __init__(self, name):
              self.name = name
      
          def query(self):
              print('Query info about %s...' % self.name)
      
      @contextmanager
      def create_query(name):
          print('Begin')
          q = Query(name)
          yield q
          print('End')
          
      # @contextmanager这个decorator接受一个generator,用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了:
      with create_query('Bob') as q:
          q.query()
      
  • closing()

    • 如果一个对象没有实现上下文(enter和exit方法),它就不能用于with语句,这时候可以使用closing()函数将对象变成上下文对象

    • from contextlib import closing
      from urllib.request import urlopen
      
      with closing(urlopen('https://www.python.org')) as page:
          for line in page:
              print(line)
      
    • closing也是一个经过@contextmanager装饰的generator,这个generator编写起来其实非常简单

    • @contextmanager
      def closing(thing):
          try:
              yield thing
          finally:
              thing.close()
      
  • 练习:

    • # 定义一个把任意对象变成可以用with调用的对象的函数。并简单的打印调用过程中产生的错误。

    • # 定义一个把任意对象变成可以用with调用的对象的函数。并简单的打印调用过程中产生的错误。
      from contextlib import contextmanager
      class Query(object):
          
          def __init__(self, name):
              self.name = name
      
          def query(self, q_name):
              print(f'Query: {q_name}')
              if self.name == q_name:
                  print(f"Yes! it's {q_name}")
              else:
                  raise KeyError(f"No! it isn't {q_name}")
                  
      
      @contextmanager
      def catch_exc(obj):
          try:
              yield obj
          except Exception as e:
              print(e)
      
      with catch_exc(Query('Bob')) as q:
          q.query('Mechale')
      

urllib

  • urllib提供了一系列用于操作URL功能的模块

  • urllib提供的功能就是利用程序去执行各种HTTP请求。如果要模拟浏览器完成特定功能,需要把请求伪装成浏览器。伪装的方法是先监控浏览器发出的请求,再根据浏览器的请求头来伪装,User-Agent头就是用来标识浏览器的。

  • request模块

    • Get

      • from urllib import request
        req = request.Request('http://www.douban.com/')
        req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
        with request.urlopen(req) as f:
            date = f.read()
            print('Status:', f.status, f.reason)
            for k, v in f.getheaders():
                print('%s: %s' % (k, v))
            print('Data:',date.decode('utf-8'))
        
    • Post:相对于Get方式,多做一步表单传输操作

      • from urllib import request, parse
        print('Login to weibo.cn...')
        username = input('username:')
        password = input('Password:')
        login_data = parse.urlencode(
            [
                ('username',username),
                ('password',password),
                ('entry','nweibo'),
                ('savestate','1'),
            ]
        )
        
        req = request.Request('https://passport.weibo.cn/sso/login')
        req.add_header('Origin','https://passport.weibo.cn')
        req.add_header('Referer','https://passport.weibo.cn/signin/login')
        req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36')
        
        with request.urlopen(req, data=login_data.encode('utf-8')) as f:
            print('Status',f.status, f.reason)
            for k, v in f.getheaders():
                print('%s: %s' % (k, v))
            print('Data:', f.read().decode('utf-8'))
        
        
    • handler:如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler来处理,示例代码如下:

      • from urllib import request
        proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
        
        proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
        
        proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
        
        opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
        
        with opener.open('http://www.example.com/login.html') as f:
            pass
        
  • 练习:利用urllib读取JSON,然后将JSON解析为Python对象:

    • import json
      from urllib import request
      
      def fetch_data(url):
          with request.urlopen(str(url)) as f:
              return json.loads(f.read().decode('utf-8'))
          return None
      
      # 测试
      URL = 'https://yesno.wtf/api'
      data = fetch_data(URL)
      print(data)
      assert data['answer']== 'yes' and (data['forced']== False)
      print('ok')
      

XML

DOM vs SAX

  • 操作XML由两种方法,一种是DOM一种是SAX。
  • DOM会把XML读入内存中,解析为树,缺点是占用内存大,解析慢,优点是可以任意遍历树的节点。
  • SAX是流模式,边读边解析,占用内存小,解析快,缺点是需要自己处理解析过程中遇到的事件。

通常优先考虑SAX,因为DOM太占用内存

SAX事件有三个需要自己处理:start_element事件(读取开头标签时)、char_data事件(读取数据时)、end_element事件(读取结束标签时)

from xml.parsers.expat import ParserCreate

class DefaultSaxHandler(object):
    def start_element(self, name, attrs):
        print('sax:start_element: %s, attrs: %s' % (name, str(attrs)))

    def end_element(self, name):
        print('sax:end_element: %s' % name)

    def char_data(self, text):
        print('sax:char_data: %s' % text)

xml = r'''<?xml version="1.0"?>
<ol>
    <li><a href="/python">Python</a></li>
    <li><a href="/ruby">Ruby</a></li>
</ol>
'''

handler = DefaultSaxHandler()
parser = ParserCreate()
parser.StartElementHandler = handler.start_element
parser.EndElementHandler = handler.end_element
parser.CharacterDataHandler = handler.char_data
parser.Parse(xml)

使用字符串的方式生成XML

L = []
L.append(r'<?xml version="1.0"?>')
L.append(r'<root>')
L.append(encode('some & data'))
L.append(r'</root>')
return ''.join(L)

注意:如果要生成复杂的XML,建议使用JSON

HTMLParser

编写一个搜索引擎步骤:1、用爬虫将网站页面爬取下来。2、解析该HTML页面

由于HTML语法没有XML那么严格,所以不能用标准的DOM或SAX方式去解析HTML。

使用HTMLParser来解析HTML。

from html.parser import HTMLParser
from html.entities import name2codepoint


class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print('starttag:%s' % tag)

    def handle_endtag(self, tag):
        print('endtag:%s' % tag)

    def handle_startendtag(self, tag, attrs):
        print('startendtag:%s ' % tag)

    def handle_data(self, data):
        print('data:',data)

    def handle_comment(self, data):
        print('comment:<!——', data, '——>')

    def handle_charref(self, name):
        print('charref:&#%s' % name)


html = r'''<html>
<head></head>
<body>
<!-- test html parser -->
    <p>Some <a href=\"#\">html</a> HTML&nbsp;tutorial...<br>END</p>
</body></html>'''

parser = MyHTMLParser()
parser.feed(html)

feed()方法可以调用多次,也就是不一定要一次性把HTML字符串都塞进去,可以一部分一部分塞进去。

特殊字符有两种,一种时英文表示的&nbsp;,一种时数字表示的&#1234;,这两种字符都可以通过Parser解析出来。

利用HTMLParser,可以把网页中的文本、图像等解析出来。

13 常用第三方模块

可以使用Anaconda管理工具,不用麻烦的去一个一个安装所需要的库

PIL(Python Imaging Library )官方文档:https://pillow.readthedocs.org/

PIL提供了操作图像的强大功能,可以通过简单的代码完成复杂的图像处理。

由于PIL仅支持到Python 2.7,加上年久失修,有一群志愿者在PIL的基础上创建了兼容的版本叫做Pillow

Pillow

图片缩放与图片模糊滤镜

# #图片缩放

# from PIL import Image
# # 打开一个图片,并获取对象
# im = Image.open(r'D:\Data\Desktop\cat.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(r'D:\Data\Desktop\cat.jpg')
# 应用模糊滤镜
im2 = im.filter(ImageFilter.BLUR)
# 保存图片
im2.save('blur.jpg','jpeg')

使用pillow生成验证码字母图片,逻辑如下:

  • 1 生成字体的函数
  • 2 生成RGB颜色的函数
  • 3 在pillow提供的函数中应用上上述两个函数
from PIL import Image, ImageFont, ImageDraw,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))

# 指定图片长度
width = 60*4
height = 60
# 创建Image对象
image = Image.new('RGB',(width,height),(255,255,255))

# 创建Font对象
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf',36)
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),text=rndChar(),font=font,fill=rndColor2())

# 添加模糊功能
image = image.filter(ImageFilter.BLUR)
image.save('code.jpg','jpeg')

requests

requests和python内置的urllib模块都是用于访问网络资源的。

相对于python内置的urllib模块,requests模块用起来比较方便,而且还封装了很多实用的高级功能。

pip3 install requests

网络请求方式有:GET、POST、PUT、DELETE

GET请求的话, 需要注意传入的url是否正确,url是否携带参数,是否需要携带请求头信息

POST请求:需要传入data数据,data的类型为字典或json类型

参数:url、headers(请求头信息)、data(post请求数据)、files(保存文件)、json(将data数据序列化为json格式)、cookies、timeout、

对象属性/方法:status_code、text、content(bytes类型数据)、encoding(编码格式)、

参考文章:https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448

chardet

chardet第三方库是用于检测bytes对象数据的编码格式,虽然Python提供了Unicode表示的strbytes两种数据类型,并且可以通过encode()decode()方法转换,但是需要猜测编码类型,就比较麻烦。

pip3 install chardet
import chardet

res = chardet.detect(b'Hello, world!')
print(res)
# {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}

confidence表示概率,1.0为100%

psutil

psutil = process and system utilities,去获取系统运行的状态的一个第三方工具

去获取cpu信息、内存信息、进程线程信息、磁盘使用信息、网络连接信息等

Linux:pstopfree

Linux/Unix/OSX/Windows:psutil

pip3 install psutil
# 获取CPU信息
import psutil
# cpu逻辑数量
print(psutil.cpu_count())
# cpu物理核心数
print(psutil.cpu_count(logical=False))

# 统计cpu的用户/系统/空间时间
print(psutil.cpu_times())


# 计算CPU使用率
# percpu指的是每个cpu,interval是指规定间隔时间去检测
# for x in range(10):
#     print(psutil.cpu_percent(interval=1,percpu=True))


# 获取物理内存,会显示总内存大小,可用内存,已用内存
print(psutil.virtual_memory())

# 获取交换内存
print(psutil.swap_memory())

# 获取磁盘信息:磁盘分区、磁盘使用率、磁盘IO信息
# print(psutil.disk_partitions())
# print(psutil.disk_usage('/'))
# print(psutil.disk_io_counters())

# 获取网络信息:网络接口、网络连接信息
# print(psutil.net_io_counters()) # 网络读写字节/包的个数
# print(psutil.net_if_addrs())    # 网络接口信息
# print(psutil.net_if_stats())    # 网络接口状态
print('---------------------------------')
# 获取当前网络连接信息
# print(psutil.net_connections())

# 获取进程信息
# print(psutil.pids())    # 所有进程ID
p = psutil.Process(10332)     # 获取指定进程ID对象
print(p.name())         # 进程名
print(p.exe())          # 进程exe路径
# print(p.cwd())        # 进程当前工作目录
print(p.cmdline())      # 进程启动的命令行
print(p.ppid())         # 父进程ID
print(p.parent())       # 父进程
print(p.children())     # 子进程列表
print(p.status())       # 状态
print(p.username())     # 用户名
print(p.create_time())  # 显示的是时间戳
import datetime
print(datetime.datetime.fromtimestamp(p.create_time())) # 将时间戳转为正常格式时间显示

# print(p.terminal())     # 进程的终端信息
print(p.cpu_times())    # 进程使用的CPU时间
print(p.memory_info())  #进程使用的内存
# print(p.open_files())   #进程打开的文件
# print(p.connections())  # 进程相关网路连接
print(p.num_threads())    # 进程的线程数
# print(p.threads())        # 进程的线程
# print(p.environ())          # 进程的环境变量
print(psutil.test())        # 模拟ps命令显示进程信息
# print(p.terminate())    # 结束进程

14 虚拟环境

虚拟环境可以创建多个运行环境,可以安装不同版本的包。可以解决版本冲突问题。

虚拟环境管理工具有很多:pipenv、virtualenv、virtualenvwrapper

参考文章:

https://zhuanlan.zhihu.com/p/60647332

https://zhuanlan.zhihu.com/p/55781739

15 图形界面

Python支持多种图形界面的第三方库:Tk、wxWidgets、Qt、GTK

Tk是一个图形库,支持多个操作系统,使用Tcl语言开发,Tk会调用操作系统提供给本地的GUI接口,完成最终的GUI。

Tkinter是Python内置的库,Tkinter封装了访问Tk的接口,所以我们直接去调用Tkinter提供的接口就可以了。

示例1:简单的GUI程序

  • Frame是所有Widget的父容器,在GUI中每个Button、Label、输入框等都是一个Widget。
  • Frame是可以容纳Widget的Widget,所有Widget组合起来就是一棵树。
  • pack()方法把Widget加入到父容器中,并实现布局。pack()是简单的布局,grid()可以实现复杂的布局。
  • GUI程序的主线程负责监听来自操作系统的消息,并依次处理每一条消息。因此,如果消息处理非常耗时,就需要在新线程中处理。
from tkinter import *

class Application(Frame): # 继承Frame
    def __init__(self,master=None):
        Frame.__init__(self, master)
        self.pack()				
        self.createWidgets()

    def createWidgets(self):
        self.helloLabel = Label(self, text='Hello world!')
        self.helloLabel.pack()	# 将helloLabel这个Widget添加到父容器Frame中
        self.quitButton = Button(self, text='Quit', command=self.quit)
        self.quitButton.pack()

app = Application()
# 设置标题窗口
app.master.title('Hello World!')
# 启动消息循环
app.mainloop()

示例2:加入一个文本框

# from tkinter import *
#
# class Application(Frame):
#     def __init__(self,master=None):
#         Frame.__init__(self, master)
#         self.pack()
#         self.createWidgets()
#
#     def createWidgets(self):
#         self.helloLabel = Label(self, text='Hello world!')
#         self.helloLabel.pack()
#         self.quitButton = Button(self, text='Quit', command=self.quit)
#         self.quitButton.pack()
#
# app = Application()
# app.master.title('Hello World!')
# app.mainloop()

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()

注意:Python内置的Tkinter就可以满足大部分场景的GUI程序要求,如果是非常复杂的程序,建议使用操作系统原生支持的语言和库来编写。

海归绘图

1966年,Seymour Papert和Wally Feurzig发明了一种专门给儿童学习编程的语言----LOGO语言,它的特色就是通过编程指挥一个小海龟(turtle)在屏幕上绘图。

海龟绘图(Turtle Graphics)后来被移植到各种高级语言中,Python内置了turtle库,基本上100%赋值了原始的Turtle Graphics的所有功能。

示例1:绘制长方形

# 导入turtle包的所有内容:
from turtle import *

# 设置笔刷宽度
width(4)

# 前进
forward(200)
# 向右转90°
right(90)

#设置笔的颜色
pencolor('red')
forward(100)
right(90)

pencolor('blue')
forward(200)
right(90)

pencolor('green')
forward(100)
right(90)

# 调用done()让窗口进去消息循环,等待被关系
done()

示例2:画五角星

from turtle import *

def drawStar(x, y):
    pu()
    goto(x, y)
    pd()
    # set heading: 0
    seth(0)
    for i in range(5):
        fd(40)
        rt(144)

for x in range(0, 250, 50):
    drawStar(x, 0)

done()

https://www.liaoxuefeng.com/wiki/1016959663602400/1249593505347328

16 网络编程

网络通信:两个进程间的通信

Python进行网络编程:在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。

Socket是网络编程的一个抽象概念。

通常我们用一个Socket表示"打开了一个网络连接",而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

TCP/IP简介

现有计算机后有互联网,计算机之间想要连接起来,需要通过互联网,而互联网需要有一些规定(协议)对于计算机之间的连接。

互联网协议包含了上百种协议标准,但最重要的两个协议是TCP协议和IP协议,所以大家把互联网的协议简称为TCP/IP协议。

互联网上每个计算机唯一标识是IP地址,但如果一台计算机同时接入到两个或多个网络中,比如路由器,则会有两个或多个IP地址,所以IP地址对应的实际上是计算机的网络接口通常是网卡

IP协议:负责将数据从一台计算机通过网络发送到另一台计算机。数据是一小块一小块以IP包的方式被发送出去,路由器负责将IP包转发出去。IP包特点是按快发送,途径多个路由,但不能保证到达,也不保证顺序到达。

TCP协议:建立在IP协议的基础上,TCP协议负责在两台计算机之间建立可连接,保证数据包按顺序

到达。握手建立连接、丢失重发。报文中包含传输数据、源IP地址、目标IP地址、源端口号、目标端口号。

注意:进程间通讯需要知道各自的IP地址和端口号,一个进程可能同时与多个计算机建立连接,因为它会申请多个端口号

TCP和UDP区别

1 是否面向连接(即是否可靠)

  • TCP面向连接,可靠传输
  • UDP面向无连接,不可靠传输

2 以什么方式传输数据

  • TCP以字节流方式
  • UDP以报文方式

3 速度方面比较

  • TCP较慢,TCP的IP包首部是20个字节
  • UDP较快,UDP的IP包首部是8个字节

TCP编程

TCP建立的是可靠连接,双方可以以流的形式发送数据。会建立三次握手流程,保证数据的到达。

TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。

客户端-服务端模式:

  • **服务端:**创建socket对象并指定连接类型socket.socket(),绑定bind()一个IP和端口用于监听,开启一个监听listen()的端口,等待客户端的连接accept(),安排线程或进程给对每个客户端连接,接收recv()客户端发过来的消息,并给客户端发送响应send()
  • **客户端:**创建socket对象并指定连接类型socket.socket(),连接指定服务端的IP和端口connect(),发送数据和接收数据send(),recv()

示例1:

import socket
# AF_INET是IPv4协议,AF_INET6是IPv6,SOCK_STREAM面向流的TCP协议
# 创建一个socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 建立连接
# 80是web服务器的标准端口
s.connect(('www.sina.com.cn',80))

# 发送数据
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\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)
print(data)
s.close()

header, html = data.split(b'\r\n\r\n',1)
print(header.decode('utf-8'))
print(html.decode('utf-8'))
with open('sina.html','wb') as f:
    f.write(html)

示例2:客户端和服务端

服务端:echo_server.py

# TCP服务端
import socket
import threading
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定一个IP和端口用于接收客户端的连接
s.bind(('127.0.0.1', 9999))

# 开始监听端口,指定等待连接的最大数量
s.listen(5)

print('Waiting for connection...')
def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')
    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()
    print('Connection from %s:%s closed.' % addr)


while True:
    # 接受一个新连接:
    sock, addr = s.accept()
    # 创建新线程来处理TCP连接:
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()

客户端:echo_client.py

#  TCP客户端
import socket

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

s.connect(('127.0.0.1',9999))
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael',b'Tracy',b'Sarah']:
    s.send(data)

s.send(b'exit')
s.close()

UDP编程

相对于TCP连接,UDP连接客户端不需要和服务端建立连接的过程,服务端也不需要去监听端口

这里省略掉多线程操作,想要多线程操作可以模仿TCP代码

示例:

# UDP服务端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1',9999))

while True:
    # recvfrom接收data和addr
    data, addr = s.recvfrom(1024)
    s.sendto(b'Hello:%s'% data,addr)
# UDP客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

for data in [b'Mic',b'Tra',b'Halo']:
    s.sendto(data,('127.0.0.1',9999))
    print(s.recv(1024).decode('utf-8'))
s.close()

17 电子邮件

电子邮件的理解:

先从普通邮件讲起:给朋友写信

  • 1 找个写邮件,装进信封,信封上写上地址和贴上邮票
  • 2 信封交给当地邮局
  • 3 信封由当地邮局转到其他邮局,再继续转,直到转到你朋友地方邮局。
  • 4 邮局叔叔不会直接给到你朋友手上,而是将信封放到朋友住址的邮箱里面
  • 5 朋友回家检查邮箱取到邮件

电子邮件:me@163.comfriend@qq.com发电子邮件

  • 1 使用OutlookFoxmail之类的软件写好邮件,填上对方地址,发送过去。这些软件被称为MUA:Mail User Agent 邮件用户代理
  • 2 Email从MUA发送出去不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent 邮件传输代理。例如发件人是163.com结尾的,则邮件会先发送到网易的MTA,再由网易的MTA发往腾讯的MTA
  • 3 Email到达腾讯的MTA后,腾讯的MTA会把Email投递到MDA:Mail Delivery Agent 邮件投递代理。MDA是腾讯的某个服务器里的文件或特殊的数据库,这个长期保存邮件的地方被称为电子邮箱。
  • 4 朋友开机,打开MUA,然后通过MUA从MDA上把邮件取到自己的电脑。

电子邮件的旅程:

发件人 -> MUA -> MTA ->若干个MTA -> MTA -> MDA <- MUA <- 收件人

程序发送邮件和接收邮件:

  • 1 编写MUA把邮件发送到MTA
  • 2 编写MUA从MDA上收邮件

发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到MTA也是SMTP协议

收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前时版本3,俗称POP3;IMAP:Internet Message Access Protocol,目前是版本4,优点是不但能取邮件,还能直接操作MDA上存储的邮件,比如将邮件从收信箱移到垃圾箱,等等。

邮件客户端在发邮件时,需要将邮件先发送到MTA,这时需要配置SMTP服务器,假设自己是163.com,则需要先配置SMTP服务器对应163的地址:smtp.163.com,为了证明你是163的用户,SMTP服务器需要你填写邮箱地址和邮箱口令,只有这样MUA才能正常地把Email通过SMTP协议发送给MTA。

类似的,从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以Foxmail之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。

SMTP发送邮件

https://www.liaoxuefeng.com/wiki/1016959663602400/1017790702398272#0

SMTP是发送邮件的协议,Python内置了对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件

Python对SMTP的支持有smtplibemail两个模块,email负责构造邮件,smtplib负责发送邮件

示例1:纯文本

from email.mime.text import MIMEText
msg = MIMEText('hellp, send by Python...','plain','utf-8')

from_addr = input('From:')
password = input('Password:')
to_addr = input('To:')
smtp_server = input('SMTP server:')

import smtplib
server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口25
server.set_debuglevel(1)                # 打印出和SMTP服务器交互的所有信息
server.login(from_addr,password=password) # 登录SMTP服务器
server.sendmail(from_addr,[to_addr],msg.as_string()) # 发送邮件,因为正文是一个str,所以使用as_string()将MIMEText对象转变成str
server.quit()   # 关闭服务

示例2:纯文本,添加主题、收件人名字、发件人名字

from email.mime.text import MIMEText
from email.header import Header
from email.utils import parseaddr,formataddr
import smtplib

# 格式化邮件地址
def _format_addr(s):
    name, addr = parseaddr(s)
    print(name,addr)
    return formataddr((Header(name, 'utf-8').encode(),addr))

from_addr = input('From:')
password = input('Password:')
to_addr = input('To:')
smtp_server = input('SMTP server:')

msg = MIMEText('hello, 我是本人,这封邮件是用来测试的','plain','utf-8')
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
# msg['To'] = _format_addr('管理员<%s>'% (to_addr))
msg['Subject'] = Header('来自benrende问候。。。', 'utf-8').encode()


server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口25
server.set_debuglevel(1)                # 打印出和SMTP服务器交互的所有信息
server.login(from_addr,password=password) # 登录SMTP服务器
server.sendmail(from_addr,[to_addr],msg.as_string()) # 发送邮件,因为正文是一个str,所以使用as_string()将MIMEText对象转变成str
server.quit()   # 关闭服务

注意:我们编写了一个函数_format_addr()来格式化一个邮件地址。注意不能简单地传入name <addr@example.com>,因为如果包含中文,需要通过Header对象进行编码。msg['To']接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可,如果要发给多个邮箱则在sendmail()方法中指定即可。

示例3:发送附件邮件,正文是纯文本或html

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart, MIMEBase
from email import encoders
from email.header import Header
from email.utils import parseaddr, formataddr
import smtplib


# 格式化邮件地址
def _format_addr(s):
    name, addr = parseaddr(s)
    print(name, addr)
    return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From:')
password = input('Password:')
to_addr = input('To:')
smtp_server = input('SMTP server:')

# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

# 邮件正文是MIMEText:
# msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))
# 将附件嵌入到正文中
msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
    '<p><img src="cid:0"></p>' +
    '</body></html>', 'html', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
# with open('/Users/michael/Downloads/test.png', 'rb') as f:
with open(r'D:\Data\Desktop\Snipaste_2020-08-31_15-21-04.png', 'rb') as f:
    # 设置附件的MIME和文件名,这里是png类型:
    mime = MIMEBase('image', 'png', filename='test.png')
    # 加上必要的头信息:
    mime.add_header('Content-Disposition', 'attachment', filename='test.png')
    mime.add_header('Content-ID', '<0>')
    mime.add_header('X-Attachment-Id', '0')
    # 把附件的内容读进来:
    mime.set_payload(f.read())
    # 用Base64编码:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)

server = smtplib.SMTP(smtp_server, 25)  # SMTP协议默认端口25
server.set_debuglevel(1)  # 打印出和SMTP服务器交互的所有信息
server.login(from_addr, password=password)  # 登录SMTP服务器
server.sendmail(from_addr, [to_addr], msg.as_string())  # 发送邮件,因为正文是一个str,所以使用as_string()将MIMEText对象转变成str
server.quit()  # 关闭服务

示例4:同时支持HTML和Plain格式

msg = MIMEMultipart('alternative')
msg['From'] = ...
msg['To'] = ...
msg['Subject'] = ...

msg.attach(MIMEText('hello', 'plain', 'utf-8'))
msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))
# 正常发送msg对象.

示例5:加密SMTP,不加密则是明文传输,发送邮件的整个过程可能会被窃听,这里以Gmail为例,不同的邮件服务商端口不同

smtp_server = 'smtp.gmail.com'
smtp_port = 587 # gmail的端口
server = smtplib.SMTP(smtp_server, smtp_port)
# server = smtplib.SMTP_SSL("smtp.qq.com", 465) # qq的端口
server.starttls()
# 剩下的代码和前面的一模一样:
server.set_debuglevel(1)
...

**总结:**使用Python的smtplib发送邮件,主要注意两点:

第1点,掌握各种邮件邮件类型的构造方法。

第2点,设置好邮件头就可以顺利发出。

Message对象:邮件对象

MIMEText对象:文本邮件对象

MIMEImage对象:作为附件的图片对象

MIMEMultipart对象:可以把多个对象组合起来,配合attach方法

MIMEBase对象:可以表示任何对象

MEssage
+-MIMEBase
	+-MIMEMultipart
	+-MIMENonMultipart
		+-MIMEMessage
		+-MIMEText
		+-MIMEImage

https://docs.python.org/3/library/email.mime.html

POP3收取邮件

收取邮件:编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或手机上

收取的文本还不是一个可阅读的文本,需要email模块提供的各种类来解析原始文本,变成可阅读的邮件对象。

收取邮件分两步:

  • 1 用Python内置模块poplib把邮件原始文本下载到本地。
  • 2 用email解析原始文本,还原为邮件对象。

示例1:收取邮件

import poplib

# 输入邮件地址, 口令和POP3服务器地址:
email = input('Email: ')
password = input('Password: ')
pop3_server = input('POP3 server: ')

# 连接到POP3服务器:
server = poplib.POP3(pop3_server)
# 可以打开或关闭调试信息:
server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字:
print(server.getwelcome().decode('utf-8'))

# 身份认证:
server.user(email)
server.pass_(password)

# stat()返回邮件数量和占用空间:
print('Messages: %s. Size: %s' % server.stat())
# list()返回所有邮件的编号:
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]
print(mails)

# 获取最新一封邮件, 注意索引号从1开始:
index = len(mails)
resp, lines, octets = server.retr(index)

# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍后解析出邮件:
msg = Parser().parsestr(msg_content)

# 可以根据邮件索引号直接从服务器删除邮件:
# server.dele(index)
# 关闭连接:
server.quit()

示例2:解析邮件

from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

import poplib

# 输入邮件地址, 口令和POP3服务器地址:
email = input('Email: ')
password = input('Password: ')
pop3_server = input('POP3 server: ')

# 连接到POP3服务器:
server = poplib.POP3(pop3_server)
# 可以打开或关闭调试信息:
# server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字:
# print(server.getwelcome().decode('utf-8'))

# 身份认证:
server.user(email)
server.pass_(password)

# stat()返回邮件数量和占用空间:
# print('Messages: %s. Size: %s' % server.stat())
# list()返回所有邮件的编号:
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]
# print(mails)

# 获取最新一封邮件, 注意索引号从1开始:
index = len(mails)
resp, lines, octets = server.retr(index)

# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍后解析出邮件:
msg = Parser().parsestr(msg_content)

# 这个Message对象本身可能是一个MIMEMultipart对象,即包含嵌套的其他MIMEBase对象,嵌套可能还不止一层。
# 所以我们要递归地打印出Message对象的层次结构:
# indent用于缩进显示:
def print_info(msg, indent=0):
    if indent == 0:
        for header in ['From', 'To', 'Subject']:
            value = msg.get(header, '')
            if value:
                if header == 'Subject':
                    value = decode_str(value)
                else:
                    hdr, addr = parseaddr(value)
                    name = decode_str(hdr)
                    value = u'%s <%s>' % (name, addr)
            print('%s%s: %s' % ('  ' * indent, header, value))
    if (msg.is_multipart()):
        parts = msg.get_payload()
        for n, part in enumerate(parts):
            print('%spart %s' % ('  ' * indent, n))
            print('%s--------------------' % ('  ' * indent))
            print_info(part, indent + 1)
    else:
        content_type = msg.get_content_type()
        if content_type == 'text/plain' or content_type == 'text/html':
            content = msg.get_payload(decode=True)
            charset = guess_charset(msg)
            if charset:
                content = content.decode(charset)
            print('%sText: %s' % ('  ' * indent, content + '...'))
        else:
            print('%sAttachment: %s' % ('  ' * indent, content_type))

# 邮件的Subject或者Email中包含的名字都是经过编码后的str,要正常显示,就必须decode:
def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value

# 文本邮件的内容也是str,还需要检测编码,否则,非UTF-8编码的邮件都无法正常显示:
def guess_charset(msg):
    charset = msg.get_charset()
    if charset is None:
        content_type = msg.get('Content-Type', '').lower()
        pos = content_type.find('charset=')
        if pos >= 0:
            charset = content_type[pos + 8:].strip()
    return charset


print_info(msg)

小结:用Python的poplib模块收取邮件分两步:第一步是用POP3协议把邮件获取到本地,第二步是用email模块把原始邮件解析为Message对象,然后,用适当的形式把邮件内容展示给用户即可。

18 访问数据库

数据库存在的意义:

当程序运行时,数据都是在内存中的。当程序终止时,需要将需要的数据存储到磁盘文中。

如何定义数据的存储格式就是一个大问题。可以用文本文件保存,但是不能做到快速查询,只有把数据全部读到内存中才能去遍历获取其中的数据,而且有些数据太大,大小超过了内存的大小。

为了便于程序保存和读取数据,而且能直接通过条件快速查询到指定的数据,数据库出现了,数据库就是专门用于集中存储和查询的软件

关系型数据库(SQL)和非关系型数据库(NoSQL)区别

  • 1 数据存储方式不同
    • SQL:表的形式,数据是分开来存储,分oSQL多个表
    • No’SQL:键值对、文档、图结构,数据是一大块存储在一起
  • 2 扩展方式不同
    • SQL:纵向扩展
    • NoSQL:横向扩展
  • 3 对事物的支持不同
    • SQL:支持对事务原子性细粒度控制,并且易于回滚事务
    • NoSQL:虽然也支持事务操作,但稳定性方面没法和SQL比较
  • 4 性能
    • SQL:性能较低
    • NoSQL:数据的获取不需要经过SQL层的解析,性能非常高

关系型数据库类别:

  • 付费的商用数据库:Oracle,SQL Server,DB2,Sybase
  • 开源数据库:MySQL,PostgreSQL,sqlite

使用SQLite

数据库由于都差不多的,Python中的使用方法都是调用对应的库,连接数据库,使用SQL写,提交,关闭连接。亦或是使用SQLAlchemy,所以没怎么总结了。可以自己去看视频来具体学习

https://www.liaoxuefeng.com/wiki/1016959663602400/1017801397501728

使用MySQL

使用SQLAlchemy

19 Web开发

web开发应用阶段:

  • 1 静态Web页面,无法与用户交互
  • 2 CGI:Common Gateway Interface,用C/C++编写的,可以和用户交互但是使用低级语言编写的不适合Web这种需要经常修改的开发
  • 3 ASP/JSP/PHP。ASP是微软推出的VBScript脚本写成的Web开发技术,JSP使用Java来编写脚本,PHP本身则是开源的脚本语言。
  • 4 MVC:为了解决脚本语言嵌入HTML导致的可维护性差的问题。引入MVC:Model-View-Controller模式。

HTTP协议简介

HTTP:一种传输协议,作用在浏览器和服务器之间

HTML是用来定义网页的文本,HTTP是在网络上传输HTTP的协议,用于浏览器和服务器的通信。

一个HTTP包含Header和Body

HTTP GET请求仅请求资源,POST请求资源会附带用户数据

HTML简介

HTML简介:

HTML文档就是一系列的Tag组成,最外层的Tage是<html>。规范的HTML也包括<head>...</head><body>...</body>(注意:不要和HTTP的Header、Body搞混了),由于HTML是富文档模型,所以,还有一系列的Tag用来表示链接、图片、表格、表单等等。

CSS简介:

CSS是Cascading Style Sheets(层叠样式表)的简称,CSS用来控制HTML里的所有元素如何展现。

JavaScript简介:

JavaScript虽然名称有个Java,但和Java一点关系都没有。JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以外部链接到HTML中。

学习网站:https://www.w3school.com.cn/

WSGI接口

具体讲解看这个:https://www.liaoxuefeng.com/wiki/1016959663602400/1017805733037760

Web应用的本质:

  • 1 浏览器发送一个HTTP请求到服务器
  • 2 服务器接收到HTTP请求,并生成一个HTML文档
  • 3 服务器将HTML文档作为HTTP响应的Body发送给浏览器
  • 4 浏览器接收到HTTP响应,从HTTP Body中取出HTML文档并显示

静态服务器:Web应用先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件读取静态HTML,返回。

  • Apacha、Nginx、Lighttpd等静态服务器

**“动态”服务器:**如果要生成动态HTML用于返回的话,则静态服务器就帮不上忙了(因为只返回静态HTML),所以需要自己实现返回操作(接收HTTP请求、解析HTTP请求、发送HTTP响应)。刚好有对应的服务器软件可以帮助我们实现动态的HTML对应的HTTP操作。帮我们处理TCP链接,HTTP原始请求和响应格式。需要一个统一接口去完成

  • WSGI:Web Server Gateway Interface,网页服务网关接口,会帮我们去完成上诉操作

WSGI接口简单定义:要求Web开发者实现一个函数,就可以响应HTTP请求。

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']

上面的application()函数就是符合WSGI标准的一个HTTP处理函数(即Web项目后端需要完成的逻辑代码函数,接收HTTP请求信息,然后返回HTTP响应)

  • environ:一个包含所有HTTP请求信息的dict对象
  • start_response:一个发送HTTP响应的函数,即HTTP响应的Header信息
  • return:返回HTTP响应的Body信息

application()函数本身没有涉及到任何解析HTTP的部分,就是说底层代码我们不需要编写,只负责在更高层次上考虑如何响应请求就可以了。

但是这个application()如何调用,两个参数environstart_response也没法提供,返回的bytes也没法发给浏览器。

所以这个application()函数就必须由WSGI服务器来调用,有很多WSGI服务器,下面先使用Python内置的WSGI服务器来实现先

运行WSGI服务:

hello.py负责编写application()函数,wsgi_server.py负责充当WSGI服务器去调用appliction(),他们两个文件处于同一文件夹下

# hello.py
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    # 从environ中读取HTTP请求中的信息
    body = '<h1> Hello %s! </h1>' % (environ['PATH_INFO'][1:] or 'web')
    return [body.encode('utf-8')]

# wsgi_server.py
# 导入python内置的wsgi服务函数
from wsgiref.simple_server import make_server
# 导入自己的application处理HTTP请求函数
from hello import application

# 创建一个服务器,IP地址为空,端口是8000,处理函数是application
httpd = make_server('', 8000, application)
# 开始监听HTTP请求
httpd.serve_forever()
# 启动WSGI服务器
python wsgi_server.py
# 去浏览器访问http://127.0.0.1:8000/michael

小结

无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。

复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。

使用Web框架

编写HTTP处理函数的流程:

  • 1 获取到HTTP请求并进行解析,获取到对应的信息,例如:请求方式、请求的URL
  • 2 获取到对应的数据信息后,根据信息来编写函数去处理对应的URL

Web框架就是帮我们完成了第一步。

用Flask编写Web App示例:

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()

实际的Web App应该拿到用户名和口令后,会去数据库查询再对比,来判断用户是否登录成功。

处理Flask,常见的Python Web框架还有:

  • Django:全能型Web框架;
  • web.py:一个小巧的Web框架
  • Bottle:和Flask类似的Web框架
  • Tornado:Facebook的开源异步Web框架

有了Web框架后,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,这样编写Web App就更加简单了。

在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']来获取表单的内容

使用模板

https://www.liaoxuefeng.com/wiki/1016959663602400/1017806952856928

每个Web框架可能都有其对应的模板与模板语法,Flask支持的是jinja2。

常见的模板:

  • jinja2:用{% ... %}表示指令,{{ xx }}表示变量

  • Mako:用<% ... %>${xxx}

  • Cheetah:<% ... %>${ xxx }

  • Django:Django是一站式框架,{% ... %}{{ xxx }}

有了模板,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率。

20 异步IO

https://blog.csdn.net/xiaoduu/article/details/108313286

https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152

CPU的速度远远快于磁盘、网络等IO。

同步IO:但在一个线程内的话,不实现异步的情况下,一旦遇到了IO操作,如读写文件、发送网络数据等,就需要去等待IO操作完成,此时线程会被挂起,CPU不能去执行线程内的其他操作。这种情况被称为同步IO。

多进程或多线程解决并发:这时候可以配置多线程或多进程来解决这个IO操作造成的阻塞问题,当一个线程被阻塞了,操作系统就可以切换到另一个线程中去,然后CPU继续执行。虽然多线程和多进程的模型解决了并发问题。但是系统不能无上限的增加线程,而且切换线程的开销也很大,即耗时。一旦线程数量过多,时间都花在切换上面了,真正运行代码的时间就少了,CPU利用率就降低,导致资源浪费。

异步IO:在一个线程内实现异步IO,异步IO需要一个消息循环,将异步任务都添加到消息循环中去,消息循环会一直处在监听状态(“读取消息-处理消息”),当其中一个任务遇到IO操作时,程序会去运行消息循环中的其他异步任务,这样就可以有效的利用CPU。

相对于同步IO模型,异步IO模型主线程不会休息,而是在消息循环中继续处理其他消息。在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。

协程

协程(Coroutine):微线程,在一个线程中,两个“子程序”之间协作完成任务,所以称为“协程”。

子程序:有称为函数,在所有语言中都是层级调用。比如A调用B,B调用C,需要等C执行完返回结果,然后B才执行,等B执行完,A才执行,最后返回A执行完的结果。

子程序总是一个入口一次返回,调用顺序是确定的。

协程:协程的调用和子程序不同,协程看上去也是子程序,但执行过程中,在子程序内部可以中断,转而去执行别的子程序,在适当的时候再返回来接着执行当前子程序。注意:在一个子程序中中断去执行其他子程序,而不是调用其他子程序来执行。

协程 vs 多线程

  • 1 最大的优势就是协程极高的执行效率。协程是一个线程内,是子程序之间进行切换执行代码,因此,没有线程切换的开销,和多线程相比,线程数量越多,协程的性能优势就越明显。
  • 2 不需要多线程的锁机制。由于是一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

协程利用多核CPU:多进程+协程

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

改用协程,无锁流程:

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)	# 第二步:切换到consumer执行
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

asyncio

https://www.liaoxuefeng.com/wiki/1016959663602400/1017970488768640

asyncio实现了TCP、UDP、SSL等协议

asyncio:Python3.4引入的标准库,内置了对异步IO的支持。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个Eventloop的引用,然后把需要执行的协程扔到Eventloop中执行,就是先了异步IO。

asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield form调用另一个coroutine实现异步操作。

async/await

async与await是针对coroutine的新语法

async代替@asyncio.coroutine

await代替yield from

aiohttp

asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。

pip install aiohttp

编写一个HTTP服务器,分别处理以下URL:

  • / -首页返回b<h1>Index</h1>
  • /hello/{name} - 根据URL参数返回文本hello,%s!

代码如下:

import asyncio

from aiohttp import web

async def index(request):
    await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>')

async def hello(request):
    await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' % request.match_info['name']
    return web.Response(body=text.encode('utf-8'))

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
    app.router.add_route('GET', '/hello/{name}', hello)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)# 创建TCP服务
    print('Server started at http://127.0.0.1:8000...')
    return srv

loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

注意:其中web.Applicationloop参数已经弃用了,make_handler()也已经弃用了,这里只做简单汇总知识点。

21 使用MicroPython

https://www.liaoxuefeng.com/wiki/1016959663602400/1346182154551329

MicroPython是Python 的一个精简版本,它是为了运行在单片机这样的性能有限的微控制器上,最小体积仅256K,运行时仅需16K内存。

运用MicroPython做嵌入式

22 实战

实战篇的话廖雪峰老师的教程太老了,有兴趣可以去看aiohttp 3.0的项目,下面有连接

https://aodabo.tech/blog/001546714438970511a8089adc94c909312e2554aa4eabd000

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值