python基础

编译型与解释型语言的区别:

编译型:开发完成,一次性把所有的代码进行编译成机器能识别的二进制码,在运行。
    代表语言:c,c++
    优点:执行速度快
    缺点:开发速度慢,调试周期长
解释型:代码从上到下一行一行解释并运行
    代表语言:python,php
    优点:开发效率快,调试周期短
    缺点:执行速度相对较慢
    
python的解释器
    1.Cpython   	把python代码转化为C语言能识别的二进制码
    2.Jpython   	把python代码转化为java语言能识别的二进制码
    3.pypy      	将所有代码一次性编译成二进制码,加快执行效率(模仿编译型语言)
    4.其他语言解释器 把python代码转化为其他语言能识别的二进制码

变量和赋值

  • 什么是变量

    • 一个实体的指代

      • 查看变量的内存地址
      id(x)
      
    • 可重新赋值的

  • 为变量赋值

    • 通过=来赋值
    x = 3
    
    • 被重新赋值之后的值去哪了?
      • 被系统回收了
  • Python常量

    • python没有常量这个数据类型

Python的六大基本类型

  • 通过type()函数来查看当前变量的数据类型

  • int (整数)

    int('1')
    
  • float (浮点数)

    float('1.0')
    float('INF')
    
    • 浮点数和小数的区别:因为计算机内部只认识1和0,所以浮点数强调的是小数的表现形式
  • string (字符串,字符序列)

    #转义,将其他类型转为string
    str(2)
    
    • 有序的
    • 如何表示字符串
      • ""
    • 转义字符

    ​ 告诉python解释器,我们当前要表示的是一个字符或者是特殊字符

    ​ 通过\来转义

  • boolean

    True等价于1,false等价于0

  • bytes(二进制序列):二进制的表示形式

  • None(空)

Python的四大基本数据结构

  • list(列表)

    • 用来装载不同数据类型的数据集结构

    • 特点

      • 有序
      • 可以装载任意数据类型
      • 可以更改
    • 如何表示list

      • 通过list() 创建一个列表
      list("hello world")
      
      • 通过[ ]声明一个列表
      a = [1,2,3]
      
  • tuple(元组)

    • 元组:不可修改的列表,常用来表示记录

    • 特点

      • 有序
      • 可以装载任意数据类型
      • 不可更改
    • 如何表示tuple

      • 通过tuple("hello")
      tuple("hello")
      
      • 通过( , )来声明一个元组
      a = (1,2,3)
      tuple(a)
      a
      # 声明单个元素的元组,要添加`,`
      a = (a,)
      
  • dict(字典)

    • 字典也叫hashtable,通过hash(散列)函数将传入的key值生成地址来查找value
      • key->hash函数->返回了value的地址->通过地址返回value值
    • 特点
      • 无序
      • 字典中的key必须是可hash的,也就是不可更改的,唯一的
      • 可更改的
    • 通过dict()来创建字典
    dict(a=2)//不常用
    
    • 通过{ }来声明一个字典
    a = {"a": 2}
    
  • set(集合)

    • set其实是没有value的字典

    • 特点

      • 无序
      • 集合中的key必须是可hash的
      • 集合可以更改的
      • 元素是唯一的
    • 如何表示set

      • 通过set( )来创建集合
      set([1,2,2])
      
      • 通过{ }来表示
      {1,2,3}
      

Python函数的基本介绍

  • 什么是函数?

    函数是一段可以直接被另外一段程序或代码引用的程序或代码, 也叫做子程序, 方法.

    • 可重复使用
    • 可互相调用
  • 函数的目的

    • 为了代码段的复用
  • Python中如何定义一个函数?

    def foo(arg):
    	return "Hello " + str(arg)
    
  • 函数的组成

    • 参数列表

      • 必须参数

        当前参数必须按顺序传入

      • 关键字参数

        根据关键字参数传参可以无视顺序

        def foo(arg=None, arg_2=None)
        
        • 默认参数

          def foo(arg='tunan', arg_2=None)
          
      • 不定长参数

        在装饰器中会大量应用
        

        可以接受任意长度的参数.

        • *

          代表了省略, 省略了参数tuple(元组)

        • **

          省略了关键字参数dict(字典)

    • 函数体

    • 返回值

      默认返回None

      return None
      

Python的运算符

  • 算术运算

    • +

    • -

    • *

      乘法

    • /

      除法

    • //

      整除

    • %

      取余数

    • **

      x的y次幂

    • 开方(没有提供直接的运算符)

      x ** (1/2)
      
    • abs()

      取绝对值

  • 赋值运算

    通过=赋值

    a = 1
    
  • 比较运算

    比较的是两个对象的字面值, 字面值暂时可以简单地理解为输出值

    • <

    • >

    • <=

    • >=

    • ==

      等于

    • !=

      不等于

  • 标识号比较运算

    比较的是两个变量的内存地址

    • is

    • is not

    • 赋值类型为str, int的时候要考虑Python常量池

      a = "test_1"
      b = "test_1"
      
      a is b
      >>> True
      
      a = '你好'
      b = '你好'
      a is b
      >>> False
      
  • 成员检测运算

    判断元素是否在当前序列当中

    • in

      a = [1,2,3]
      1 in a
      >>> True
      
      b = [1, 2]
      b in a
      >>> False
      
    • not in

  • 布尔运算

    判断当前语句的结果是True还是False

    • and

      只有两边都是True才返回True

    • or

      两边表达式有一个True返回的结果为True

      • 短路

        表达式A or 表达式B
        当表达式A为True时, 表达式B就不会运行
        
    • 逻辑取反

      not

  • 位运算

    二进制运算, 未来刷题的时候再说, 有兴趣的同学可以了解一下
    
    • ~
    • ^
    • >>
    • <<
    • &
    • |

Python运算符优先级

运算符描述
or布尔运算或
and布尔运算与
not布尔运算逻辑取反
in, not in, is, is not, <, !=, …比较运算, 成员检测运算, 标识号检测
+, -加法和减法
*, /, //, %乘法, 除法, 整除, 取余
+x, -x正负数
**
  • 自定义优先级

    如果不确定优先级, 出于可读性和避免未知的BUG, 我们都应该用()来自定义优先级
    
    • 通过()

      (not b and c) or (d and e)
      
  • 用函数实现一个具有加, 减, 乘, 除, 整除, 取余, 开方的计算器

    my_calculator.py
    
    def add(a, b):
    	...
    def sqrt(a, b):
    	...
    

字符串(字符序列)和字节序列

  • 字符

    • 由于历史原因, 将字符定义为unicode字符还不够准确, 但是未来字符的定义一定是unicode字符
  • 字节

    就是字符的二进制表现形式

  • 码位

    我们计算机显示的实际上是码位

    >>> '你好'.encode("unicode_escape").decode()
    '\\u4f60\\u597d'
    >>>
    >>> '\u4f60\u597d'
    '你好'
    
    • UNICODE标准中以4~6个十六进制数字表示
  • 编码

    • 字符序列(string) -> 字节序列(bytes) -------------编码(encode)

      >>> "你好".encode("utf-8")
      b'\xe4\xbd\xa0\xe5\xa5\xbd'
      
    • 字节序列(bytes) -> 字符序列(string) -------------解码(decode)

      >>> b
      b'\xe4\xbd\xa0\xe5\xa5\xbd'
      >>> b.decode("utf")
      '你好'
      
  • 编码错误

    • 乱码和混合编码

      • 检查编码

        没有办法通过字节序列来得出编码格式, 都是统计学来预估当前的编码

        # 安装chardet
        pip install chardet
        
        # 导入charet
        >>> import chardet
        >>> chardet.detect(b)
        
      • 解决乱码和混合编码

        • 忽略错误编码

          >>> b_2.decode("utf-8", errors='ignore')
          '你好'
          
        • 利用鬼符来替换

          >>> b_2.decode("utf-8", errors='replace')
          '你好'
          

字符串的CRUD操作

通过dir("")可以查看当前字符串的操作方法
  • Create(创建)

    • +

      >>> a = "a"
      >>> id(a)
      22951584
      >>> a = a + "b"
      >>> id(a)
      60513280
      >>> a
      'ab'
      
    • +=

      a += "b"  就是 a = a + "b" 省略写法 
      
  • Retrieve(检索)

    • 根据索引获取字符

      在计算机语言当中, 索引值是从0开始数的

      >>> a = "hello, world"
      >>> a[1]
      'e'
      
    • find和index(获取目标字符的索引值)

      >>> a.find("e")
      1
      >>> a.find("!")
      -1
      
      # 找不到目标字符时, index会报错
      >>> a.index("!")
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: substring not found
      
    • startwith和endwith

      >>> f = "2020-11-22-xxxxx"
      >>> f.startswith("2020-11-22")
      True
      >>> f = "xxxxx.jpg"
      >>> f.endswith("jpg")
      True
      
  • UPDATE(更新)

    • replace(替换)

      返回的是一个新的字符串

      a.replace("wer", "wor")
      
    • split(分割)

      >>> a = "<<python>>, <<java>>, <<c++>>"
      >>> a.split(",")
      ['<<python>>', ' <<java>>', ' <<c++>>']
      
    • join(拼接)

      >>> b
      ['<<python>>', ' <<java>>', ' <<c++>>']
      >>> ",".join(b)
      '<<python>>, <<java>>, <<c++>>'
      
  • DELETE(删除)

    • strip

      >>> a
      '          hello, world          '
      >>> a.strip()
      'hello, world'
      >>>
      
      
    • lstrip

    • rstrip

字符串的输出和输入(文件)

  • 保存到文件

    # open函数打开一个文件, 没有文件会新建, 但是路劲不对会报错
    # 指定文件名, 方法(读, 写, 追加), 编码格式
    output = open("output.txt", "w", encoding="utf-8")
    content = "hello, world"
    # 正式写入文件
    output.write(content)
    # 关闭文件句柄
    output.close()
    
  • 读取文件

    input = open("output.txt", "r", encoding="utf-8")
    # 获取文件中的内容
    content = input.read()
    print(content)
    
    # 暂时理解为只能读取一遍
    content_2 = input.read()
    print(content_2)
    
  • 追加文件

    output = open("output.txt", "a", encoding="utf-8")
    content = "\nhello, world"
    # 正式写入文件
    output.write(content)
    # 关闭文件句柄
    output.close()
    

字符串的格式化输出

  • format

    • 按传入参数默认顺序

      a = "ping"
      b = "pong"
      
      "play pingpong: {}, {}".format(a, b)
      
    • 按指定参数索引

      a = "ping"
      b = "pong"
      
      "play pingpong: {0}, {1}, {0}, {1}".format(a, b)
      
    • 按关键词参数

      a = "ping"
      b = "pong"
      
      print("play pingpong: {a}, {b}, {a}, {b}".format(a='ping', b='pong'))
      
    • 按变量(推荐, 但是只有3.6以上才可以使用)

      a = "ping"
      b = "pong"
      
      print(f"playing pingpong: {a}, {b}")
      
    • 小数的表示

      >>> "{:.2f}".format(3.14159)
      '3.14'
      >>>
      
  • %

    >>> "playing %s %s" % ("ping", "pong")
    'playing ping pong'
    
    

了解变量和引用

  • 变量简单地说就是指向了一个实体

  • 引用简单地说就是指向变量的变量

    >>> a = 1
    >>> b = a
    >>> id(a)
    1778508560
    >>> id(b)
    1778508560
    

基础数据结构的CRUD操作

  • List(列表)

    list中存的元素是引用

    • create(增加)

      • append

        末尾添加元素

        >>> l = []
        >>> id(l)
        55200584
        >>> l.append("a")
        >>> l
        ['a']
        >>> id(l)
        55200584
        
      • ++=

        • +

          拼接两个列表, 然后返回一个新列表

        • +=

          >>> l = ['a']
          >>> id(l)
          55200664
          >>> l += ['b']
          >>> id(l)
          55200664
          >>> l
          ['a', 'b']
          
      • **=

        >>> a = 'a'
        >>> id(a)
        53622432
        >>> l = [a] * 10
        >>> l
        ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']
        >>> id(l[0])
        53622432
        >>> id(l[1])
        53622432
        >>> id(l[9])
        53622432
        
        # 赋值语句之后, a已经是一个新的对象了
        >>> a = 'b'
        >>> l
        ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']
        >>> id(a)
        53647264
        
      • insert

        指定位置添加元素

        l.insert(0, 'b')
        
    • Retrieve(检索)

      • 索引取值

        所有序列都支持索引取值

      • 切片

        your_list[start:end:step]
        # 取一段区间
        your_list[start:end]
        
        # 取最后一个值
        your_list[-1]
        
        # 间隔问题
        your_list[1:20:2]
        
      • index

        >>> l
        ['a', 'b', 'c']
        >>> l.index('a')
        0
        
    • Update(更新)

      • 索引赋值

        l[0] = 'a_1'
        
      • 切片赋值

        >>> l
        ['a_1', 'a_2', 'b', 'c']
        >>> l[0:2] = "a"
        >>> l
        ['a', 'b', 'c']
        >>> l[0:2] = 1
        
    • DELETE(删除)

      • pop()

        从末尾删除元素并返回

        >>> l
        ['a', 'b', 'c']
        >>> x = l.pop()
        >>> l
        ['a', 'b']
        >>> x
        'c'
        
      • clear()

        清楚当前列表的元素, 不会改变列表的内存地址.

    • ★SORT(排序)

      • sort()

        >>> l
        [1, 3, 2, 6, 4]
        >>> l.sort()
        >>> l
        [1, 2, 3, 4, 6]
        
        
      • sorted

        排序后返回新列表

        >>> l2 = sorted(l)
        >>> l
        [1, 3, 2, 6, 4]
        >>> l2
        [1, 2, 3, 4, 6]
        >>> id(l)
        55201384
        >>> id(l2)
        55200984
        
      • reverse

        >>> l2
        [1, 2, 3, 4, 6]
        >>> l2.reverse()
        >>> l2
        [6, 4, 3, 2, 1]
        
      • reversed

        倒序之后返回新列表

        >>> l
        [1, 3, 2, 6, 4]
        >>> list(reversed(l))
        [4, 6, 2, 3, 1]
        
  • tuple

    • Create

    • Retrieve

      • 索引取值
      • index
      • 切片
    • Update

    • Delete

  • dict

    • Create

      • 键对值赋值

      • update

        提供合并字典的功能

        >>> d
        {'a': 1}
        >>> d2 = {"b":2, "c": 3}
        >>> d.update(d2)
        >>> d
        {'a': 1, 'b': 2, 'c': 3}
        
      • setdefault

        如果字典中没有当前key, 那么就设置默认值

        >>> d
        {'a': 1, 'b': 2, 'c': 3}
        >>> d.setdefault('b', 0)
        2
        >>> d.setdefault('d', 0)
        0
        >>> d
        {'a': 1, 'b': 2, 'c': 3, 'd': 0}
        
        
    • Retrieve

      • 键对值访问

      • get

        键对值访问缺失key会报错, 而get可以指定默认值

        >>> d['e']
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        KeyError: 'e'
        >>> d.get('f')
        >>> d.get('f', 0)
        0
        
      • keys()

        返回所有key

        d.keys()
        
      • values()

        返回所有value

        d.values()
        
      • items()

        返回所有键对值

        d.items()
        
    • Update

      • 键对值赋值

         d['a'] = 100
        
      • update

        >>> d.update({"b": 200, "c": 300})
        >>> d
        {'a': 100, 'b': 200, 'c': 300, 'd': 0}
        
    • Delete

      • pop(key)

        删除当前元素并返回value

      • popitem()

        对于人来说, 相当于随机返回一个item

      • clear()

  • set

    • Create

      • add
      • update
    • Retrieve

      • 运算符in

        >>> s
        {'a'}
        >>> "a" in s
        True
        
        
    • update(作用为增加)

      • union

        合并两个set, 并返回一个新的set

    • delete

      • remove 和discard

        discard缺失元素时不会报错, 而remove会报错

        >>> s
        {'b', 'c', 'a'}
        >>> s.remove("a")
        >>> s
        {'b', 'c'}
        >>> s.discard("e")
        >>> s.remove("a")
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        KeyError: 'a'
        >>>
        
      • pop()

        当成无序删除并返回元素

Python的逻辑控制语句

  • 条件判断语句

    • if

    • elif

    • else

      a = 50
      if a > 100:
          print("a 超过阈值")
      elif a == 50:
          print("a 只有阈值的一半")
      else:
          print("a 小于阈值")
      
  • 循环语句

    • for

      遍历一个可迭代对象(暂时理解为list), 会影响相同作用域当中的变量

      l = [1, 2, 3, 4, 5, 6]
      e = 0
      
      
      for e in l:
          print(e)
      
      print(f"final e value: {e}")
      
      • 获取索引值和值

        l = [1, 2, 3, 4, 5, 6]
        
        
        for i, e in enumerate(l):
            print(f"index: {i}, value: {e}")
        
    • while循环

      一定要有逻辑判断语句来退出while循环

      while 判断语句:
      	表达式
      
      while True:
      	判断语句
      	表达式
      
    • 跳出循环

      • break

        停止当前循环

      • continue

        跳过当前的执行逻辑, 立即执行下一个循环语句单元;

      • pass

        跳过当前条件判断中的执行语句, 后续语句继续执行;

Python的异常与处理

  • 异常

    程序遇到严重错误时, 会终止程序的运行并抛出异常

    def my_sub(a, b):
        return a / b
    
    my_sub(1, 0)
    
  • 捕获异常

    try:
    	表达式
    except [Exception] as e:
    	表达式
    finnaly:
    	表达式
    
    
    
    def my_sub(a, b):
        try:
            return a / b
        except ZeroDivisionError:
            # print(e)
            print("分母不可为0")
            return None
        finally:
            print("function my_sub end")
    
    my_sub(1, 0)
    
    • Exception

      所有异常的基类, 所有的异常都是Exception的子类

    • 处理异常颗粒度要细一点, 尽量不要捕获基类Exception, 尤其是数据处理的时候.

    • 常见的异常

      • IndexError

        索引值超过了列表长度

        >>> l = [1]
        >>> l[2]
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        IndexError: list index out of range
        
        
      • KeyError

        找不到Key

        >>> d = {"a": 1}
        >>> d["b"]
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        KeyError: 'b'
        
      • ValueError

        传入的参数错误

        >>> int('a1')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        ValueError: invalid literal for int() with base 10: 'a1'
        
        
      • TypeError

        类型错误, 常见于运算

        >>> 1 + '2'
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        TypeError: unsupported operand type(s) for +: 'int' and 'str'
        
        
      • SyntaxError

        语法报错, 检查自己的语法有没有写错

      • IndentationError

        缩进错误

        • 混用tab和space(空格)
        • 缩进长度不对
  • 如何处理异常

    • 处理

    • 抛出新异常

      def my_sub(a, b):
          try:
              return a / b
          except ZeroDivisionError:
              print("分母不可为0")
              raise Exception("params error")
          finally:
              print("function my_sub end")
      
    • 重新抛出

      def my_sub(a, b):
          try:
              return a / b
          except ZeroDivisionError:
              print("分母不可为0")
              raise ZeroDivisionError
          finally:
              print("function my_sub end")
      
    • 忽略(不推荐)

      pass

      用来指示当前处理语句没有正式写完, 尽量不要忽略异常, 否则代码的健壮度会很差, 造成不可预知的bug.

  • 自定义异常

    class ParamsError(Exception):
        pass
        
    def my_sub(a, b):
        try:
            return a / b
        except ZeroDivisionError:
            raise ParamsError("分母不可以为0")
        finally:
            print("function my_sub end")  
    

课后作业

  • 用for循环和while来完成简单的计数

  • 用for循环和while循环两种方式来实现斐波那契函数, 限制在100以内

    • 斐波那契函数

      第N项是N-1, N-2的和

      F(n)=F(n - 1)+F(n - 2)
      
      [0, 1, 1, 2, 3, 5, 8, 13, 21....]
      
  • 在第二周-第一节课我们实现的简单计算器的基础上, 对参数进行检查, 如果报错就抛出我们自定义异常ParamsError

重新认识函数

  • 内置函数

    • 认识Python自带的, 可全局调用的函数, 避免我们命名冲突导致了函数性状发生改变

    • 查看Python携带的内置函数

      from pprint import pprint
      # 格式化输出的库
      pprint(dir(__builtins__))
      
    • 常见的内置函数

      • str

        >>> str(1.0)
        '1.0'
        
      • int

        >>> int(1.0)
        1
        >>> int("1.0")
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        ValueError: invalid literal for int() with base 10: '1.0'
        >>> int("1")
        1
        >>>
        
      • float

        >>> float("1.0")
        1.0
        >>> float(1)
        1.0
        >>> float('1')
        1.0
        
      • bytes

        >>> bytes('a'.encode("utf-8"))
        b'a'
        
      • bool

        >>> bool(0)
        False
        >>> bool(1)
        True
        >>> bool(2)
        True
        >>> bool('0')   *****
        True
        >>> bool(0.0)
        False
        
      • list

        只要是序列都可以转换成list

        >>> list("qwe")
        ['q', 'w', 'e']
        
      • tuple

        >>> tuple("qwe")
        ('q', 'w', 'e')
        >>> tuple([1,2])
        (1, 2)
        
      • dict

        >>> dict(a=1)
        {'a': 1}
        
      • set

        >>> set([1,2,2])
        {1, 2}
        >>> set("qweqweqwe")  ****
        {'q', 'w', 'e'}
        
      • id

        查看当前对象的内存地址

        >>> a = "1"
        >>> id(a)
        26114944
        
      • dir

        • 当前对象下的所有方法和属性
        • 在Python中一切皆为对象
        dir(__builtins__)
        
      • max

        返回一个序列中的最大值

        >>> max([2, 4,67,1])
        67
        
      • min

        返回一个序列中的最小值

        >>> min([2, 4,67,1])
        1
        
      • range

        返回一组数字区间的可迭代对象

        >>> r = range(100)
        >>> r
        range(0, 100)
        
        >>> for i in range(10):
        ...     print(i)
        
  • 函数的形参和实参

    • 形参

      形式参数, 简单地说就是还没接受到实际值的参数. 函数未调用时就是形参

      def my_power(a, b):
          return a ** b
      
    • 实参

      实际传入的参数, 函数调用时传入的值就叫实参

      print(my_power(2, 3))
      
  • 函数的返回值

    • 返回值的类型

      任意类型, 包括函数本身

    • 如何接受返回值

      • 接收单个值

      • 一个变量接受返回的多个值

        实际上返回的是个tuple

        >>> def foo(a, b):
        ...     return a*2, b*2
        ...
        >>> result = foo(1, 2)
        >>> result
        (2, 4)
        
      • 多个变量按顺序接收

        实现原理是元组解包(unpack)

        >>> a,b = foo(1,2)
        >>> a
        2
        >>> b
        4
        # 等同于
        >>> result = foo(1,2)
        >>> a, b = result
        
      • 不定长变量接收

        >>> result
        (1, 2, 3, 4, 5, 6, 7)
        >>> a, *b, c = result
        >>> a
        1
        >>> c
        7
        >>> b
        [2, 3, 4, 5, 6]
        

匿名函数

顾名思义匿名函数就是没有名字的函数, 一般都是提供给高阶函数调用.

  • 通过lambda关键字来声明匿名函数

    >>> lambda x: x **2
    # 返回的是一个匿名函数对象
    <function <lambda> at 0x018BB660>
    
  • 函数体是纯表达式

    • 不能有复杂的逻辑判断语句

      • 唯一例外的例子:

        lambda x: 返回值 if 纯表达式 else 返回值
        
        lambda x: True if  x % 2==0 else False
        
    • 不能有循环语句

    • 不能有异常捕获

    • 不能有赋值语句

    • 不能有return

      • 默认表达式运行的结果就是返回值

        >>> lambda x: x **2
        返回值就是 x**2
        
  • 例子

    l = [[1,2], [2,1], [6,4], [3,5]]
    l.sort(key=lambda x: x[1])
    print(l)
    

高阶函数

接受函数作为参数, 或者把函数作为结果返回

  • map(映射)

    对一个序列每个元素进行相同的操作, 这个过程就叫映射

    >>> l = [1,2,3]
    >>> m = map(lambda x: x**2, [1,2,3])
    
    # 获得返回结果是一个map对象
    >>> m
    <map object at 0x03545C10>
    >>> l
    [1, 2, 3]
    
    # map对象是一个可迭代对象, 需要驱动可迭代对象返回值, list就有这样的功能. 暂时不要太纠结
    >>> list(m)
    [1, 4, 9]
    >>> l
    [1, 2, 3]
    
    • 等同于以下:

      def my_powser_2(a):
      	return a ** 2
      
      # 匿名函数只是图方便, 所有的匿名都可以通过正常函数替换
      >>> m = map(my_powser_2, [1,2,3])
      >>> list(m)
      [1, 4, 9]
      
    • 多用于和math库进行运算操作

      >>> m = map(math.sqrt, [1, 4, 9, 16, 25])
      >>> list(m)
      [1.0, 2.0, 3.0, 4.0, 5.0]
      
  • filter(过滤)

    filter(函数, 可迭代对象)
    函数中的表达式返回结果为False, 就会被过滤
    
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    # 过滤偶数
    >>> f = filter(lambda x: x%2, l)
    >>> list(f)
    [1, 3, 5, 7, 9]
    
    # 过滤奇数
    >>> f = filter(lambda x: False if x%2 == 1 else True, l)
    >>> list(f)
    [0, 2, 4, 6, 8]
    

递归函数

在函数中调用自身的函数就叫递归函数

  • 核心思想

    将大的任务拆分为子任务来解决复杂问题, 只要大任务能拆分成子任务, 就可以使用递归

    F(n) = F(F(n-1))
    
  • 声明一个递归函数(阶乘)

    • 一定要有退出机制
    F(n) = n * F(n-1)
    
    def fact(n):
        if n == 1:
            return 1
        return n * fact(n-1)
    
  • 使用递归函数重构斐波那契函数

    f(n) = f(n-1) + f(n-2)
    
    def recur_fibo(n):
        """递归函数
        输出斐波那契数列"""
        if n <= 1:
            return n
        else:
            return (recur_fibo(n - 1) + recur_fibo(n - 2))
    
    # 获取用户输入
    nterms = int(input("您要输出几项? "))
    
    # 检查输入的数字是否正确
    if nterms <= 0:
        print("输入正数")
    else:
        print("斐波那契数列:")
        for i in range(nterms):
            print(recur_fibo(i))
    
    

作用域

程序创建, 访问, 改变一个变量时, 都是在一个保存该变量的空间内进行, 这个空间被称为命名空间, 即作用域

  • Built-in 内置

    • 可以在Python环境中的任何模块, 任意位置访问和调用
  • Global 全局变量

    • 只作用于当前模块(可以理解为当前文件)

    • 可以简单地理解为定以在函数外的变量就是全局变量, 如果在函数体定义那就时局部变量.

    • 如何将局部变量变成全局变量?

      • 使用global关键字

        a = 1
        
        def foo():
            global a
            a = 2
            print(a)
        
        foo()
        print(a)
        
  • Enclosed(嵌套) 自由变量

    在嵌套函数中, 访问函数体之外的非全局变量

    • 只作用于嵌套函数体

    • 最大的应用就是闭包

    • 自由变量是个相对的概念

    • 将局部变量变成自由变量

      • 使用nonlocal关键字

        def make_averager():
            total = 0
            count = 0
            def averager(value):
                nonlocal total, count
                total += value
                count += 1
                return total / count
            return averager
            
        my_avg = make_averager()
        print(my_avg(1))
        print(my_avg(2))
        
  • Local局部变量

    • 只作用于当前函数体

    • 一旦变量在函数体中赋值, 那么该变量相对该函数来说就是局部变量

      a = 1
      b = []
      
      
      def foo():
          a = 2
          b.append(2)
          # 局部变量会在函数声明的时候就定义好
          # 不是按照我们逻辑思维上先执行全局变量b.append(2), 然后再声明一个局部变量b
          # 而是再函数声明之初就已经定义了b为局部变量
          # b = 3
          return None
      
      foo()
      print(a)
      print(b)
      

闭包和装饰器

  • 闭包

    闭包指延申了作用域的函数, 也就是作用域中的Enclosed的概念

    def make_averager():
        series = []
        def averager(value):
            series.append(value)
            total = sum(series)
            return total / len(series)
        return averager
    
    # my_avg就是延申了作用域的函数
    # series就是被延申作用域的变量
    my_avg = make_averager()
    print(my_avg(1))
    print(my_avg(2))
    
  • 装饰器

    • 实现原理

      就是闭包, 延申了被装饰函数的作用域, 本质是将函数作为参数传递给一个可调用对象(函数或类)

    • 目的

      增加和扩展可调用对象(函数或类)的行为

    • 实现一个装饰器

      • 通过@关键字装饰函数

        def clock_it_deco(func):
            def wrapper(*args, **kwargs):
                start_time = time.time()
                result = func(*args, **kwargs)
                end_time = time.time()
                print(f"{func.__name__} execute time: {format(end_time - start_time, '.2f')} s")
                return result
            return wrapper
        
        # @other_deco
        @clock_it_deco
        def foo(a, b):
            count = 1
            while True:
                if count > a ** b:
                    break
                count += 1
        
        foo(10, 5)
        
      • 等同于

        foo = clock_it_deco(foo)
        foo(10, 5)
        

导入第三方模块

  • 导包的层级关系

    • 模块(module)

      以文件为载体, 包含各类对象

    • 包(package)

      以文件夹为载体, 包含了各类模块

    • 库(lib)

      包含了各类包

  • import 库

  • from 库/模块 import 模块/函数

  • 导包的命名冲突

    通过as这个关键词来给当前模块/函数取个别名

    from datetime import datetime as p_datetime
    

时间模块time

调用的都是系统级的接口, 提供时间的访问和转换的功能

  • 查看时间

    • 获取当前时间

      # 有时区的
      time.localtime()
      
      返回的是一个time.struct_time对象
      
    • 时间戳

      time.time()
      
    • 时间的格式化输出

      now = time.localtime()
      now = time.strftime("%Y-%m-%d %H:%M:%S", now)
      print(now)
      
      # 可以省略时间对象
      now = time.strftime("%Y-%m-%d %H:%M:%S")
      
    • 运算

      将时间对象转换为list, 对相应的时间重新赋值后, 通过time.struct_time生成一个新的时间对象

      time_list = list(time.localtime())
      time_list[2] = 4
      time.struct_time(time_list)
      
    • 时间休眠

      当前程序休眠n秒

      time.sleep(3)
      

时间模块datetime

封装了time, 提供了更高级和更友好的接口

  • 查看时间

    # 获取计算机时间, 返回的是一个datetime.datime对象
    datetime.datetime.today()
    
    
    # 获取指定时区的时间
    datetime.datetime.now(tz=None)
    
    # 获取utc时间
    datetime.datetime.utcnow()
    
  • 时间格式的转换

    • datetime.datetime -> str

      now = datetime.datetime.now(tz=None)
      now.strftime("%Y-%m-%d %H:%M:%S")
      
    • str -> datetime.datetime

      >>> now
      '2021-01-03 23:38:26'
      >>> datetime.datetime.strptime(now, "%Y-%m-%d %H:%M:%S")
      datetime.datetime(2021, 1, 3, 23, 38, 26)
      
    • datetime.datetime -> timestamp

      >>> now
      datetime.datetime(2021, 1, 3, 23, 40, 45, 749240)
      >>> now.timestamp()
      1609688445.74924
      
    • timestamp -> datetime.datetime

      >>> ts
      1609688445.74924
      >>> datetime.datetime.fromtimestamp(ts, tz=None)
      datetime.datetime(2021, 1, 3, 23, 40, 45, 749240)
      
  • 时间运算

    • timedelta

      只作用于datetime.datetime格式

      # 选中目标模块  ctrl+B / command+B 跳转到模块源码     
          def __new__(cls, days=0, seconds=0, microseconds=0,
                      milliseconds=0, minutes=0, hours=0, weeks=0):
      
      >>> from datetime import timedelta
      >>> now + timedelta(hours=-1)
      datetime.datetime(2021, 1, 3, 22, 40, 45, 749240)
      

类的创建, 实例化, 初始化

  • 什么是类

    类就是拥有相同功能或者相同属性的对象集合

  • 类的创建

    • object是所有类的基类

      class GoGamer(object):
          subject = 'go'
      
      print(GoGamer)
      
  • 类的实例化

    实例就是抽象概念的具象化

    kejie = GoGamer()
    print(kejie)
    
  • 类的初始化

    类创建一个新实例的时候会默认调用__init__这样一个特殊方法

    class GoGamer(object):
        subject = 'go'
        def __init__(self, obj):
            self.p = obj
    
    kejie = GoGamer("金勺子")
    print(f"柯洁含着{kejie.p}出生")
    
    • 关于self

      指代还未实例化的实例

面向对象

  • 面向过程

    • 程序=数据结构+算法
    • 强调的是一个实现的细节
  • 面向对象

    完成对越来越庞大项目代码以及对外公开接口的归类和重用, 是一种更高级的抽象.

    • 通过什么手段来完成上述目的?

      • 继承

        class ChineseGoGamer(GoGamer):
            nation = 'cn'
        
        class KoreaGoGamer(GoGamer):
            nation = 'kr'
        
        • 处理多继承冲突

          • 查看MRO(mehotd resolution order)

            class A:
                def __init__(self):
                    print("init A")
            
            class B:
                def __init__(self):
                    print("init B")
            
            
            class C(A, B):
                pass
            
            print(C.__mro__)
            
          • 指定类方法的调用顺序

            class C(A, B):
                def __init__(self):
                    super(A, self).__init__()
            
          • super函数源码

            def super(cls, inst):
            	mro = inst.__class__.mro()
            	return mro[mro.index(cls) + 1]
            
            def super(类, 实例):
            	# 获取当前实例的方法解析顺序
            	mro = 实例.类.mro()
            	return mro[mro.index(类) + 1]
            
      • 多态

        方式为覆盖和重载

        • 覆盖(子类和父类之间的, 是垂直的关系)

          子类可以继承父类的所有属性和方法, 但是同时子类也可以重写父类的属性和方法, 达到自定义的目的.

          class A:
              def __init__(self):
                  print("init A")
          
          class B:
              def __init__(self):
                  print("init B")
          
          
          class C(A, B):
              def __init__(self):
                  print("init C")
          
        • 重载(类中的方法和方法之间的, 是水平关系)

          Python中式没有重载, 但是可以用装饰器来实现该功能.

      • 封装

        把客观事物封装成抽象的类, 隐藏实现细节, 使得代码模块化.

  • 类属性和实例属性

    • 类属性

      • 通过类对象可以直接访问的属性

      • 抽象概念的固有属性, 要考虑当前抽象概念的普适性

        # 贴标签不是一个特别好的抽象, 原因他没有一个普适性
        class Developer:
            programing_language = None
            busy = True
        
        • 私有属性

          不希望外部更改, 只作用于类内部

          • 通过__变量名来声明私有属性

            class Lottery:
                __items = ["mac", "ipad", "iphone"]
            
          • 通过类._类名__变量名来访问私有属性

            print(Lottery._Lottery__items)
            
    • 实例属性

      • 绑定在实例上的属性, 只能通过该实例进行访问

      • 实例的自有属性

        class Developer:
            programing_language = None
            busy = True
            __case = "doing something"
        
        d_python = Developer()
        d_python.programing_language = "python"
        d_java = Developer()
        d_java.programing_language = "java"
        
        print(d_java.programing_language)
        print(d_python.programing_language)
        
        • 私有属性
          • 通过self.__变量名来声明私有属性
          • 通过实例._类名__变量名来访问私有属性

    类方法, 静态方法, 实例方法

    • 类方法

      • 仅供类调用的方法

      • 通过classmethod装饰器来声明一个类方法

      • 自定义类创建

        class Developer:
            programing_language = None
            busy = True
            __case = "doing something"
            def __init__(self, hairs):
                self.__hairs = hairs
        
            @classmethod
            def __new__(cls, *args, **kwargs):
                print("init class")
                return super().__new__(cls)
        
            @classmethod
            def get_case(cls):
                return cls.__case
        
    • 静态方法

      • 类可以直接调用的方法

      • 通过staticmethod装饰器装饰

      • 对一类抽象行为的归类

        class MyMath:
        
            @staticmethod
            def add(a, b):
                return a + b
        
    • 实例方法

      • 仅供实例调用的方法

    接口, 协议和抽象基类

    • 接口

      对象公开方法的子集, 让对象在系统中扮演特定的角色.

      list实现了增删改查的接口, 只要有一个接口没有实现那就不属于list
      tuple只提供了查的接口
      
    • 协议

      非正式的接口, 协议与继承没有关系, 一个类可能会实现多个接口, 从而让实例扮演多个角色

      list扮演者列表的角色, 但同时也是一个序列, 序列并不是一个实体类.
      
      • 协议的应用

        class MyDict(dict):
            def __iadd__(self, other):
                self.update(other)
                return self
        
            def __str__(self):
                return f"My Dict {self.items()}"
        
    • 抽象基类

      把客观事物封装成抽象的元类, 区分概念和实现.

      • 只要有@abc.abstractmethod装饰器的类就是抽象基类

        import abc
        class Mixin:
            def sign(self):
                pass
        
            def rank(self):
                pass
        
        class Gamer:
            @abc.abstractmethod
            def sign(self):
                pass
        
        
        class GoGamer(Mixin, Gamer):
            pass
        
        class Swimmer(Mixin, Gamer):
            pass
        

    课后作业

    • 将之前封装的MyMath类中的实例方法改为静态方法, 体会两者的区别.
    • 为上节课自定义类添加以下功能:
      • 添加类属性
      • 添加类私有属性
      • 添加类方法或者类的私有属性
      • __init__方法中初始化实例属性
      • __init__方法中绑定私有实例属性
      • 在自定义类中实现__str__, 自定义输出格式

类属性和实例属性

  • 类属性

    • 通过类对象可以直接访问的属性

    • 抽象概念的固有属性, 要考虑当前抽象概念的普适性

      # 贴标签不是一个特别好的抽象, 原因他没有一个普适性
      class Developer:
          programing_language = None
          busy = True
      
      • 私有属性

        不希望外部更改, 只作用于类内部

        • 通过__变量名来声明私有属性

          class Lottery:
              __items = ["mac", "ipad", "iphone"]
          
        • 通过类._类名__变量名来访问私有属性

          print(Lottery._Lottery__items)
          
  • 实例属性

    • 绑定在实例上的属性, 只能通过该实例进行访问

    • 实例的自有属性

      class Developer:
          programing_language = None
          busy = True
          __case = "doing something"
      
      d_python = Developer()
      d_python.programing_language = "python"
      d_java = Developer()
      d_java.programing_language = "java"
      
      print(d_java.programing_language)
      print(d_python.programing_language)
      
      • 私有属性
        • 通过self.__变量名来声明私有属性
        • 通过实例._类名__变量名来访问私有属性

类方法, 静态方法, 实例方法

  • 类方法

    • 仅供类调用的方法

    • 通过classmethod装饰器来声明一个类方法

    • 自定义类创建

      class Developer:
          programing_language = None
          busy = True
          __case = "doing something"
          def __init__(self, hairs):
              self.__hairs = hairs
      
          @classmethod
          def __new__(cls, *args, **kwargs):
              print("init class")
              return super().__new__(cls)
      
          @classmethod
          def get_case(cls):
              return cls.__case
      
  • 静态方法

    • 类可以直接调用的方法

    • 通过staticmethod装饰器装饰

    • 对一类抽象行为的归类

      class MyMath:
      
          @staticmethod
          def add(a, b):
              return a + b
      
  • 实例方法

    • 仅供实例调用的方法

接口, 协议和抽象基类

  • 接口

    对象公开方法的子集, 让对象在系统中扮演特定的角色.

    list实现了增删改查的接口, 只要有一个接口没有实现那就不属于list
    tuple只提供了查的接口
    
  • 协议

    非正式的接口, 协议与继承没有关系, 一个类可能会实现多个接口, 从而让实例扮演多个角色

    list扮演者列表的角色, 但同时也是一个序列, 序列并不是一个实体类.
    
    • 协议的应用

      class MyDict(dict):
          def __iadd__(self, other):
              self.update(other)
              return self
      
          def __str__(self):
              return f"My Dict {self.items()}"
      
  • 抽象基类

    把客观事物封装成抽象的元类, 区分概念和实现.

    • 只要有@abc.abstractmethod装饰器的类就是抽象基类

      import abc
      class Mixin:
          def sign(self):
              pass
      
          def rank(self):
              pass
      
      class Gamer:
          @abc.abstractmethod
          def sign(self):
              pass
      
      
      class GoGamer(Mixin, Gamer):
          pass
      
      class Swimmer(Mixin, Gamer):
          pass
      

requests模块的介绍

  • requests的作用

    通过python来模拟请求网址

  • 一个模拟请求由以下四个部分组成

    • url
    • method
    • body
    • headers
  • 模拟请求百度

     没有安装requests库的同学, 在当前python环境下执行以下语句安装第三方库
     pip install requests
    

    install_requests.png

    import requests
    
    
    def request_baidu():
        url = "https://www.baidu.com/"
        # body = ""
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
        }
        response = requests.get(url=url, headers=headers)
        print(response.text)
        
    request_baidu()
    

理解多线程和多进程

  • 什么是进程?什么是线程?

    • 进程: 可以简单地认为是一个程序. 进程是操作系统分配资源的最小单位.
    • 线程: 一个进程可以有多个线程, 每个线程可以独立完成一些任务. 线程是操作系统进行运算调度的最小单位.
  • 多线程demo

    from threading import Thread    
    for i in range(10):
        # 只是创建了线程对象
        t = Thread(target=request_baidu)
        # 启动线程
        t.start()
    
  • 多进程demo

    from multiprocessing import Process   
    for i in range(10):
        # 只是创建了进程对象
        p = Process(target=request_baidu)
        # 启动进程
        p.start()
    
  • 多线程

    • 等待任务完成后回到主进程

      通过调用Thread对象的join方法

      # 保存当前thread对象
      thread_array = []
      for i in range(10):
          t = Thread(target=request_baidu, args=(i, ))
          thread_array.append(t)
          t.start()
      # 调用thread对象join接口, 等待任务完成后回到主进程
      for t in thread_array:
          t.join()
      print("done!")
      
    • 如何拿到返回结果

      • 赋值到全局变量当中, 添加到可变对象之中

        result = []
        def request_baidu(index):
        	...
            result.append(response)
            
        if __name__ == "__main__":
            thread_array = []
            for i in range(10):
                t = Thread(target=request_baidu, args=(i, ))
                thread_array.append(t)
                t.start()
            for t in thread_array:
                t.join()
            print("done!")
            print(result)
        
  • 多进程

    • 等待任务完成后回到主进程

      通过调用Process对象的join方法

    • 如何拿到返回结果

      无法通过全局变量存储返回结果.

      多进程相当于启动了多个程序, 共同执行了同一份代码, 他们之间的内存地址完全不一样

      import requests
      import time
      from threading import Thread
      from multiprocessing import Process
      
      result = []
      print(f"主进程result内存地址: {id(result)}")
      
      def request_baidu(index):
          time.sleep(2)
          url = "https://www.baidu.com/"
          # body = ""
          headers = {
              "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
          }
          response = requests.get(url=url, headers=headers)
          print(f"当前请求序号: {index}, 返回结果状态码: {response.status_code}")
          print(f"子进程result内存地址: {id(result)}")
          result.append(response)
      
      # 如果没有判断入口代码段if __name__ == "__main__", 多进程程序会报错
      # 原因是windows和pycharm的进程阻塞带来的问题
      if __name__ == "__main__":
          process_array = []
          for i in range(10):
              p = Process(target=request_baidu, args=(i, ))
              process_array.append(p)
              p.start()
          for p in process_array:
              p.join()
          print("done!")
          print(result)
      
  • 多进程和多线程的异同点

    • 相同点

      • 都是对cpu工作时间段的描述, 只是颗粒度不同.

        简单地说就是多进程和多线程都会调用cpu资源的, 但是进程可以启动多个线程去执行.

      • linux内核态不区分进程和线程

    • 不同点

      • 进程有自己的独立地址空间, 建立数据表来维护代码段, 堆栈段和数据段, 而线程共享进程中的资源, 使用相同的地址空间, 所以线程间的切换快得多.
      • 因为线程共享进程的全局变量, 静态变量等对象, 线程间的通信更为方便, 而进程间的通信更加复杂, 需要以ipc的方式进行.
      • 多进程要比多线程要健壮. 进程之间一般不会相互影响, 而多线程有一条线程崩溃, 会导致整个进程跟着发生崩溃或者无法正常退出等.

全局解释器锁(GIL)

  • 计算密集型

    主要占用cpu资源

  • IO密集型

    IO就是input output, 需要等待的一些任务

    • 网络请求会有网络延迟
    • 和数据库交互需要等待数据库查询事件
    • 读写硬盘
  • 多进程在处理计算密集型程序的时候比多线程块

    由于全局解释器锁的存在, 一个进程下, 只允许一个线程执行Python程序的字节码(当前代码文件的二进制表示).

    简单地说, 创建的10个线程其实在争夺一个cpu资源. 但是遇到io操作会让渡cpu资源.

  • 如何绕过GIL?

    • 将多线程方法改为多进程
    • 将计算密集型任务转移给C扩展.
    • 分布式计算引擎spark, Apache
    • 使用PyPy解释器, 工业上几乎没人这么用, 因为PyPy并不成熟.

进程间通信(IPC)

  • 文件

    通过读写文件来进行变量, 数据, 信息的传递

    • 读写冲突

      两个进程同时进行写, 或者一个写一个读, 造成了冲突.

    • 解决读写冲突

      • 互斥锁

        from multiprocessing import Process, Lock
        
        
        def save_to_file(index, lock):
            with lock:
                with open("test.log", "a", encoding="utf-8") as f:
                    f.write(str(index) + "\n")
        
        
        if __name__ == "__main__":
            process_array = []
            lock = Lock()
            for i in range(10):
                p = Process(target=save_to_file, args=(i, lock))
                process_array.append(p)
                p.start()
            for p in process_array:
                p.join()
            print("done!")
        
  • 套接字(socket-插座)

    通过一个协议, 连接两个进程. 主要就是网络请求.

    进程A向百度云上传文件, 进程B向百度云下载文件, 不会有冲突.

    socket.png

  • 管道(了解)

    用文件的内存缓冲区作为管道, 实现进程间通信

    • 匿名管道

      主进程和子进程进行交互

    • 具名管道

      和匿名管道原理是一样的, 不是不相关的进程也可以互相访问

    ipc_pipeline.png

  • 消息队列

    就是一个存在内核内存空间中的列表

    redis就是消息队列+socket

    from multiprocessing import Queue
    
    def save_to_queue(index, my_queue):
        my_queue.put(index)
    
    
    if __name__ == "__main__":
        process_array = []
        my_queue = Queue()
        for i in range(10):
            p = Process(target=save_to_queue, args=(i, my_queue))
            process_array.append(p)
            p.start()
        for p in process_array:
            p.join()
    
        while True:
            print(my_queue.get())
    
  • 共享内存(了解)

    进程访问内核态同一块内存

    from multiprocessing import Queue, Array, Value
    
  • 信号量(了解)

    不是用来传递数据的, 是用来传递消息

    进程B要等到进程A执行到某一步操作后, 才会启动

    进程A->发消息->内核->转发信息->进程B

线程间通信

线程间通信强调的是线程之间传递对象引用

  • 共享变量

    • 线程安全

      线程有GIL锁, 但是拿到GIL锁不代表可以一直执行下去.

      现代计算机多线程也是A执行一会儿, B执行一会儿这样交替执行.

      import requests   
      import time
      from threading import Thread
      
      
      zero = 0
      
      def foo():
          global zero
          for i in range(10**7):
              zero += 1
              zero -= 1
      
      
      if __name__ == "__main__":
          process_array = []
          for i in range(2):
              p = Thread(target=foo)
              process_array.append(p)
              p.start()
          for p in process_array:
              p.join()
      
          print(zero)
      
    • 解决线程安全

      将重要指令包装成原子操作(不可分割的).

      • 加互斥锁
      import requests
      import time
      from threading import Thread,Lock
      
      
      zero = 0
      lock = Lock()
      
      def foo():
          global zero
          for i in range(10**6):
              with lock:
                  zero += 1
                  zero -= 1
      
      
      if __name__ == "__main__":
          process_array = []
          for i in range(2):
              p = Thread(target=foo)
              process_array.append(p)
              p.start()
          for p in process_array:
              p.join()
      
          print(zero)
      

迭代器和生成器

  • 迭代器

    概念上: 迭代器可以用来表示一个数据流, 提供了数据的惰性返回功能(只有我们主动去使用next方法调用, 才会返回值).

    实现上: 实现了__next__接口的对象

    传统声明一个列表, 里面的元素会立即写进内存当中, 占用大量内存.

    迭代器可以一次只返回一个元素, 占用内存非常小, 在读取大文件和大的数据集合的时候特别有用

    • 通过iter方法返回一个迭代器对象

      # 两者实现的功能是一摸一样的
      l = list(range(10**7))
      l2 = iter(range(10**7))
      
    • 通过next方法主动获取迭代器中的值

      # 当迭代器中没有值了以后, 会抛出StopIteration的异常, 需要大家自行处理一下
      l = iter(range(5))
      print(next(l))
      print(next(l))
      print(next(l))
      print(next(l))
      print(next(l))
      print(next(l))
      
  • 生成器

    生成器是一种特殊的迭代器, 在迭代器惰性返回数据的基础上, 提供了额外的功能, 实现了程序的暂停.

    • 声明一个生成器

      只要函数体中有yield关键词, 它就是一个生成器

      yield翻译为让渡, 我们可以简单理解为暂停并返回右边的值

      def my_range_gen(n):
          for i in range(n):
              yield i*i
              print(f"current index: {i}")
      
      my_range = my_range_gen(10)
      print(my_range)
      print(next(my_range))
      print(next(my_range))
      print(next(my_range))
      print(next(my_range))
      
  • 生成器和迭代器的区别?

    同样提供了惰性返回的功能, 迭代器侧重于提供数据的惰性返回功能, 生成器侧重于指令的惰性返回功能

协程

  • 协程的原理

    协程的实现原理就是生成器的实现原理, 在生成器的基础上又提供了传递值的功能.

    • 通过send方法向生成器传递值, 以下例子中, b就是通过send方法赋值为2

      对生成器进行send操作一定要调用next方法预激, 使其停留在第一个yield位置

      def simple_coro(a):
          print("初始值 a=", a)
          b = yield a
          print("传递值 b=", b)
          c = yield a + b
          print("传递值 c=", c)
      
      coro = simple_coro(1)
      print(next(coro))
      print(coro.send(2))
      print(coro.send(3))
      
      
      

    • 用协程实现计算平均数的函数

      def coro_avg():
          total = 0
          length = 0
          while True:
              try: 
                  value = yield total/length
              except ZeroDivisionError:
                  value = yield 0
              total += value
              length += 1
      
      my_avg = coro_avg()
      print(next(my_avg))
      print(my_avg.send(2))
      print(my_avg.send(3))
      
    • yieldyield from

      yield from实现的协程异步程序晦涩难懂, 在python3.4引用asyncio标准库之后被弃用

      yield from 用来驱动子程序中的循环并返回最终值

      def return_triple():
          while True:
              value = yield
              if value % 3 == 0:
                  return value
      
      
      def triple_recorder():
          while True:
              result = yield from return_triple()
              triple_array.append(result)
      
      triple_array = []
      coro = triple_recorder()
      next(coro)
      for i in range(100):
          coro.send(i)
      print(triple_array)
      

异步I/O

  • asyncio(异步)

    Python3.4引入的标准库, 替换yield from实现协程异步IO, 可以更好地实现异步程序

    实现原理: 自动维护了一个事件队列, 然后循环访问事件来完成异步的消息维护.

    import asyncio
    import time
    
    
    class Response:
        staus_code = 200
    
    
    async def sim_request(index):
        print(f"模拟发送请求 Index: {index}")
        response = Response()
        # 模拟网络延迟
        # 当前是单线程运行的, 如果调用的是time.sleep(1), 那么这个线程会被阻塞
        # 当前线程被阻塞之后, 不会让渡cpu资源, 异步的效率就不会体现
        await asyncio.sleep(1)
        print(f"request index {index}, response status_code: {response.staus_code}")
        return response.staus_code
    
    # 获取消息队列
    loop = asyncio.get_event_loop()
    
    # 包装任务
    task_array = []
    for i in range(100):
        task_array.append(sim_request(i))
    
    # 循环访问事件来完成异步的消息维护
    loop.run_until_complete(asyncio.wait(task_array))
    
    # 关闭事件循环
    loop.close()
    
    • 当前异步实际上有没有提高效率, 也关乎到你调用的第三方是不是异步的.

      这也是当前python异步的一个痛点, 就是丰富的第三方库不是都支持asyncio的.

    • 小技巧: 获取异步完成之后的所有返回值

      result = loop.run_until_complete(asyncio.gather(*task_array))
      print(result)
      
推荐书籍 <<图解TCP/IP>>

输入网址后发生了什么

  • 输入url

    统一资源定位器uniform resource locator

    • url组成

      https://www.baidu.com/
      协议://域名[:端口]/路径
      
      file:///H:/BaiduNetdiskDownload/
      
    • url作用

      定位指定的资源.

      url是uri的一个子集, uri是唯一标识符的意思. 身份证可以是uri, 但不是url.

  • DNS解析

    域名系统Domain Name System, 将域名解析为IP地址

    • 域名解析流程

      域名(www.baidu.com) -> DNS服务器->返回真实的IP地址36.152.44.96:443 -> 通过IP地址访问服务器

  • 客户端与服务器建立连接.

    客户端和服务端要互相确认身份, 建立连接通道后再发送数据

  • 客户端正式向服务端发送请求.

  • 服务端处理请求并返回结果

  • 浏览器接收到响应后, 做相应的渲染

TCP/IP五层协议

https://www.cnblogs.com/xjtu-lyh/p/12416763.html

img

img

img

  • 应用层

    为进程(客户端应用)和进程(服务器应用)之间提供服务. 应用层协议定义了应用之间进行数据交互的方式.

    浏览网页
    网易云
    用python模拟请求
    
    • 应用层协议
      • HTTP/HTTPS(超文本传输协议)
      • DNS(域名系统)
      • FTP(文件传输协议)
      • SMTP(邮箱传输协议)
  • 传输层

    负责向两个主机应用进程的通信提供服务.

    一个主机可以开启不同的因看应用, 同不同的服务器之间进行通信, 但是都是共用一个传输服务来发送和接受信息

    进程 <---> 进程
    
    • 传输层协议

      • TCP(传输控制协议)

        提供面向连接, (尽可能)可靠的数据传输服务.

        一对一
        
        面向连接指的就是, 客户端和服务端进行三次交互验证, 也就是TCP三次握手. 建立连接后才可以发送数据.
        
        • 文件传输(FTP)
        • 浏览网页(HTTP)
      • UDP(用户数据协议)

        提供无连接的, 不保证数据传输的可靠性

        一对多, 一对一, 多对多...
        
        • 直播
        • 实况游戏
  • 网络层

    决定了数据的转寄和路径选择, 封装和分组运输层产生的报文段/用户数据段.

    主机 <---> 主机
    
    • 网络层协议

      • IP协议

        • 公网IP

          也就是指的传统IP地址, 是唯一的.

        • 局域网IP

          ipconfig
          
  • 数据链路层

    负责两台主机之间的数据传输, 向网路层提供数据传输服务

    网卡 <---> 网卡
    
    • 数据链路层的作用

      比特流在传输媒介上传输时肯定有误差, 数据链路层的作用就是检错和纠错

      • *流量控制
      • 差错检测
      • 差错控制
  • 物理层

    物理层在局部局域网上传送数据帧, 在设备节点传输比特流.

    光纤 <---> 光纤
    
    • 物理层和数据链路层

      物理层才是真正传输数据的, 数据链路层是用来检查数据完整性的.
      

理解TCP/IP协议

  • 什么是TCP/IP协议

    TCP/IP并不是单个协议, 而是指一组协议的集合, 所以TCP/IP也叫TCP/IP协议族.

  • TCP/IP的作用

    起到了应用和硬件的之间承上启下的作用.

    手机的APP应用 -> 路由器 -> 光猫 -> 运营商网络 -> 互联网
    

TCP/IP三次握手

为了建立可靠的TCP连接, 尽可能地保证数据传输的正确性.

  • 三次握手的过程

    • 客户端向服务端发送带有SYN(同步序列编号)标识的数据包 --------------------------服务端确认了客户端的发送能力正常
    • 服务端向客户端发送了带有SYN-ACK(确认字符)标识的数据包-----------------------服务端确认了自己接受能力是正常
    • 客户端向服务端返回带有ACK标识的数据包-----------------------------------------------服务端确认了自己发送能力, 客户端接受正常
  • 第2次握手已经传回了ACK, 为什么服务端还要返回SYN?

    为了告诉客户端, 接收到的信号确实是其发送的信号, 表明了客户端到服务端的通信是正常的.

TCP/IP四次挥手

  • 四次挥手的过程

    我们以客户端作为主动关闭方来描述四次挥手过程
    
    • 客户端向服务端发送了一个FIN(finish)数据包-------------------------------------关闭客户端到服务端的连接通道
    • 服务端收到FIN后, 返回了ACK数据包----------------------------------------------------服务端已经知道了客户端到服务端的连接通道已关闭
    • 服务端发送FIN数据包至客户端, 关闭与客户端的连接------------------------------目的是关闭服务端到客户端的连接通道
    • 客户端返回ACK数据包确认------------------------------------------------------------------通知服务端客户端已经知道了服务端到客户端之间的连接通道已关闭

HTTPS

  • https加密的过程

    • 客户端向服务端发送通信请求

    • 服务端返回给客户端证书和密钥

    • 客户端通过CA中心验证证书的真实性

    • 客户端完成认证之后, 使用公钥对发送数据进行加密, 发送给服务端.

      • 非对称加密

        16 = 2* 8 也可以是 4 * 4
        公钥就是拿到了16这个结果
        私钥就是某个因数2
        通过这样的方式才可以得出唯一解8
        
    • 服务端收到加密后的请求数据后, 使用私钥进行解密.

    • 服务器和客户端使用对称加密进行通信

  • 中间人攻击

    插入到客户端和服务端之间的通信, 对服务端伪造客户都安, 对客户端伪造服务端, 拦截通信产生的数据.

    • 产生的条件

      我们的客户端要主动信任中间人的证书

抓包

抓包其实就是中间人攻击, 只是我们会主动信任像fiddler这样的代理软件.

对于服务端, 它伪装成客户端. 对于客户端, 它伪装成服务端.

  • 抓包软件

    • Fiddler

      https://www.telerik.com/fiddler
      
    • Charles

    • wireshark

  • web端抓包

    现代互联网环境几乎都是https协议的网站

    • 信任证书

      Qi3lw.png

      TOOLs -> Options -> HTTPS
      - 勾选Decrypt HTTPS traffic
      - 右上角点击Actions
      - Trust Root Certificates
      
  • App端抓包

    下载夜神模拟器
    
    • 打开远程终端连接

      QinkU.png

      Rules -> Options -> Connections -> Allow remote computes to connect
      
    • 把手机/模拟器的代理指向fiddler

      - wifi调出设置的时候要长按
      - 查看当前fiddler所在pc本地局域网ip
        - ipconfig/ifconfig
      
    • 在代理项中填写ip地址和fiddler端口, 默认是8888

    
    
    • 信任证书

    • App有一定的反爬措施, 第一件事就是修改请求协议

      • 双向验证

        需要客户端也带上证书

    • 解决请求协议上的反爬措施

      • 安装VirtualXposed_0.18.2, JustTrustMe

模拟请求

  • PostMan简单使用

    • GET

    • POST

      • form_data

        参数表单

      • x-www-form-urlencoded

        如果headers中content-type为x-www-form-urlencoded, 那么我们需要在当前选项下填写参数

      • raw

        请求的真实body内容.

课后作业

  • 学会用fiddler抓包https请求
  • 学会用fiddler抓包手机app中的请求
  • 学会使用postman模拟GET, POST请求.
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值