码迷,mamicode.com
首页 > 编程语言 > 详细

# Python基础

时间:2021-01-01 12:03:44      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:属性绑定   面向过程   重用   lse   case   star   开始   超过   形参与实参   

一、前言(总结)

1. 运行python程序的两种方式

交互式

  • 即时得到程序的运行结果,多用于调试

脚本的方式

  • 把程序写到文件里(约定俗成后缀为.py),用python解释器解释其中的内容
  • 用python python程序文件的路径

2. python程序运行的三个步骤

  • python C:\x\x\x. py
    1. 先启动python 3.8解释器
    2. 解释器发送系统调用,把x. py的内容从硬盘读入内存,此时x. 的内容全为普通字符,无语法意义
    3. 解释器开始解释执行刚刚读入内存的x. py的代码,开始识别python语法
  • 对比文本编辑器读取 C:\x\x\x. py文件内容的三个步骤
    • 1. 先启动文本编辑器
      
      1. 文本编辑器会发送系统调用,把x. py的内容从硬盘读入内存
        . 文本编辑器会将刚刚读入内存的内容控制输出到屏幕上,让用户看到结果(不会检查语法)
  • 总结:
      1. 二者在前两个阶段所做的事情完全一致
      1. 唯一不同的就是第三个阶段对读入内存的python代码处理方式不同

4.注释

    1. 注释是对代码的解释说明
    2. 被注释的代码不会被执行
    3. 为关键代码加注释

注释的形式

    1. #
      • 可以写在代码前一行,也可以写在同一行
      • 写在同一行时,需要空两个空格
    1. """ """ / ‘‘‘ ‘‘‘
      • 可以用来写单行注释,也可以用来写多行注释

二、基础

1. 变量

  1. 什么是变量?

    1. 变量是可以变化的量,量指事物的状态,比如年龄,性别,身高等。
  2. 为什么要有变量

    1. 为了让计算机能够像人一样去记忆事务的某种状态,并且状态可以改变。
    2. 程序执行的本质就是一系列状态的变化,变是程序执行的直接体现,所以我们需要有一种机制能够反映或者说是保存下来程序执行时状态,以及状态的变化。
  3. 变量的基本使用

    原则:先定义后引用

    name = ‘Stephane‘  # 定义
    print(name)  # 引用
    

    Python的值传递都是引用传递(内存地址的传递)

    1. 内存管理:垃圾回收机制

      1. 垃圾:当一个变量值被绑定的变量名的个数为0,该变量值无法被访问到,称之为垃圾

      2. 引用数:变量绑定变量名的个数

        1. 引用数增加
        x = 10  # 10的引用次数为1
        y = x   # 10的引用次数为2
        z = y   # 10的引用次数为3
        
        1. 使引用数减少的方式
    2. del解除绑定

        2. 变量名指向另一个变量
      
      x = 10  # 10的引用次数为1
      y = x  # 10的引用次数为2
      z = y  # 10的引用次数为3
      del x  # 10的引用次数为2
      del y  # 10的引用次数为1
      
    3. 直接引用与间接引用:

    • 直接引用:直接与值关联
      • 栈区指向堆区
    • 间接引用:将值放进容器(如字典、列表等)关联
      • 堆区指向堆区
    1. 变量组成部分:

      1. 变量名:是指向等号右侧值的内存地址的,用来访问等号右侧的值
  4. 赋值符号: = 将变量值的内存地址绑定给变量名

    1. 变量值:代表记录的事物的状态

    2. 变量命名:

    3. 命名基础

      原则: 变量名应该见名知意

      1. 只能由字母、数字、下划线组成
    4. 不能用数字开头

      1. 不能使用关键字或内置函数命名变量

      ps:尽量不要用拼音,不要用中文,在见名知意的情况下,尽可能短

  5. 命名风格

    1. 纯小写加下划线的方式

    2. 驼峰体

    3. 变量值的三个重要特征

  6. id:反映的是变量值的内存地址(id不是地址),内存地址不同,则id不同

    1. id不同,值可能相同
    2. id相同,值一定相同
         name = stephane
    print(id(name))
    
    2. type:不同类型的值表示记录不同的状态
    
         name  = stephane
    print(type(name))
    
    3. value:值本身
    
         name = stephane
    print(name)
    
    5. is 与 ==
    
    1. is:比较左右两值身份id是否相等

      一般来说,两个值相等的整数变量id相同([-5, 256]),因为python解释器在启动之时就会在内存中事先申请一部分空间放入一系列常用的整数,被称为小整数池,事实上其他变量也有类似优化

    2. ==:比较左右两个值是否相等

2. 数据类型(该处没有集合和元组)

  1. 数字类型

    1. int 整型

      1. 作用:记录年龄、号码、个数等的整数变量

      2. 定义:变量名 = 整数

        age = 18
        print(type(age))  # int
        
    2. float 浮点型

      1. 作用:记录小数、身高体重等的小数变量

      2. 定义:变量名 = 小数

        height = 1.78
         print(type(height))  # float
        
    3. 数字类型的其他使用

      1. 变量进行算术运算后的结果可以赋值给另一个数字变量
        2. 数字变量可以自加
        3. 可以输出一个算式的结果
        4. int 可与float之间进行算术运算
  2. 字符串类型

    1. 作用:记录描述性质的状态、名字、一段话等

    2. 定义:用(‘ ‘," ",‘‘‘ ‘‘‘,""" """)包含的一串字符

      str1 = ‘19‘
      str2 = "Hi"
      str3 = ‘Do you know "Python"?‘
      str4 = "I‘m a engineer!"
      str5 = ‘I\‘m a engineer‘
      # str6和str7赋值的字符串可换行,输出形式包括换行
      str6 = ‘‘‘Hello world‘‘‘
      str7 = """Hello
      everyone!"""  
      
    3. 其他使用:

      1. 内层和外层不能同时为单引号或同时为双引号
      2. 字符串之间可以相加(效率极低)
      3. 字符串与数字(整型)相乘,会重复输出相应遍的字符串
  3. 列表类型:索引对应值,从0开始

    1. 作用:记录多个值,并且按照索引取指定位置的值

    2. 定义:在中括号内用逗号分隔开多个任意类型值的元素,一个值称为一个元素

      list1 = [0, 0, 2, 6]
      print(list1[3])  # 2
      
    3. 其他使用:

      1. 嵌套取值
      2. 排序
      3. 遍历
      4. 查找等
    4. 在内存中的存储方式:

      1. 在每一个索引中存储着值所对应的地址
      2. 地址对应相应的元素所在的空间
  4. 字典类型:key对应值,其中key通常为字符串类型,key对值有描述性的功能

    1. 作用:存多个值,每个值都有唯一一个key与之对应,key对值有描述作用

    2. 定义:在{}中由逗号分开为多个key: value,key必须是不可变类型,value可以是任意类型

      dict1 = {"Name": "Stephane", "Age": 19, "height": 178}
      print(dict["Name"])  # Stephane
      
    3. 其他使用:

      1. 嵌套取值
      2. key遍历
      3. key-value遍历等
  5. 布尔类型

    1. 作用:用来记录真假两种状态

    2. 定义:True False

      a = True
      b = False
      
    3. 其他使用:用来当作判断条件

  6. 可变不可变类型

    1. 定义:
      1. 可变类型:值改变,id不变,改的是原值
      2. 不可变类型:值改变,id改变,产生了新的值,没有改变原值,即只能通过赋值来改变值
    2. 验证:
      1. 不可变类型:(int float str被设计成了不可分割的整体,不能被改变)
        • 整型
        • 浮点型
        • 字符串类型
        • 布尔类型
        • 元组
      2. 可变类型:
        • 列表
        • 字典

3. 垃圾回收机制详解

  1. 定义:垃圾回收机制(GC)是Python解释器的一种机制,专门用来回收不可用的变量值所占用的空间

  2. 引用计数

    x = 10
    print(x)  # 10
    
    l = [10, x]  # 间接引用
    dict1 = {"age": x}  # 间接引用
    z = x  # 直接引用
    
    # id相同
    print(id(x))  # 直接引用
    print(id(l[1]))
    
  3. 标记清除

    1. 变量存储位置
      1. 变量名存在栈区,同时指向一块地址
      2. 变量名指向的地址在堆区中,存放变量值
      3. python垃圾回收机制管理的是堆区(当堆区地址被回收,栈区地址自然亦被回收)
    2. 标记清除
      • 解释器运行一段时间,扫描栈区内存,若堆区的变量值没有被直接引用,则将其清除
  4. 分代回收

    1. 背景:每次回收内存,都需要把所有对象的引用计数都遍历一遍,浪费时间,于是引入分代回收,采用“空间换时间”的策略

    2. 分代回收的核心思想:在历经多次扫描的情况下,都没有被回收的变量,GC机制认为其比较常用,于是降低对此类变量的扫描频率,依据权重,将其分为多个代,权重越高,扫描频率越低

4. 用户交互

  1. 接受用户输入

    • python3
      • input()
    • python2.7
      • input()
        • 用户必须明确输入的数据类型,即该函数会识别输入的类型
      • raw_input()
        • 用法同python3中的input()
  2. 字符串格式化输出

    • print()

      • %

        1. python诞生之初就有,兼容性最好
        2. %用法与C语言中printf()中类似
        3. %s中不止可以引用字符串
        name = ‘Stephane‘
        age = 18
        
        print(‘My name is %s, I am %d‘ % (name, age))
        print(‘My name is %(name)s, I am %(age)d‘ % {‘name‘: ‘Ste‘, ‘age‘: 18})  # 字典形式
        
      • (重要)str.format()(python2.6及之后可用)

        1. 用来进行字符串格式化的函数(非输出函数)

        2. 在()中写变量名,在str中变量名由{}代替{}中数字代表变量序号

        3. 可以用字典的键值对传值

        4. 在str中可用一些格式使某个特定字符对字符串进行填充

          • : 表示有附加的字符
          • <表示原字符串左对齐,>表示右对齐,^表示居中显示
          • >或<前跟附加的字符形式
            • 不能用空格填充
        5. 若变量之前已赋值有变量值,则可以使用索引进行标记,否则需要使用变量名进行标记

          # 使用索引标记
          name = ‘Stephane‘
          age = 18
          
          print("My name is {0}, I am {1}".format(name, age))
          
          # 使用变量名标记
          print("Today is {week}.".format(week=‘Mon‘))
          
  • 最后的数字表示字符串总长度(若无附加字符时,就已超过总长度,则不会出现附加字符)

    1. str中可对数据做一些精度要求
    • 变量后可跟:.精度+f,表示精确到某位小数

    • f‘ ‘(python3.5及以后可用但效率低)

      1. f‘格式化字符串‘

      2. 变量由{}括起

      3. {}内的字符串可以被当作表达式运行

      name = ‘Stephane‘
      age = 18
      
      print("My name is {0}, I am {0}".format(name, age))
      
      res = ("My age is {age}, I am {name}".format(name=‘ZHS‘, age=18))
      print(res)
      
      print(‘{x:=<10}‘.format(x=‘Start‘))  # Start=====
      print(‘{x:=^10}‘.format(x=‘Start‘))  # ==Start===
      
      print(‘{x:.3f}‘.format(x=1020.363520))  # 1020.364
      
name = ‘Stephane‘
age = 18
print(f‘My name is {name}, and I am {age} years old.‘)
print(f‘10+3‘)  #  10+3
print(f‘{10+3}‘)  #  13
f"{print(‘abcd‘)}"  #  abcd

?

5. 基本运算符

  1. 算术运算符

    1. -
    2. *
    3. **
    4. / 除以
    5. // 整除
    6. % 取模
  2. 赋值运算符

    1. =

    2. 增量赋值

      1. +=
      2. -=
      3. *=
      4. /=
      5. //=
      6. **=
    3. 链式赋值

      • 变量1 = 变量2 = 变量3 = ... = value
    4. 交叉赋值(交换变量值)

      • python提供了更简单的方式
      • 变量1, 变量2 = 变量2, 变量1
      a = 10
      b = 20
      a, b = b, a
      print(a, b)  #  20 10
      
      
    5. 解压赋值(若解压字典,默认解压key)

      1. 将列表中的元素拆分为单独变量并一一对应
      2. 元素一般与对应变量个数相同
        • 若变量数小于元素数(取前n个元素)
          1. 可对列表做切片处理
          2. 一般可在已知变量最后加*_(其他的名字也可以),接受剩余的所有元素,形成一个剩余元素组成的列表
        • 若变量数小于元素数(取后n个元素)
          1. 可对列表做切片处理
          2. 一般可在已知变量之前加*_接受剩余的所有元素,形成一个剩余元素组成的列表
      3. 原列表仍存在
      list1 = [1, 2, 3, 4]
      a, b, c, d = list1
      print(a, b, c, d)  #  1 2 3 4
      print(list1)  #  [1, 2, 3, 4]
      
      
      list1 = [1, 2, 3, 4]
      a, b, c, *_ = list1
      print(a, b, c)  #  1 2 3
      print(_)  #  [4]
      
  3. 比较运算符

    1. >
    2. <
    3. >=
    4. <=
    5. ==
    6. !=
  4. 逻辑运算符:用来连接多个条件加以判断的运算符

    1. not:逻辑否
    2. and:逻辑与
    3. or: 逻辑或
    4. 优先级:
      1. 如果一串and连接或一串or连接,按照从左向右顺序依次运算即可
      2. 如果混用,考虑优先级(否 > 与 > 或)
    5. 短路运算:(偷懒原则,偷懒到哪个位置,就把哪个位置的值返回)
      1. 用and连接的条件如果前一个条件为假,则不判断后一个条件 结果一定为假
      2. 用or连接的条件如果前一个为真,则不判断后一个条件 结果一定为真
      3. 在同一模块中的if...elif...else...结构中,若执行一个,则其后面同等级的判断语句不被执行
  5. 成员运算符:

    1. in
      • 适用于可遍历的变量,判断子变量是否在大变量中
    2. not in
      • 适用于可遍历的变量,判断子变量是否不在大变量中
  6. 身份运算符:is

    • 如果若干个变量的id都相同,即指向同一块地址,则变量1 is 变量2 is 变量3的结果为True,反之,则为False

6. 流程控制

6.1 if判断

  1. 什么是if判断:判断条件是否成立的语句

  2. 为什么要有if判断:增强程序的流程多样性

  3. 怎么用:

    """
    语法1:
    if 条件:
    	语句1
    	语句2
    	...
    
    语法2:
    if 条件:
    	语句1
    	...
    else
    	语句1
    	...
    	
    语法3:
    if 条件1:
    	语句1
    	...
    elif 条件2:
    	语句1
    	...
    ...
    else:
    	语句1
    	...
    """
    
  • if语句中若无执行语句也可加"pass"或"..."占位符,表示无语句执行

6.2 while循环(条件循环)

  1. while循环的语法与基本使用

    """
    while 条件:
    	代码1
    	代码2
    	...
    """
    
  2. 死循环与效率问题

    1. 死循环:不能结束的循环,循环条件永远为真
    2. 纯计算无IO的死循环会导致致命的效率问题
  3. 循环的应用

  4. 退出循环的两种方式

    1. 条件为假
    2. while + break:结束本层循环
  5. while循环嵌套

    """
    while 条件1:
    	语句1
    	...
    	while 条件2:
    		语句2
    		...
    或
    while 条件1:
    	语句1
    	...
    	while 条件2:
    		语句2
    		...
    		break
    	break
    """
    
  6. while + continue:结束本次循环

  7. while + else

    """
    while 条件:
    	语句1
    	...
    else:
    	语句2
    	...
    """
    
    • else包含的代码在while循环结束后,并且while循环没有被break打断才会运行

6.3 for循环

  1. for循环的语法与基本使用

    1. 什么是for循环:for循环shipython提供的第二种循环机制

    2. 为什么要用for循环:

      1. 理论上for循环可以做的事情,while循环也可以做
      2. 在循环取值(遍历取值)上for循环更简洁
    3. 如何用for循环:

      """
      for 变量名 in 可迭代对象:
      # 可迭代对象可以是list,string,dictionary,set,tuple
      	语句1
      	语句2
      	...
      """	
      
  2. for循环应用

    1. for循环可以用来遍历
    2. for循环可以用来计数,range(self, stop, step)函数
  3. for循环嵌套

    """
    for 变量名1 in 可迭代对象1:
    	语句1
    	for 变量名2 in 可迭代对象2:
    		语句2
    		...
    """
    
  4. for + break

  5. for + continue

  6. for + else

  7. while循环与for循环的异同:

    1. 异:
      1. while循环为条件循环,循环次数取决于条件何时为假
      2. for循环是取值循环,循环次数取决于可迭代对象元素个数,中止for循环只能用break
    2. 同:都是循环,while循环的能量大于for循环

7. 深浅copy

7.1 深copy

  • 导入copy模块

  • 使用函数copy.deepcopy()

  • 在两个变量中对于任意不可变类型变量,指向的地址都相同,对于任意可变类型,指向地址都不同

    ![](引用图像\批注 2020-07-30 000912.png)

7.2 浅copy

  • copy后,二者指向同一块地址(把原可变类型第一层内存地址不加区分完全copy一份给新的可变类型),对任意一个元素改变,则会对地址指向的内容改变,即二者内容都会更新
  • 直接将可变变量互相赋值,并使用非直接赋值方法进行修改,为浅copy
  • 使用.copy()函数,使不可变类型的元素修改独立开来,但对于可变类型元素,仍不可修改
  • Linux的文件硬链接也是浅copy

8. 基本数据类型及其内置方法

8.1 数字

8.1.1 int
  1. 作用:表示整数
  2. 定义:变量名 = 数字
  3. 类型转换:
    1. int() 也可用于其他进制转十进制
      • 当对象为字符串时,可用int(‘‘数字‘‘, 进制数),数字为十进制时,可不写进制数
    2. bin() 转二进制
    3. oct() 转八进制
    4. hex() 转十六进制
  4. 使用:无内置方法
8.1.2 float
  1. 作用:表示非整数
  2. 定义:变量名 = 数字
  3. 类型转换:float()
  4. 使用:无内置方法
8.1.3 complex
  1. 作用:表示复数
  2. 定义:变量名 = 实部 +/- 虚部j
    1. 变量名.real = 数字 实部
    2. 变量名.img = 数字 虚部
  3. 类型转换:complex
  4. 使用:无内置方法

8.2 字符串

  1. 作用

  2. 定义:

    1. 变量名 = 字符串
    2. str.format
  3. 类型转换:str() 把任意其他类型转换成字符串

  4. 使用:

    1. 优先掌握

      1. 按索引取值

        # 正向取
        str = "Hello"
        print(str[1])  # e
        # 反向取
        print(str[-1])  # o
        # 只能取
        str[1] = ‘a‘  # 错误,字符串不可修改
        
      2. 切片(从一个大的字符串中拷贝出一个子字符串)

        # 左闭右开区间
        str = "HelloWorld"
        print(str[0:3])  # Hel
        # 步长
        print(str[0:5:2])  # Hlo (start <= stop)
        # 反向步长
        print(str[5:0:-2])  # Wle (start >= stop)
        
      3. 长度(len)

        str = "Hello"
        print(len(str))  # 5
        
      4. 成员运算符 in 和 not in

        # 判断一个子字符串是否在大字符串中
        str = "hello"
        print(‘h‘ in str)  # True
        print(‘a‘ not in str)  # True
        
      5. 移除空白 strip

        # strip只去两边,不去中间
        str = " hello World "
        print(str.strip())  # "hello world"
        print(str.lstrip())  # "hello world "
        print(str.rstrip())  # " hello world"
        str1 = "********hello********"
        print(str1.strip(‘*‘))  # "hello"
        
      6. 切分split

        # 把一个字符串按照某种分隔符(默认为空格)进行切分,得到一个列表(不包括分隔符)
        # split(sep, maxsplit)
        str = "a:b:c:d"
        list1 = str.split(‘:‘)
        print(list1)  # [‘a‘, ‘b‘, ‘c‘, ‘d‘]
        list1 = str.split(‘:‘, 2)
        print(list1)  # [‘a‘, ‘b‘, ‘c:d‘]
        
      7. 循环

        # for循环
        
    2. 需要掌握

      1. lower,upper

        str = "abCdEF"
        print(str.lower())  # abcdef
        print(str.upper())  # ABCDEF
        
      2. startswith, endswith

        str = "abcdefg"
        print(str.startswith("abc"))  # True
        print(str.endswith("efg"))  # True
        
      3. split, rsplit

        str = "a:b:c:d"
        list1 = str.split(‘:‘)
        print(list1)  # [‘a‘, ‘b‘, ‘c‘, ‘d‘]
        
        list1 = str.rsplit(‘:‘)
        print(list1)  # [‘a‘, ‘b‘, ‘c‘, ‘d‘]
        
        list1 = str.rsplit(‘:‘, 2)
        print(list1)  # [‘a:b‘, ‘c‘, ‘d‘]
        
      4. join

        # 将列表拼接成字符串
        # 列表中每个元素为字符(串)
        list1 = [‘a‘, ‘b‘, ‘c‘, ‘d‘]
        str1 = ‘:‘.join(list1)  # a:b:c:
        
      5. replace

        # replace(old, new, count)
        str1 = "Hello World"
        print(str1.replace("H", "U"))  # "Uello World"
        
      6. isdigit

        # 判断字符串是否由纯数字组成
        str1 = "123123123123"
        str2 = "1a2b3c"
        print(str1.isdigit())  # True
        print(str2.isdigit())  # False
        
    3. 了解

      1. find, rfind

        str1 = "Stephane"
        # find和rfind若找不到会返回-1
        print(str1.find(‘e‘))  # 2  返回查找字符串在大字符串中的起始索引
        print(str1.rfind(‘e‘))  # 7  返回从右查找字符串在大字符串中的起始索引
        
      2. index, rindex

        str1 = "Stephane"
        # index和rindex若找不到会直接报错
        print(str1.find(‘e‘))  # 2  返回查找字符串在大字符串中的起始索引
        print(str1.rfind(‘e‘))  # 7  返回从右查找字符串在大字符串中的起始索引
        
      3. count

        str1 = "Ada is Ada"
        print(str1.count("Ada"))  # 2 返回目标字符串出现的次数
        
      4. center

        # center(wideth, fullchar) 使字符串居中,并用填充字符填充至指定长度
        str1 = "hello"
        print(str1.center(11, ‘*‘))  # ***hello***
        
      5. ljust

        # ljust(wideth, fullchar)使字符串左对齐,并用填充字符填充至指定长度
        str1 = "hello"
        print(str1.ljust(11, ‘*‘))  # hello******
        
      6. rjust

        # ljust(wideth, fullchar)使字符串右对齐,并用填充字符填充至指定长度
        str1 = "hello"
        print(str1.rjust(11, ‘*‘))  # ******hello
        
      7. zfill

        # zfill(wideth)使字符串右对齐,并用0填充至指定长度
        str1 = "hello"
        print(str1.rjust(11, ‘*‘))  # 000000hello
        
      8. expandtabs

        # expandtabs(tabsize)若字符串中有制表符,指定制表符所包含的空格数
        str1 = "hello\tworld"
        print(str1.expandtabs(2))  # hello  world
        
      9. capatalize

        # capatalize()使字符串首字母大写
        
      10. swapcase

        # swapcase()使字符串中的大小写反转
        
      11. title

        # title()使每个单词首字母大写
        
      12. is其他

        # islower()
        # isupper()
        # isspace()
        # istitle()
        # isalnum()
        # isalpha()
        # isidentifier() 判断是否为合法标识符
        # isdecimal()
        # isnumberic() 可判断数字、中文数字、罗马数字等
        

8.3 列表

  1. 作用:按位置存多个值

  2. 定义:list1 = [ ]

  3. 类型转换:list()可以转换能被for循环遍历的类型

  4. 使用

    1. 优先掌握

      1. 按索引存取值

        list1 = [111, 222, 333, 444]
        # 正向取
        list1[1]
        # 反向取
        list1[-1]
        # 可修改
        list1[0] = 100
        list1[4] = 555  # 索引不存在,报错
        
      2. 切片

        list1 = [11, 20, 36, 33, 22, 10]
        print(list1[0:5:2])  # [11, 36, 22]
        print(list1[5:0:-2])  # [10, 33, 20]
        
      3. 长度

        len()
        
      4. 成员运算

        list1 = [111, 222, 333, 444]
        print(111 in list1)  # True
        print(555 not in list1)  # True
        
      5. 追加

        # append(object)
        list1 = [111, 222]
        list1.append(333)
        list1.append(444)
        print(list1)  # [111, 222, 333, 444]
        
      6. 插入

        # insert(index, object)
        list1 = [111, 333]
        list1.insert(1, 222)
        print(list1)  # [111, 222, 333]
        
      7. 列表添加可迭代值的元素

        # extend(iterable)
        list1 = [111, 222, 333]
        list2 = [444, 555]
        list1.extend(list2)
        print(list1)  # [111, 222, 333, 444, 555]
        
      8. 删除

        list1 = [111, 222, 333]
        del list1[0]  # 通用删除方法,没有返回值
        print(list1)  # [222, 333]
        list1 = [111, 222, 333]
        
        # pop(index)  返回值为被删除的元素,不指定索引默认删最后一个元素
        x = list1.pop(2)
        print(list1)  # [111, 222]
        print(x)  # 333
        
        # remove(object)  无返回值,删除指定元素
        list1 = [111, 222, 333]
        x = list1.remove(111)
        print(list1)  # [222, 333]
        print(x)  # None
        
      9. 循环

        list1 = [111, 222, 333]
        for item in list1:
        
    2. 需要掌握

      1. count()

        list1 = [10, 100, 10, 10, 1000]
        print(list1.count(10))  # 3
        
      2. index()

        list1 = [101, 1002, 103, 104, 10005]
        print(list1.index(103))  # 2,从左开始查找
        
      3. clear()

        # clear() 将整个列表的元素删除,留下空列表
        list1 = [101, 1002, 103, 104, 10005]
        list1.clear()
        print(list1)  # []
        
      4. reverse()

        # reverse() 将列表元素倒置
        list1 = [101, 1002, 103, 104, 10005]
        list1.reverse()
        print(list1)  # [10005, 104, 103, 1002, 101]
        
      5. sort()

        # sort(key, reverse) # 默认升序,当reverse为真,为降序 
        list1 = [101, 1002, 103, 104, 10005]
        list1.sort()
        print(list1)  # [101, 103, 104, 1002, 10005]
        list1 = [100, ‘a‘, 1000]
        list1.sort()  # 不同类型混合,不可排序
        
    3. 了解

      1. 列表比大小,原理同字符串比大小

        # 对相应位置的元素为同种类型
        list1 = [‘aaa‘, 123, 100]
        list2 = [‘a‘, 100, 1000]
        print(list2 < list1)  # True
        
    4. 补充(列表模拟)

      1. 队列

        • first in first out(FIFO),先进先出
        list1 = []
        # 入队操作
        list1.append(100)
        list1.append(200)
        list1.append(300)
        print(list1)  
        
        # 出队操作,先进先出
        print(list1.pop(0))
        print(list1.pop(0))
        print(list1.pop(0))
        
      2. 堆栈

        • last in fast out(LIFO),后进先出
        list1 = []
        # 入队操作
        list1.append(100)
        list1.append(200)
        list1.append(300)
        print(list1)  
        
        # 出队操作,后进先出
        print(list1.pop())
        print(list1.pop())
        print(list1.pop())
        

8.4 元组(不可变的列表)

  1. 作用:按照索引存放多个值,只能读,不能写

  2. 定义:tuple1 = () ()内用逗号分割开多个任意类型的元素

    • 元组中的索引值也是指向元素内存地址

    • 单独一个括号,只是包含,并不是元组

      tuple1 = (100)   # int
      tuple1 = (100,)  # tuple
      
  3. 类型转换:tuple()

  4. 使用

    1. 优先掌握
      1. 按索引取值
      2. 切片
      3. 长度
      4. 成员运算
      5. 循环
    2. 需要掌握
      1. 寻找 index()
      2. 计数 count()
    3. 了解

8.5 字典

  1. 作用

  2. 定义:dict1 = {},value可以是任意类型, key只能是不可变类型

    • # 可使用这种方式进行定义/类型转换
      dict1 = dict(x=1, y=2, z=3)
      print(d)  # {‘x‘: 1, ‘y‘: 2, ‘z‘: 3}
      print(type(d))  # dict
      
      # 也可使用这种方式创建字典(快速初始化),但value为相同值
      Ages = [‘a‘, ‘b‘, ‘c‘]
      dict1 = {}.fromkeys(Ages, 19)
      print(dict1)
      
  3. 类型转换:dict()

    • # 可以使用这种方法
      info = [
          [‘11‘, ‘22‘],
          [‘33‘, ‘44‘],
          [‘55‘, ‘66‘]
      ]
      
      info = dict(info)
      print(info)
      
  4. 使用

    1. 优先掌握

      1. 按key存取值

        dict1 = {‘a‘: 100, ‘b‘: 200}
        print(dict1[‘a‘])  # 100
        dict1[‘a‘] = 150  # 赋值
        dict1[‘c‘] = 300  # 创建key
        print(dict1)  # {‘a‘: 150, ‘b‘: 200, ‘c‘: 300}
        # 创建字典时,若一个key出现若干次,则以最后一次出现的value为准
        
      2. 长度len

        # 若有若干个key相同,则该key只统计一次
        dict1 = {‘a‘: 100, ‘b‘: 200, ‘a‘: 150, ‘b‘: 250}
        print(len(dict1))  # 2
        
      3. 成员运算

      4. 删除

        # del
        dict1 = {‘a‘: 100, ‘b‘: 200}
        del dict1[‘a‘]
        print(dict1)  # {‘b‘: 200}
        
        # pop()    根据key删除
        dict1 = {‘a‘: 100, ‘b‘: 200}
        l = dict1.pop(‘a‘)
        print(dict1)  # {‘b‘: 200}
        print(l)  # 100    pop()返回删除的键值对的值
        
        # popitem()    随机删除
        dict1 = {‘a‘: 100, ‘b‘: 200}
        l = dict1.pop(‘a‘)
        print(dict1)
        print(l)  # popitem()返回一个元组,包含删除的key和value
        
      5. keys()、values()、items()

        # keys()   	返回字典中所有的key组成的列表
        # values()	返回字典中所有的value组成的列表
        # items()	返回所有键值对组成的列表(每个键值对分别为子元组)
        
      6. fromkeys()

        # fromkeys([key1, key2], value)  给字典中添加可迭代列表中的所有key,value均为fromkeys的第二个参数值,返回值为包含相应键值对的字典
        
        # 但子列表中所有的key对应的value始终指向同一块地址
        dict1 = {}
        dict2 = dict1.fromkeys([‘a‘, ‘b‘], 100)
        print(dict1)  # {}
        print(dict2)  # {‘a‘: 100, ‘b‘: 100}
        
  5. 循环

    dict1 = {}
    for key in dict1.keys():
        ...
    for value in dict1.values():
        ...
    for key, value in dict1.items():
        ...
    
  6. 需要掌握

    1. clear()

    2. update()

      dict1 = {‘a‘: 100, ‘b‘: 200}
      dict2 = {"c": 100}
      dict2.update(dict1)
      print(dict2)  # {‘c‘: 100, ‘a‘: 100, ‘b‘: 200}
      # 旧字典当中存在的key会被更新,旧字典当中不存在的key会由新字典追加
      
    3. setdefault()

      # setdefault(key, defalut)  defalut为默认的值,返回值为字典中该键对应的值
      
      dict1 = {‘a‘: 100, ‘b‘: 200}
      dict1.setdefault(‘b‘: 10000)  # 若原字典中有该键,则忽略其中的默认值,即不进行修改
      dict1.setdefault(‘c‘: 10000)  # 若原字典中没有该键,则将该键添加到字典各种,并将默认值作为其值
      
      print(dict1)  # {‘a‘: 100, ‘b‘: 200, ‘c‘: 10000}
      
    4. get() 容错性好

      # get(key)   返回key对应的value,若key不存在会返回None
      dict1 = {‘a‘: 100, ‘b‘: 200}
      print(dict1.get(‘a‘))  # 100
      print(dict1.get(‘c‘))  # None
      

8.6 集合

  1. 作用

    1. 关系运算
      1. | 并集∪
      2. & 交集 ∩
      3. - 差集 返回前者独有的元素组成的集合
      4. ^ 对称差集 返回二者各自独有的元素组成的集合
      5. == / > / < 判断集合是否相等/大于/小于
    2. 去重(有局限性)
      1. 只能针对不可变类型
      2. 无法保证原来的顺序
  2. 定义 s = set(...), s = set() 定义空集合

    • 在{}内用","分隔多个元素

    • 每个元素必须是不可变类型

    • 集合内没有重复的元素

    • 集合内元素无序

  3. 类型转换

    1. 元素为不可变类型
    2. 重复的元素只会保留一次
    3. 字典进行转换默认只保留key
  4. 使用

    1. 优先掌握

      1. intersection(s) ∩

      2. union(s) ∪

      3. difference(s) -

      4. symmetric_difference(s) ^

      5. issuperset(s) >=

      6. issubset(s) <=

      7. discard()

        # discard(args)
        
        # 删除指定元素,若不存在该元素,do nothing
        
        s = {100, 200, 300}
        s.discard(100)
        print(s)  # {200, 300}
        s.discard(400)
        print(s)  # {200, 300}
        
      8. remove()

        # remove(args)
        
        # 删除指定元素,若不存在该元素,则会报错
        s = {100, 200, 300}
        s.discard(100)
        print(s)  # {200, 300}
        s.discard(400)
        print(s)  # 报错
        
      9. update()

        # update(s)  将新集合中的元素加入旧集合,并去重
        
        s = {100, 200, 300}
        s1 = {150, 200, 300, 450}
        s.update(s1)
        print(s)  # {100, 200, 300, 150, 450}
        
    2. 需要掌握

      1. isdisjoint()

        # isdisjoint(s)  若两个集合交集为空,返回True,否则返回False
        
        s = {100, 200, 300}
        s1 = {150, 200, 300, 450}
        print(s.isdisjoint(s1))  # False
        s1 = {450, 150}
        print(s.isdisjoint(s1))  # True
        
      2. pop()

      3. clear()

      4. add()

8.7 总结与分类

  1. 有序/无序
    1. 有序(序列类型)
      1. 列表
      2. 元组
    2. 无序
  2. 存一个值/存多个值
    1. 存一个值:原子类型
      1. 数字
      2. 字符串
    2. 存多个值:容器类型
      1. 列表
      2. 字典
      3. 集合
      4. (元组)
  3. 可变/不可变
    1. 可变
      1. 列表
      2. 字典
      3. 集合
    2. 不可变
      1. 数字
      2. 字符串
      3. 元组

9. 文件处理

9.1 字符编码

  • 人类与计算进行交互时,使用人类字符,而计算机只识别二进制数,因此字符必须翻译为二进制数

  • 翻译的过程中必须参照特定标准,称之为字符编码表,存放字符与二进制数的映射关系

  • unicode(内存使用的固定编码)

    • 兼容万国字符

    • 与万国字符编码都有对应关系

    • 采用16位二进制数对应一个中文字符串(个别生僻使用32、64位二进制数)

    • 老的字符编码都可以转化为unicode,但不能通过unicode互相转化

      ![](引用图像/批注 2020-08-24 000408.png)

  • utf-8(unicode translate format-8) 可以作为字符编码格式直接存入硬盘

    • 英文 -> 1Byte
    • 汉字 -> 3Byte
    • ...
9.1.1 编码与解码
9.1.1.1 编码
  • 由字符转化为内存中的unicode,以及由unicode转化成其他编码的过程,称为编码encode
9.1.1.2 解码
  • 由内存中的unicode转化成字符,以及由其他编码转化成unicode的过程,称为解码decode
9.1.1.3 编码解码
  • 编码
x = ‘上‘
x = x.encode(‘gbk‘)  # 将unicode转为gbk格式
print(x, type(x))  # b‘\xc9\xcf‘ <class ‘bytes‘>
  • 解码
x = b‘\xc9\xcf‘
x = x.decode(‘gbk‘)  # 按什么方式编码,则按什么方式解码
print(x, type(x))  # 上 <class ‘str‘>
9.1.2 如何保证不乱码
  • 编码格式应设置成支持文件内容字符的格式

  • 以什么编码格式存的就必须以什么编码格式取

  • 指定文件头,修改默认的编码,即存入硬盘时的编码格式

    • 在文件首行写

      # coding: xxx    如gbk,utf-8
      
    • python3默认为:utf-8

    • python2默认为:ASCII

  • python3的str类型默认直接存成unicode格式,不会乱码

  • 保证python2的str类型不乱码,需强制存为unicode格式

    str = u‘你好‘
    
  • python2有两种字符串类型

    • str
    • unicode
    str1 = ‘你好‘  ---->  str  # 字符串值会按照文件头指定的编码格式存入变量值的内存空间
    str2 = u‘你好‘  ----> unicode
    
9.1.3 文件头
  • 设置文件头模板

    • File ---- Settings ---- File and Code Templates ---- Python Script

      ![](引用图像/批注 2020-09-01 125649.png)

9.2 文件处理

  • 什么是文件
    • 文件是操作系统提供给用户/应用程序操作硬盘的一种接口
  • 为何要用文件
    • 用户/应用程序可以通过文件将数据永久地保存在硬盘中,即操作文件就是操作硬盘
    • 用户/应用程序直接操作的都是文件,对文件进行的所有的操作,都是向操作系统发送系统调用,然后再由操作系统将其转换成具体的硬盘操作
  • 如何用文件
    • open()
9.2.1 文件处理基本流程
  1. 打开文件

    • open()功能的使用
  • 返回值为文件对象(句柄)
    - f = open(r‘文件路径‘, [模式(默认为rt)], [encoding=‘xxx‘])
    - r 指rawstring 原生字符串,也可不加"r",将文件路径的"\"改为"/"
    - 类UNIX系统的默认encoding为utf-8,Windows默认为GBK
  1. 读/写文件
    • f.read(n) 从指针当前位置开始读,n表示读 n个字节,在t模式下指n个字符
    • f.write() 从指针当前位置开始写
  2. 关闭文件
    • f.close() 回收操作系统的资源
    • del f
9.2.2 资源回收与with语法
  • with:上下文管理

    # 在with的子代码运行结束后,自动执行f.close()
    with open(‘a.txt‘) as f:
    	res = f.read()
        print(res)
    # 可打开多个文件
    with open(‘a.txt‘) as f, open(‘b.txt‘) as f1:
        res = f.read()
        print(res)
        res1 = f1.read()
        print(res1)
    
9.2.3 操作模式
9.2.3.1 控制文件读写操作的模式
  1. r 只读模式(默认操作模式)

    一次性将文件所有内容读入内存中

    • 当文件不存在时,会报错
    • 文件不可写
    • 当文件存在时,文件指针指向开始位置
    • 当文件过大时,内存会被过度占用,甚至崩溃
  2. w 只写模式

    • 当文件不存在时,会创建一个新的空文件
    • 当文件存在时,会将之前的内容清空后再进行写操作,文件指针指向开始位置
    • 对文件不可读
    • 在以w模式打开文件没有关闭的情况下,连续写不会清空本次打开文件写入的结果。即执行清空的是w模式,而非write()函数
  3. a 只追加写模式

    • 当文件不存在时,会创建一个新的空文件
    • 当文件存在时,文件指针指向末尾
    • 对文件不可读
  4. + 可读可写模式

    1. r+
      • 覆盖写
    2. w+
    3. a+
  5. x 只写模式

    • 不可读:不存在则创建、存在则报错
    • 在创建的时候可以对文件写入
9.2.3.2 控制文件读写内容的模式

t和b不能单独使用,必须与a/w/r连用

  1. t 文本(默认模式)

    • 读写都以str(unicode)为单位

    • 只针对文本文件

    • 必须指定字符编码( encoding=‘xxx‘)

  2. b 二进制

    • 针对所有文件

    • 读写都以bytes为单位

      • bytes的类型为utf-8格式
    • 不能指定字符编码

    • bytes类型转换 byte(‘str‘, encoding)

9.2.4 操作文件的其他方法
  • readline() 每次只读一行
  • readlines() 将所有行分别读出并存入列表,但容易导致内存溢出
  • flush() 强制将数据从内存刷新至硬盘,立即执行读写操作
  • readable() 文件是否可读
  • writable() 文件是否可写
  • f.closed 文件是否关闭
  • f.encoding
  • f.name
9.2.5 控制文件指针的移动
  • f.seek(n, whence)
    • n 移动的字节个数,指针移动的单位都是以字节为单位
    • whence
      • 0(默认) *参照点是文件开头位置 *
        • t下模式可以正常使用0模式
      • 1 参照点是当前位置
      • 2 参照点是文件末尾位置,应该倒着移动
        • t模式下可以使用1和2模式。但第一个参数n只能为0,即不能移动指针
  • f.tell() 返回文件指针当前位置
9.2.6 文件修改的两种方式
9.2.6.1 方式一
  • 将文件内容存入变量,并进行修改,最后再写入原文件,但是容易造成内存溢出

    with open(r‘third.txt‘, mode=‘r+t‘, encoding=‘utf-8‘) as f:
        for line in f:
            line = line.replace(‘男‘, ‘Male‘)
            line = line.replace(‘女‘, ‘Female‘)
            print(line.strip())
    
9.2.6.2 方式二
  • 打开一个新文件,将原文件每一行的新的修改都写入新文件中但硬盘的占用率多了一份

    with open(r‘third.txt‘, mode=‘r+t‘, encoding=‘utf-8‘) as f,        open(r‘third.txt.swap‘, mode=‘wt‘, encoding=‘utf-8‘) as g:
    
        for line in f:
            g.write(line.replace(‘1‘, ‘0‘))
    
    
  • 移除原文件,并将新文件改为原文件的文件名

    import os
    
    os.remove(r‘third.txt‘)
    os.rename(r‘third.txt.sap‘, r‘third.txt‘)
    

10.函数的基本使用

  1. 什么是函数

    • 函数相当于具备某一种功能的工具
    • 函数的使用必须先定义,后调用
  2. 为什么要有函数

    • 代码冗余
    • 程序结构不清晰,可读性差
    • 可维护性、拓展性差
  3. 如何使用函数

    • 先定义
    • 后调用
    • 返回值
  • 定义函数的语法

    def 函数名(参数1, 参数2...):
    	"""文档描述"""
    	函数体
    	return 值
    
    1. def:定义函数的关键字
    2. 函数名:函数名指向函数的内存地址
    3. 括号:括号内定义参数
    4. 冒号:冒号下一行缩进,编辑函数体
    5. 文档描述:描述函数功能,参数介绍等,增强可读性
    6. 函数体:由语句和表达式组成
    7. return:函数的返回值
  • 定义的过程

    • 申请内存空间保存函数体代码
    • 将上述内存地址绑定函数名
    • 定义函数不会执行函数体代码,但会检测函数体语法

10.1 定义

10.1.1 第一种方式
# 无参函数
def func():
    print(‘Hi‘)
10.1.2 第二种方式
# 有参函数
def func(x, y):
    print(x, y)

func(1, 2)  # x = 1, y = 2
10.1.3 第三种方式
  • 常用于构思函数时使用,可先写入文档描述
# 空函数,函数体为pass
def func():
    pass

func()

10.2 调用

  • 调用函数的过程
    • 通过函数名找到函数的内存地址
    • 加"()"会触发函数体代码的执行,即调用
10.2.1 第一种方式
# 语句形式(只调用函数)
def func():
    print(‘Hi‘)

func()
10.2.2 第二种方式
# 表达式形式
def func(x, y):
    z = x + y
	return z

sum = func(1, 2)  # x = 1, y = 2
10.2.3 第三种方式
# 参数形式
def func(x, y):
    z = x + y
	return z

print(func(1, 2))

10.3 返回值

  • 即函数运算的结果,可有可无
  • return是函数结束的标志,即函数体代码一旦运行到return会立刻终止函数的运行,会将return的值(可不写)作为本次函数运行的结果
10.3.1 第一种形式
# 返回None,即函数体内没有return或返回空值
def func():
    return

print(func())
10.3.2 第二种形式
# 返回一个值
def func(x, y):
    z = x + y
	return z

print(func(1, 2))
10.3.3 第三种形式
# 返回多个值:用‘,‘分隔开,解释器会将他们存入同一个元组中
def func(x, y):
    z = x + y
	return x, y, z

total  = func(1, 2)
print(total, type(total))  # (1, 2, 3)

10.4 类型提示 Type hinting

  • Python是解释型的强类型动态语言

  • Python对于函数的参数没有硬性要求,但在3.5之后,引入了类型提示功能

    • 不会报错,但会显示提醒
    • 提示信息理论上可以随便写,也可以写表达式语句等......
    • 可以使用函数的 _annotations_ 属性进行查看
    def func(name: str, age: int) -> int:  # 参数后+":"+类型 表示类型提示功能
    # name为字符串类型, age为整型, 函数返回值为整型
    	print(name)
    	print(age)
        return 1
    

11. 函数的参数

11.1 形参与实参

  • 形参:在定义函数阶段定义的参数称为形式参数,简称形参(相当于变量名)
    • 不能为一个形参重复传值
  • 实参:在调用函数阶段传入的参数称为实际参数,简称实参(相当于变量名)
  • 在调用阶段,实参的值与形参的变量名绑定,绑定关系只适用于函数体。本次函数调用执行结束后,解除绑定

11.2 具体使用

11.2.1 位置参数
  • 位置参数:按从左到右顺序依次定义的参数称为位置参数
  • 位置形参:在函数定义阶段,按照从左到右的顺序直接定义的参数(“变量名”)
    • 必须被传入值,传入个数必须与定义的个数相同
  • 位置实参:在函数调用阶段,按照从左到右的顺序依次传入的值
    • 按照顺序与形参一一相应
11.2.2 关键字参数
  • 关键字实参:在函数调用阶段,按照key=value的形式传入的值
    • 指定给某个变量传值,可以完全不参照顺序
  • 对于实参而言,关键字参数可与位置参数混用,位置实参必须放在关键字实参前
11.2.3 默认参数
  • 默认形参:在定义函数阶段,已经被赋值的形参
    • 在调用阶段可以不用为其赋值
    • 虽然默认值可以被指定为任意的数据类型,但不推荐使用可变类型
11.2.4 可变长度的参数(*与**用法)
  • 在调用函数时,传入的值(实参)的个数不固定
可变长度的位置参数
  • *形参名:接受溢出的位置实参,溢出的位置实参会被*保存成元组格式,将元组赋值给紧跟*的形参名(通常为*args)

  • *实参名:将*后紧跟的值拆分(打散)成位置实参

    def func(x, y, *z)
    	print(x, y, z)
        
    func(1, 2, 3, 4, 5)  # 1 2 (3, 4, 5)
    
    def func_1(x, y, z):
      	print(x, y, z)
    
      func_1(*[1, 2], 3)  # 1 2 (3,)
    
可变长度的关键字参数
  • **形参名:接受溢出的关键字实参,溢出的关键字实参会被**保存成字典格式,将字赋值给紧跟*的形参名(通常为**kwargs)

  • **实参名:将**后紧跟的值(只能是字典)拆分成关键字实参

  • 混用*与**:"*args" 必须在 "**kwargs" 之前

    def func(x, y, **z)
    	print(x, y, z)
        
        
    func(1, 2, fi=10, se=100, thd=1000)  # 1 2 {‘fi‘: 10, ‘se‘: 100, ‘th‘: 1000}
        
    def func1(x, y, z):
        print(x, y, z)
    
    func1(1, **{‘y‘: 2, ‘z‘: 3})  # 1 2 3
    
11.2.5 命名关键字参数
  • 在定义函数时,*后定义的参数(不是"*"后紧跟的参数)称为命名关键字参数

    • 命名关键字实参必须按照key=value形式传值
    def func(x, y, *, z):  # z 为命名关键字参数
    	print(x, y, z)
    
    func(1, 2, z=3)  # 1 2 3
    
    def func1(a, b, *args, c=1000, d):  # 不局限于只能用"*",也可用"*args"(只要有*都算)
        print(a, b, c, d)
    
    func1(1, 2, d=2000)  # 1 2 1000 2000
    
11.2.6 组合使用
  • 形参定义顺序:

    1. 位置形参
    2. 默认形参
    3. 可变长度的位置参数
    4. 命名关键字形参
    5. 可变长度的关键字参数
    def func(x, y=111, *args, z, **kwargs):
        ...
    
  • 实参调用顺序:

    1. 位置参数
    2. 可变长度的位置参数
    3. 关键字参数
    4. 可变长度的关键字参数

12. 名称空间与作用域

  • 名称空间:存放名字的空间,即令栈区分为不同的块,每一块为不同的名称空间,拥有了名称空间,就可以在栈区存放相同的名字

    ![](引用图像/批注 2020-09-11 170446.png)

  • 作用域:不同名称空间作用的范围不同,即作用域不同

  • 名称空间的嵌套关系以函数定义定义为准,与函数调用阶段无关

    x = 10
    
    def func1():
        print(x)
    
    def func2():
        x = 100
        func1()
    
    func2()  # 10
    

12.1 名称空间(namespaces)与存活周期

12.1.1 内置名称空间(只有一个)
  • 存放的名字:Python解释器内置的名字
  • 存活周期:Python解释器启动产生,Python解释器关闭销毁
12.1.2 全局名称空间(只有一个)
  • 存放的名字:只要不是函数内定义、亦不是内置的名字,剩下的都属于全局名称空间
  • 存活周期:Python文件执行产生,Python文件运行完毕后销毁
12.1.3 局部名称空间(若干个)
  • 存放的名字:在调用函数时,运行函数体代码过程中产生的名字
  • 存活周期:调用函数时产生,函数体执行完毕后销毁
12.1.4 三种名称空间的加载顺序
  1. 内置名称空间
  2. 全局名称空间
  3. 局部名称空间
12.1.5 三种名称空间的销毁顺序
  1. 局部名称空间
  2. 全局名称空间
  3. 内置名称空间

12.2 作用域

12.2.1 全局作用域与局部作用域
  • 全局作用域:全局名称空间、内置名称空间的名字
    • 全局存活
    • 全局有效(被所有函数共享)
  • 局部作用域:局部名称空间的名字
    • 临时存活
    • 局部有效(仅在函数内有效)
12.2.2 作用域与名字查找的优先级
  • 名字查找优先级:从当前所在的位置一层一层向上查找
    • 当前在局部:局部名称空间 -> 全局名称空间 -> 内置名称空间
    • 当前在全局:全局名称空间 -> 内置名称空间
12.2.3 函数嵌套使用时的作用域与名字的查找优先级
  • 函数嵌套时的作用域:LEGB

    • locals 是函数内的名字空间,包括局部变量和形参
    • enclosing 外部嵌套函数的名字空间(闭包中常见)
    • globals 全局变量,函数定义所在模块的名字空间
    • built-ins 内置模块的名字空间
  • 查找优先级:根据名称空间的嵌套关系,查找规则同上

12.3 global与nonlocal

  • global:用于在局部名称空间声明该变量为全局作用域变量

  • nonlocal:用于修改函数外层函数包含的名字对应的值(不可变类型),直到找到最外层的函数中的该名字对应的变量

    x = 10
    def f1():
        x = 11
        def f2():
            global x
            x = 22
            print("f1: ", x)
        f2()
        print("f2: ", x)
    
    f1()  # f1: 22  f2: 11
    
    #------------------------------------
    x = 10
    def f1():
        x = 11
        
        
        def f2():
            nonlocal x
            x = 22
            print("f1: ", x)
            
            
        f2()
        print("f2: ", x)
    
        
    f1()  # f1: 22  f2: 22
    print(x)  # 10
    

13. 函数对象与闭包函数

13.1 函数对象

  • 将函数当作变量使用
13.1.1 函数可以被引用
def func():
	...
	
f = func
print(f, func) # <function func at 0x016C9FA0> <function func at 0x016C9FA0>
13.1.2 函数可以作为容器类型的元素
list1 = []
dict1 = {}
def func():
   	...

list1.append(func)
print(list1[0])  # <function func at 0x03239FA0>
dict1[‘k‘] = func
print(dict1)  # {‘k‘: <function func at 0x03239FA0>}
13.1.3 函数可以作为参数传入另外一个函数
def func(x):
    print(x)

def func1():
   	...

func(func1)  # <function func1 at 0x03239FA0>

13.1.4 函数的返回值可以是一个函数
def func(x):
	return x

def func1():
	...
	
print(func(func1))  # <function func1 at 0x010D9FA0>

13.2 闭包函数

  • 闭包函数=名称空间与作用域+函数嵌套+函数对象l

  • 什么是闭包函数

    • 闭函数:该函数是定义在一个函数内的函数(内嵌函数)
    • 包函数:该函数包含对其外层函数(非全局作用域)作用域的引用
  • 为何要有闭包函数

  • 如何定义闭包函数

13.2.1 闭包的用途
  • 将闭包函数作为外函数的返回值可以在全局作用域调用该闭包函数

14. 装饰器

14.1 装饰器介绍

  • 什么是装饰器
    • 装饰:指为其他事物添加额外功能点缀
    • 器:指工具,可以定义成函数
    • 装饰器:定义一个函数(也可以是类),该函数用来装饰其他函数,即为其它函数增加额外的功能
  • 为什么要用装饰器
    • 开放封闭原则
      • 开放:对拓展功能是开放的
      • 封闭:对修改源代码是封闭的
    • 装饰器就是在不修改被装饰对象的源代码以及调用方式的情况下为被装饰对象增加新功能
  • 如何使用装饰器
    • 在不修改被装饰对象的源代码以及调用方式的情况下为被装饰对象增加新功能

14.2 装饰器的实现

  • # 方案一:仍容易产生重复代码
    import time
    
    
    def index(*args, **kwargs):  # index为被修饰函数
        """The number of elements in total."""
        time.sleep(2)
        number_of_argument = 0
    
        for i in args:
            print(i)
            number_of_argument += 1
        for i in kwargs.keys():
            print(kwargs[i])
            number_of_argument += 1
    
        return number_of_argument
    
    
    def out(func):
    
        def wrapper(*args, **kwargs):  # wrapper为闭包函数
            start = time.time()
            res = func(*args, **kwargs)
            stop = time.time()
            print(stop - start)
            wrapper.__name__ = func.__name__  # 将index对象的函数名由wrapper改为index
            wrapper.__doc__ = func.__doc__  # 将index对象的说明文档由wrapper的改为index的
            ...  # 各种函数的属性的赋值
            return res  # 保证与被装饰函数返回值相同
        return wrapper
    
    
    index = out(index)  # 将函数结果赋值给与被修饰函数名字相同的对象
    result = index(1, 2, 456878.222, k=3, g=666)
    print(result)
    print(index.__name__)
    print(index.__doc__)
    
    
    # 方案二:重复代码减少
    import time
    from functools import wraps
    
    def out(func):
    
        @wraps(func)  # 将func的属性都赋值给wrapper
        def wrapper(*args, **kwargs):
            start = time.time()
            res = func(*args, **kwargs)
            stop = time.time()
            print(stop - start)
            
            return res
        return wrapper
    
    
    @out  # 在被装饰对象的单独一行写"@装饰器名字"可以实现"将函数结果赋值给与被修饰函数名字相同的对象",即实现index = out(index)
    def index(*args, **kwargs):
        """The number of elements in total."""
        time.sleep(2)
        number_of_argument = 0
    
        for i in args:
            print(i)
            number_of_argument += 1
        for i in kwargs.keys():
            print(kwargs[i])
            number_of_argument += 1
    
      return number_of_argument
    
    
    result = index(1, 2, 456878.222, k=3, g=666)
    print(result)
    print(index.__name__)
    print(index.__doc__)
    
  • 如果想实现叠加多个装饰器进行修饰

    @out1 #  out1(wrapper2) = wrapper1 ==> index = wrapper1
    @out2 #  out2(wrapper3) = wrapper2 ==> index = wrapper2
    @out3 #  out1(index) = wrapper3 ==> index = wrapper3
    # 从下向上加载
    # 从上而下执行
    def index():
    	pass
    
14.2.1 无参数装饰器
  • 模板
  def out(func):
  	
  	def wrapper(*args, **kwwrgs):
  		# 1.调用原函数
  		# 2.添加装饰的新功能
  		res = func(*args, **kwargs)
  		return res
  		
  	return wrapper
14.2.2 有参数装饰器
  • 由于语法糖@的限制,若外层函数out的参数至少有一个与被修饰对象的参数不匹配,则使用有参数装饰器

  • 模板

    def out(x):
        def deco(func):
            def wrapper(*args, **kwargs):
                # 1.调用原函数
                # 2.添加装饰的新功能
                res = func(*args, **kwargs)
                return res
       		return wrapper
        return deco
    
    
    @out(x)  # out(x) ---> return deco ---> func = deco(func) 
    def func(*args, **kwargs):
        pass
    

15. 可迭代对象

15.1 迭代器

  • 什么是迭代器
    • 迭代器指的是迭代取值的工具,迭代值是一个重复的过程,每次重复都是基于上一次的结果而继续的(单纯的重复不是迭代)
  • 为何要有迭代器
    • 迭代器是用来迭代取值的工具,而涉及把多个值循环取出来的类型有:列表、字符串、元组、字典、集合、打开的文件,其中列表、字符串、元组需要通过索引取值
    • 为解决索引迭代取值的局限性,Python提供一种不依赖索引的取值方式,即迭代器
  • 如何使用迭代器
15.1.1 可迭代对象(可以转换成迭代器的对象)
  • 只要内置有__iter__方法/iter()的都称为可迭代对象

    # 调用可迭代对象下的__iter__方法会将其转换成迭代器对象
    s = ‘Hi‘
    res = s.__iter__()  # 存在
    # <str_iterator object at 0x00B0F538>
    
    l = [1, 2]
    l.__iter__()  # 存在
    
    t = (1, 2)
    t.__iter__()  # 存在
    
    d = {1: 2, 3:4}
    res = d.__iter__()  # 存在
    print(res.__next__())  # 1
    print(res.__next__())  # 3
    print(res.__next__())  # 抛出异常,StopInteration
    # 遍历,在一个迭代器取值取干净的情况下,再对其取值无法取到
    while True:
    	try:  
    		print(res.__next__())
    	except StopIteration:  # 捕捉异常
    		break
    
    
    set = {1, 2, 3}
    set.__iter__()  # 存在
    
    with open(r‘a.txt‘, mode=‘w‘) as f:
    	f.__iter__()  # 存在
    
15.1.2 迭代器对象
  • 内置有__next__方法,并内置有__iter__方法的对象
    • 迭代器对象.__next__()/next(迭代器对象):得到迭代器的下一个值
    • 迭代器对象.__iter__()/iter(迭代器对象):得到迭代器本身
15.1.3 for循环原理
  • for循环可称为迭代器循环
    1. (可迭代对象/迭代器对象).__iter__()得到一个迭代器对象
    2. 迭代器对象.__next__()拿到一个返回值,将返回值赋值给"in"前的变量
    3. 循环往复步骤2,直到抛出StopInteration异常并捕捉后,结束循环
15.1.4 迭代器的优缺点
优点
  • 节省内存
  • 可以遍历可迭代对象
缺点
  • 必须按顺序取值
  • 取完值后不可再取(一次性)

15.2 生成器(自定义迭代器)

15.2.1 生成器与yield
  • 在函数内一旦存在yield,调用函数不会执行函数体代码,函数体返回一个生成器对象

  • 生成器的__next__的方法会触发函数体代码的执行,遇到yield停下,并返回yield的值

  • yield类似于return,但可保存函数的状态并挂起函数,用于返回多次值

    def func():
        print("The first time")
        yield 1
        print("The second time")
        yield 2
        print("The third time")
        yield 3
        print("The forth time")
        
        
    g = func()
    res = g.__next__()  # The first time
    print(res)  # 1
    res = g.__next__()  # The second time
    print(res)  # 2
    res = g.__next__()  # The third time
    print(res)  # 3
    res = g.__next__()  # The forth time
    # 抛出异常(StopInteration)
    
15.2.2 yield表达式及其应用
  • yield表达式

    • g.send()(g.send(None)等同于next(g)) ---- 给函数(生成器)中当前挂起位置的yield赋值,第一次传入的值必须为"None"

      • 若有对象引用至yield,yield 将send得到的值(除过起使的next(g))先传给该对象,再将yield之后的值作为函数返回值
    • g.close() ---- 关闭后无法传值

      def func():
          print("---Begin---")
          while True:
              x = yield 100
              print("x = {}".format(x))
              yield 200 
              
      
      g = func()
      g.send(None)
      res = g.send(5)  # x = 5,此时函数挂起在第二个yield
      print(res)  # 200
      res = g.send(10)
      print(res)  # 100,此时函数挂起在第一个yield
      ...
      
  • 应用

    • 可产生若干个元素的迭代器

      # 自定义产生若干个元素的迭代器
      def func(start, stop, step=1):
          print("Start")
          while start <= stop:
              start += step
              yield start-1 
        
      
      start_number = int(input())
      stop_number = int(input())
        
      g = func(start_number, stop_number)
      while True:
          try:
              print(next(g))
          except StopIteration:
              print("End")
              break
      
15.2.3 三元表达式、列表生成式、字典生成式
三元表达式
  • a if b else c ---- 其中b为条件,a、c为语句
列表生成式(有嵌套语句时反而繁琐)
  • [a for b in c if d]

    • 判断部分也可不加,也可适当改变,下同
    # 代码原理同下
    
    for b in c:
        if d:
            a
    
字典生成式
  • {a: b for b in c if d}
    • key必须对应value
集合生成式
  • {a for b in c if d}
生成器生成式
  • (a for b in c if d)

    # 例子
    g = (i for i in range(10) if i > 8)  # g内部一个值也没有
    
    print(next(g))  # 9
    print(next(g))  # IterationStop
    

三、进阶

1. 函数高级使用

1.1 函数的递归调用

  • 是函数嵌套调用的一种特殊形式

    • 在调用一个函数的过程中又直接或间接地调用到本身
    • 递归不能无限调用,Python默认限制嵌套1000层
  • 递归的两个阶段

    • 回溯:一层一层调用

    • 递推:满足某种条件,结束递归调用,一层一层返回

      ![](引用图像/屏幕截图 2020-10-09 103923.png)

1.2 二分法

  • 查找数据高效的方式

  • 通过二分法查找数据

    List1 = [1, 20, 21, 19, 25, 56, 87, 15, 12, 16, 20, 27, 23, 50]
    
    
    def dichotomy(num, list1):
        try:
            print(list1)
            middle = list1[len(list1)//2]
            if middle > num:
                list1 = list1[:len(list1)//2]
                dichotomy(num, list1)
    
            elif middle < num:
                list1 = list1[len(list1)//2+1:]
                dichotomy(num, list1)
    
            else:
                print("FIND IT")
    
        except IndexError:
            print("No such number")
    
    
    List1.sort()
    number = int(input())
    dichotomy(number, List1)
    
    

1.3 面向过程编程

  • 编程思想/范式:编程的风格
  • 面向过程的编程思想:过程即流程,指做事的步骤。
    • 基于该思想编写程序,类似于流水线
    • 优点:复杂的问题流程化,进而简单化
    • 缺点:扩展性非常差

1.4 函数式编程

  • 函数式并非用函数编程,而是将计算机的运算视为数学意义上的运算,比起面向过程,函数式编程更重视的是执行结果而非执行过程,Python并非是函数式编程语言,但提供了很多函数式编程好的特性。
1.4.1 匿名函数与lambda
  • 对比使用def关键字创建的有名字的函数,使用lambda关键字创建的规则是没有名字的函数(匿名函数),在定义时就得被调用(由于没有绑定变量名,在运行至下一条语句时,会被回收),执行语句的结果为函数返回值

  • 匿名函数用于临时调用一次的场景:更多的是将其与其他函数配合使用

    lambda 参数1, 参数2,...: expression
    
    • 举例
    # 定义
    lambda x, y: x+y
    
    # 调用1
    total = (lambda x, y: x+y)(1, 2)
    print(total)  # 3
    
    # 调用2
    func =  lambda x, y: x+y
    total = func(1, 2)
    print(total)  # 3
    
  • 应用

    • 可以用于一次性调用的场景

      dic = {‘Tom‘: 123123,
             ‘Step‘: 100000,
             ‘Pop‘: 124560
             }
      
      res = max(dic, key=(lambda x: dic[x]))
      print(res)  # Pop
      
1.4.2 filter
  • filter(函数,可迭代对象)

    • 函数的参数为可迭代对象的每一次的返回值

    • 会判断函数返回的布尔值,若为真,则保留

      persons = [‘Step‘, ‘Egon‘, ‘Ada‘]
      res = filter(lambda name: name.endswith(‘a‘), persons)
      
      print(next(res))  # Ada
      print(next(res))  # StopIteration
      
1.4.3 map
  • map(函数,可迭代对象)

    • 函数的参数为可迭代对象的每一次的返回值
    persons = [‘Step‘, ‘Egon‘, ‘Ada‘]
    res = map(lambda name: name+‘.M‘, persons)
    # res是可迭代对象
    print(next(res))  # Step.M
    
    ress = map(lambda name: name.replace(‘.M‘, ‘_M_‘))
    print(next(ress))  # Step_M_
    
1.4.4 sorted
  • sorted(x, key=函数名, reverse) ---- reverse=False(默认从小到大排)

    • 函数的参数为可迭代对象的每一次的返回值
  • 遍历一个可迭代对象,并对其排序,返回一个列表

1.4.5 max
  • max(x, key=函数名)

    • 函数的参数为可迭代对象的每一次的返回值
  • 迭代一个可迭代对象,并找出最大值

1.4.6 min
  • min(x, key=函数名)
    • 函数的参数为可迭代对象的每一次的返回值
  • 迭代一个可迭代对象,并找出最小值
1.4.7 reduce
  • 需要从外部模块导入

    from functools import reduce
    
  • reduce(函数, 可迭代对象, 初始值) ---- 初始值默认为0

    • 合并数据,前一步合并结果赋值给初始值

    • 不只限于数字类型

      res = reduce(lambda x, y: x+y, [10, 20, 30], 10)
      print(res)  # 70
      
      ress = reduce(lambda x, y: x+y, [‘a‘, ‘b‘, ‘c‘], ‘Hi‘)
      print(ress)  # Hiabc
      

1.5 内置函数

函数 作用
abs () 绝对值
all() 当可迭代对象中所有元素都为True或可迭代对象为空,则返回True
any() 当可迭代对象中有一个元素为True则或返回True
bin() hex() oct() 略
callable() 判断一个对象可否被调用
setattr()
getattr()
hasattr()
delattr()
dir() 查看某个对象有哪些属性
divmod(x, y) 返回x除以y的商和余数
set() 创建集合shib
frozenset() 创建不可变集合
isinstance(a, b) 判断对象a是否为类b的实例
slice() 完全等于切片操作
zip(x, y) 返回x和y元素相互对应的元组组成的列表(多出的元素部分不配对)
__import__() 将字符串形式的模块名转化为可使用的模块名并导入

2. 模块

2.1 模块介绍

  1. 什么是模块
    • 模块就是一些列功能的集合体,分为三类
      1. 内置模块 ---- Python解释器自带模块
      2. 第三方模块
      3. 自定义模块
    • 模块氛围四种形式
      1. 使用Python编写的.py文件
      2. 已被编译为共享库或者dll的C或C++拓展
      3. 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)
      4. 使用C语言编写并链接到Python解释器的内置模块
  2. 为何要用模块
    • 内置与第三方的模块拿来就用,无需定义,可以极大提高开发效率
    • 自定义模块,将程序的各部分功能提取出来,放入一个模块中,为各文件共享使用,减少代码冗余,程序结构更加清晰
      • 自定义模块的命名只能由小写字母与下划线组成
  3. 如何用模块

2.2 模块使用

2.2.1 import语句
  • 首次导入模块时会发生:

    1. 执行模块对应的文件
    2. 产生模块的名称空间,将模块运行空间产生的名字都纳入其名称空间中
    3. 在当前文件中产生一个名字(即模块名),该名字指向步骤2中产生的名称空间
  • 之后的导入都是引用首次导入产生的命名空间,无论查看还是修改操作,与调用位置无关

  • 引用模块

    • 模块名.对象 ---- 不会与当前文件名称空间冲突
    • 若当前文件的对象名与模块中的对象名有重名,优先执行当前文件中的对象
  • 可以一条语句导入多个模块(不建议)

    import 模块1, 模块2...
    
  • 可以在函数内导入模块,作用域为函数的局部作用域

  • import语句的优点:不会与当前名称空间中的对象名字冲突

  • import语句的缺点:加前缀显得麻烦

2.2.2 from ... import语句
  • 导入时会发生:

    • 产生一个模块的名称空间,将模块运行空间产生的名字都纳入其名称空间中
    • 在当前名称空间拿到一个对象的名字,该名字与模块名称空间中的相同名字的对象对应,即指向同一内存地址
  • 导入多个对象:

    • from ... import 对象1, 对象2... ---- 导入模块中多个对象名字
    • from ... import * ---- 导入模块中所有对象名字
  • __all__ ----导入的对象的名字组成的列表

  • from ... import语句的优点:不用加前缀,直接使用,代码精简

  • from ... import语句的缺点:会与当前名称空间中的对象名字冲突

2.2.3 其他导入语法(as)
  • 当导入模块的名字复杂或过长等情况,使用as进行名字的重命名

    import 模块 as new_name
    from 模块 import 对象 as new_name
    
2.2.4 循环导入问题(类似于函数互相调用)
2.2.5 搜索模块的路径与优先级
  • 模块查找优先级

    • 无论是import还是 from...import,再导入模块时都涉及到查找问题

    • 优先级

      优先级 位置
      1 内存
      2 按照sys.path中的文件夹位置逐个查找
    • sys.path中的第一项为当前文件夹,第二项为当前项目文件夹...(列表形式)

      • 可以在列表中添加其他路径(临时)
    • 环境变量是以执行文件为准的,被导入的模块或者后续其他文件引用的sys.path都是参照执行文件的sys.path

    • sys.modules可以查看已经加载到内存的模块(字典形式)

2.2.6 区分py文件的两种方法
  • 文件的__name__属性,可由此编写当文件仅为执行文件时执行的内容
    • 当文件被当作执行文件时,__name__属性为‘__main__‘
    • 当文件被当作模块时,__name__属性为‘模块名‘
2.2.7 编写一个规范的模块
  • 文档注释
  • 导入其他模块
  • 定义
    • 全局变量(但最好定义局部变量)
    • 类
    • 函数
    • 主程序
2.2.8 链接

3. 包的使用

  • 包是包含有 __init__.py文件夹
    • 包是模块的一种形式
  • 包是用来当作模块导入的(即导入的为包名)
    • 在导入包时,运行的为其下的__init__.py文件
  • 为何要有包?
    • 将不同的功能写入不同的文件,便于维护
3.1 绝对导入

以包的文件夹作为起始来导入

  • 关于包的导入语句也分import 和 from... import...
    • 凡是导入时带“点”的,点的左边必须是一个包,可以带有一连串的“点”,如[顶级包.子包.子模块]
    • 包A和包B导入文件时,同名模块也不会冲突,,因为来自于两个命名空间
    • import导入文件时,产生名称空间的名字来源于源文件
    • import导入包,产生名称空间的名字来源于源文件
3.2 相对导入

仅限于包内使用,不能跨出包。包内模块之间的导入,推荐使用

  • . 代表当前文件夹
  • .. 代表上一层文件夹

4. 软件开发的目录规范

  • 在项目下应设立的文件(夹)
    • bin/ 存放启动代码等
      • setup.py
    • conf/ 存放配置文件
      • settings.py
    • core/ 放置核心代码的逻辑
      • core.py
    • api/ 放置接口文件,接口主要用于为核心代码逻辑提供数据操作
      • api.py
    • db/ 放置数据库相关的操作代码
      • db_handle.py
    • lib/ 防止程序常用的共享库(模块)
      • common.py
    • requirements.txt 存放软件依赖的外部Python包列表
    • README.md 项目说明文件
  • 在同一文件夹内倒入时,使用相对导入
  • 在跨文件夹导入时
    • 使用绝对导入,参照sys.path,即参照执行文件,在执行文件中将模块文件地址加入sys.path
      • 可移植性差
      • 添加方式麻烦
    • 使用绝对导入,将顶级目录加入环境变量
    • 使用相对导入
      • 使用 __file__ 获取当前文件的绝对路径
      • 使用os模块中的path.dirname获取当前文件所在文件夹路径,来获得项目顶级目录

5. 常用模块

5.1 time datetime

5.1.1 time
  • 时间分为三种格式

    1. 时间戳:从1970.1.1到现在经过的秒数

      • time.time()
      • 用于实现间隔时间
    2. 格式化显示的时间

      • 可自行设置格式 time.strftime(‘格式‘)

      • 字符 含义
        %Y 年
        %m 月
        %d 日
        %H 时
        %M 分
        %S 秒
        %p AM/PM
        %X %H:%M:%S
        %a 星期
        %b 月份的英文简写
      • time.asctime() ---- ,不需进行特定格式化,显示格式为‘%a %b %d %H:%M:%S %Y‘

    3. 结构化的时可用于展示时间间

      • time.localtime() ---- 当地时间
      • 显示结构化的时间,即当前时间为第x年,第x天......
        • 可通过time.localtime.tm_xxxx对某个元素进行单独提取
      • 可获取当前时间的某一特定部分
  • 时间格式的互相转化

    ![](引用图像\屏幕截图 2020-11-01 233950.png)

    • import time
      
      s_time = time.localtime()  # 结构化的时间
      t_time = time.time()  # 时间戳
      f_time = time.strftime("%Y-%m-%d %H:%M:%S")  # 格式化的时间
      
      
      print(time.mktime(s_time))  # 结构化时间转化为时间戳s_time
      print(time.strftime("%Y-%m-%d %H:%M:%S",s_time))  # 结构化的时间转化为格式化的时间
      print(time.strptime(f_time, "%Y-%m-%d %H:%M:%S"))  # 格式化的时间转化为结构化的时间
      print(time.localtime(t_time))  # 时间戳转化为结构化的时间
      print(time.gmtime(t_time))  #时间戳转化为结构化的时间(世界标准时间,0经度处)
      
5.1.2 datetime
  • datetime.datetime.now()
    • 显示当前时间的格式化形式(是一个特定的类)
    • 可用于时间的加减,显示结果的时间格式化形式
      • datetime.timedelta(关键字参数)
  • datetime.datetime.utctnow()
    • 显示当前世界标准时间
  • datetime.datetime.fromtimestamp()
    • 将时间戳转化为格式化形式

5.2 os

用于控制操作系统

  • os.getcwd() ---- 获取当前目录

  • os.getsize(‘dirname‘) ---- 获取当前文件夹大小(Bytes)

  • os.chdir(‘dirname‘) ---- 改变当前脚本目录,相当于cd

  • os.curdir ---- 返回当前目录字符串名(.)

  • os.pardir ---- 返回父目录字符串名(..)

  • os.makedirs(‘dirname/dirname2/...‘) ---- 可生成多层递归目录

  • os.removedirs(‘dirname‘) ---- 若目录为空,则删除,递归至上级目录,若为空,则删除......

  • os.mkdir(‘dirname‘) ---- 相当于shell中的mkdir

  • os.rmdir(‘dirname‘) ---- 删除单级空目录,若目录不为空则无法删除并报错;相当于rmdir

  • os.listdir(‘dirname‘) ---- 列出指定目录下所有文件和子目录,包括隐藏文件,相当于ls -a

  • os.remove() ---- 删除一个文件

  • os.rename(‘oldname‘, newname) ---- 文件重命名

  • os.stat(‘path/filename‘) ---- 获取文件/目录信息

  • os.sep ---- 输出操作系统的路径分隔符(\\ /)

  • os.linesep ---- 输出当前平台使用的行终止符

    • Windows "\t\n"
    • Linux "\r\n"
  • os.pathsep ---- 输出用于分割文件路径的字符串

    • Windows ";"
    • Linux ":"
  • os.name ---- 输出字符串指示当前使用平台

    • Windows "nt"
    • Linux "posix"
  • os.system("bash command") ---- 运行shell命令,直接显示

  • os.environ ---- 获取系统环境变量

    • 是一个字典,可以对其进行更改
    • key和value都是字符串
  • os.path

    • os.path.abspath(path) ---- 返回path规范化的绝对路径(适应平台)
    • os.oath.split(path) ---- 将path分割成目录和文件二元组返回
    • os.path.dirname(path) ---- 返回path中的目录
    • os.path.basename(path) ---- 返回path中的文件名
    • os.path.exists(path) ---- 若path存在,返回True
    • os.path.isabs(path) ---- 若path为绝对路径,返回True
    • os.path.isfile(path) ---- 若path为存在的文件,返回True
    • os.path.isdir(path) ---- 若path为存在的目录,返回True
    • os.path.getatime(path) ---- 返回path指向文件最后的读取时间
    • os.path.getmtime(path) ---- 返回path指向文件最后的内容修改时间
    • os.path.getctime(path) ---- 返回path指向文件最后的状态修改时间
    • os.path.join(path1, path2...) ---- 将多个路径拼接(以根目录为起始)
    • os.path.getsize(path) ---- 返回path的大小

5.3 sys

  • sys.path ---- 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
  • sys.argv ---- 命令行参数List,其中第一个元素为程序本身路径(同C语言)

5.4 random

  • random.random() ---- 产生0~1的一个浮点数
  • random.randint(a, b) ---- 产生大于等于a,小于等于b的整数
  • random.randrange(a, b) ---- 产生大于等于a,小于b的整数
  • random.choice(a, b, c...) ---- 产生a,b,c...等元素中的任意一个(不限类型)
  • random.sample(list, num) ---- 产生List中任意num个元素的组合
  • random.uniform(a, b) ---- 产生大于a,小于b的浮点数
  • random.shuffle(list) ---- 对list元素进行重排

5.5 shutil

高级的文件(夹)、压缩包处理模块

  • shutil.copyfileobj(fsrc, fdst[, lenght])

    • 将文件内容拷贝到另一个文件中

      import shutil
      
      shutil.copyfileobj(open(‘src‘, ‘r‘), open(‘dst‘, ‘w‘))
      
  • shutil.copyfile(src, dst)

    • 拷贝文件
    • 目标文件无需存在
  • shutil.copymode(src, dst)

    • 仅拷贝权限;内容、组、用户均不变
    • 目标文件必须存在
  • shutil.copystat(src, dst)

    • 进拷贝文件的状态信息
      • mode
      • bits
      • atime
      • mtime
      • flags
    • 目标文件必须存在
  • shutil.copy(src, dst)

    • 拷贝文件和权限
    • 目标文件无需存在
  • shutil.copy2(src, dst)

    • 拷贝文件和状态信息
    • 目标文件无需存在
  • shutil.ignore_pattern(*patterns)

  • shutil.copytree(src, dst, symlinks=False, ignore=None)

    • 递归拷贝文件夹

    • 目标目录不能存在

    • 目标目录的父目录应有写权限

      import shutil
      
      shutil.copytree("dir1", "dir2", ignore=shutil.ignore_pattern(‘*.txt‘))  # 忽略.txt文件
      
  • shutil.rmtree(path[,ignore_errors[, onerror]])

    • 递归删除文件夹
  • shutil.move(src, dst)

    • 递归移动文件,类似于mv命令
  • shutil.make_archive(nase_name, format, ...)

    • 创建压缩包并返回文件路径
    • 参数
      • base_name:压缩包的文件名
        • 若为文件名,则保存至当前目录
        • 若为路径,则保存至指定路径
      • format:压缩包种类
        • zip
        • tar
        • bztar
        • gztar
      • root_dir:要压缩的文件夹路径(默认当前目录)
      • owner:用户,默认当前
      • group:组,默认当前
      • logger:用于记录日志,通常是logging._Logger对象
  • shutil对于解包分两个模块进行

    • zipfile

      #解压缩
      import zipfile
      
      z = zipfile.ZipFile(‘a.zip‘, ‘r‘)
      z.extractall(path=‘.‘)  # 解压至指定目录
      z.close()
      
    • tarfile

      # 解压缩
      import tarfile
      
      t = tarfile.open("a.tar.gz", ‘r‘)
      t.extractall(‘/tmp‘)  # 解压至指定目录
      t.close()
      

5.6 json&pickle

  • 序列化与反序列化

    • 是什么:

      • 序列化是将内存的数据类型转化成一种特定的格式的内容
        • json格式
        • pickle格式
        • 可用于存储或传输给其他平台使用
      • 反序列化是将特定格式的内容转化为内存中的数据类型
    • 为何需要:

      • 序列化得到的特定的格式的内容可用于
        • 存储 -> 用于存档
          • 可以是一种专用的格式 -> pickle(json也可以,但有局限)
        • 传输给其他平台 -> 跨平台数据交互
          • 通用的、被所有语言识别的格式 -> json格式
            • json中只有所有语言中通用的数据类型,而不包括特定语言的特定数据格式
    • 如何序列化与反序列化

      • json模块

        • json.dumps() json.dump() json.loads() json.load() (适用于pickle模块,且用法相同,但写入格式为二进制而不是字符串)

          import json
          
          # 序列化写入文件的复杂形式
          res = json.dumps([1, 2, ‘a‘, True])  # 序列化
          print(res, type(res))  # [1, 2, "a", true] <class ‘str‘>
          with open(‘t.json‘, mode=‘wt‘, encoding=‘utf-8‘) as f:
              f.write(res)
          
          # 序列化写入文件的简单方式
          with open(‘t.json‘, mode=‘wt‘, encoding=‘utf-8‘) as f:
              json.dump([1, 2, ‘a‘, True], f)
          
              
          # 反序列化读出文件的复杂方式
          with open(‘t.json‘, mode=‘rt‘, encoding=‘utf-8‘) as f:
          	res = json.loads(f.read())
          print(res, type(res))  # [1, 2, ‘a‘, Trrue] <class ‘list‘>
          # 反序列化读出文件的简单方式
          with open(‘t.json‘, mode=‘rt‘, encoding=‘utf-8‘) as f:
              res = json.load(f)
          
        • 猴子补丁

          • 属性在运行时的动态替换,叫做猴子补丁(Monkey Patch);拥有在模块运行时替换的功能

          • 应在入口处打补丁,即在执行文件处打补丁

            def monkey_patch_json():
                json.__name__ = ‘ujson‘  # ujson的性能比json更好,且方法相同,可用补丁替换
                jsonn.dumps = ujson.dumps
                json.loads = ujson.loads
            
        • pickle模块

          • 用法与json模块相同,但写入格式为二进制而不是字符串
          • 在进行文件操作时应使用二进制格式

5.7 shelve

  • shelve模块比pickle模块简单,只有一个open函数,返回类似字典的对象,可读可写;

  • key必须为字符串,而值可以是python所支持的数据类型

    import shelve
    
    f=shelve.open(r‘sheve.txt‘)
    # f[‘student_1‘] = {‘name‘: ‘Stephane‘, ‘age‘:19}
    
    print(f[‘student_1‘])  # {‘name‘: ‘Stephane‘, ‘age‘:19}
    f.close()
    

5.8 xml(*)

  • xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单

  • xml格式如下,通过"<>"节点来区别数据结构

    <?xml version="1.0"?>
    <data>
        <country name="Liechtenstein">
            <rank updated="yes">2</rank>
            <year>2008</year>
            <gdppc>141100</gdppc>
            <neighbor name="Austria" direction="E"/>
            <neighbor name="Switzerland" direction="W"/>
        </country>
        <country name="Singapore">
            <rank updated="yes">5</rank>
            <year>2011</year>
            <gdppc>59900</gdppc>
            <neighbor name="Malaysia" direction="N"/>
        </country>
        <country name="Panama">
            <rank updated="yes">69</rank>
            <year>2011</year>
            <gdppc>13600</gdppc>
            <neighbor name="Costa Rica" direction="W"/>
            <neighbor name="Colombia" direction="E"/>
        </country>
    </data>
    

5.9 configparser

  • 配置文件后缀一般为*.ini或*.cfg

    • 文件格式

      # 注释1
      ; 注释2
      
      [section1]
      k1 = v1
      k2:v2
      user=egon
      age=18
      is_admin=true
      salary=31
      
      [section2]
      k1 = v1
      
  • 读取

    import configparser
    
    config = configparser.ConfigParser()
    config.read(‘tt.ini‘)
    
    # 获取sections
    print(config.sections())
    # 获取某个section下的keys
    print(config.options(‘section1‘))  # 所有key组成的列表
    # 获取items
    print(config.items(‘section1‘))
    # 获取特定section下特定key的值
    print(config.get(‘section1‘, ‘user‘))
    print(config.getint(‘section1‘, ‘age‘))
    print(config.getboolean(‘section1‘, ‘is_admin‘))
    
  • 改写

    import configparser
    
    config=configparser.ConfigParser()
    config.read(‘tt.ini‘,encoding=‘utf-8‘)
    
    
    #删除整个标题section2
    config.remove_section(‘section2‘)
    
    #删除标题section1下的某个k1和k2
    config.remove_option(‘section1‘,‘k1‘)
    config.remove_option(‘section1‘,‘k2‘)
    
    #判断是否存在某个标题
    print(config.has_section(‘section1‘))
    
    #判断标题section1下是否有user
    print(config.has_option(‘section1‘,‘‘))
    
    
    #添加一个标题
    config.add_section(‘Stephane‘)
    
    #在标题Stephane下添加name=Stephane,age=19的配置
    config.set(‘Stephane‘,‘name‘,‘Stephane‘)
    config.set(‘Stephane‘,‘age‘,19) #报错,必须是字符串
    
    
    #最后将修改的内容写入文件,完成最终的修改
    config.write(open(‘tt.ini‘,‘w‘))
    

5.10 hashlib

  • 什么是哈希

    • hash是一类算法,该算法接受传入的内容,经过运算得到一串哈希值
    • 哈希值的特点
      1. 只要传入内容一样,则产生哈希值一样
      2. 不能由hash值反推内容
      3. 只要使用的hash算法不变,无论校验的内容有多大,得到的hash值长度是固定的
  • 哈希的用处

    • 特点1、3用于文件完整性校验
    • 特点2用于密码密文传输与验证
  • 如何用哈希

    • import hashlib
      
      passwd = hashlib.md()
      passwd.update("Hello".encode(‘utf-8‘))  # 必须为bytes类型
      passwd.update("World".encode(‘utf-8‘))
      print(passwd.hexdigest())  # "HelloWorld"对应的哈希值
      

5.11 subprocess

  • 用来执行系统命令

    import subprocess
    
    obj = subprocess.Popen(‘dir .‘, 
                     shell=True,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)
    
    print(obj.stdout.read().decode(‘utf-8‘))
    print(obj.stderr.read().decode(‘utf-8‘))
    

5.12 logging

  • 日志模块

    • 日志级别与配置

      import logging
      
      #1. 日志配置
      logging.basicConfig(
          # 1、日志输出位置:1、终端 2、文件
          # filename=‘access.log‘, # 不指定,默认打印到终端
      
          # 2、日志格式
          format=‘%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s‘,
      
          # 3、时间格式
          datefmt=‘%Y-%m-%d %H:%M:%S %p‘,
      
          # 4、日志级别
          # critical => 50
          # error => 40
          # warning => 30
          # info => 20
          # debug => 10
          level=30,
      )
      
      # 2. 输出日志
      logging.debug(‘debug‘)  # 调试日志 级别10
      logging.info(‘info‘)  # 消息日志 20
      logging.warning(‘warning‘)  # 警告日志 30
      logging.error(‘error‘)  # 错误日志 40
      llogging.critical(‘critical‘)  # 严重日志 50
      # 输出:
      """
      WARNING:root:warning
      ERROR:root:error
      CRITICAL:root:critical
      """
      
    • 日志字典配置

      """
      logging配置
      """
      
      import os
      
      # 1、定义三种日志输出格式,日志中可能用到的格式化串如下
      # %(name)s Logger的名字
      # %(levelno)s 数字形式的日志级别
      # %(levelname)s 文本形式的日志级别
      # %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
      # %(filename)s 调用日志输出函数的模块的文件名
      # %(module)s 调用日志输出函数的模块名
      # %(funcName)s 调用日志输出函数的函数名
      # %(lineno)d 调用日志输出函数的语句所在的代码行
      # %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
      # %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
      # %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
      # %(thread)d 线程ID。可能没有
      # %(threadName)s 线程名。可能没有
      # %(process)d 进程ID。可能没有
      # %(message)s用户输出的消息
      
      # 2、强调:其中的%(name)s为getlogger时指定的名字
      standard_format = ‘[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]‘                   ‘[%(levelname)s][%(message)s]‘
      
      simple_format = ‘[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s‘
      
      test_format = ‘%(asctime)s] %(message)s‘
      
      # 3、日志配置字典
      LOGGING_DIC = {
          ‘version‘: 1,
          ‘disable_existing_loggers‘: False,
          ‘formatters‘: {
              ‘standard‘: {
                  ‘format‘: standard_format
              },
              ‘simple‘: {
                  ‘format‘: simple_format
              },
              ‘test‘: {
                  ‘format‘: test_format
              },
          },
          ‘filters‘: {},
          
          # handlers是日志的接收者,不同的handler会将日志输出到不同位置
          ‘handlers‘: {
              #打印到终端的日志
              ‘console‘: {
                  ‘level‘: ‘DEBUG‘,
                  ‘class‘: ‘logging.StreamHandler‘,  # 打印到屏幕
                  ‘formatter‘: ‘simple‘
              },
              #打印到文件的日志,收集info及以上的日志
              ‘default‘: {
                  ‘level‘: ‘DEBUG‘,
                  ‘class‘: ‘logging.handlers.RotatingFileHandler‘,  # 保存到文件,日志轮转
                  ‘formatter‘: ‘standard‘,
                  # 可以定制日志文件路径
                  # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
                  # LOG_PATH = os.path.join(BASE_DIR,‘a1.log‘)
                  ‘filename‘: ‘a1.log‘,  # 日志文件
                  ‘maxBytes‘: 1024*1024*5,  # 日志大小 5M
                  ‘backupCount‘: 5,
                  ‘encoding‘: ‘utf-8‘,  # 日志文件的编码,不用担心中文log乱码了
              },
              ‘other‘: {
                  ‘level‘: ‘DEBUG‘,
                  ‘class‘: ‘logging.FileHandler‘,  # 保存到文件
                  ‘formatter‘: ‘test‘,
                  ‘filename‘: ‘a2.log‘,
                  ‘encoding‘: ‘utf-8‘,
              },
          },
          
          # loggers是日志的产生者,产生的日志会传递到handler然后控制输出
          ‘loggers‘: {
              #logging.getLogger(__name__)拿到的logger配置
              ‘my_logger‘: {
                  ‘handlers‘: [‘default‘, ‘console‘],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
                  ‘level‘: ‘DEBUG‘, # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
                  ‘propagate‘: False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
              },
              ‘public_logger‘: {
                  ‘handlers‘: [‘other‘,],
                  ‘level‘: ‘DEBUG‘,
                  ‘propagate‘: False,
              },
              
              # 当找不到日志名时,默认调用‘‘(空key)
              ‘‘: {
                  ‘handlers‘: [‘other‘,],
                  ‘level‘: ‘DEBUG‘,
                  ‘propagate‘: False,
              },
          },
      }
      
    • 导入配置文件

      import os
      from logging import config, getLogger
      current_path = os.path.dirname(os.path.dirname(__file__))
      os.path.join(current_path)
      from logging_config import LOGGING_DIC  # 从定义日志配置的文件中导入配置字典
      
      config.dictConfig(LOGGING_DIC)
      mode = logging.getLogger(‘my_logger‘)
      
      
      mode.info(‘Info‘)  # [2020-11-08 02:35:49,389][MainThread:19000][task_id:my_logger][logging模块.py:22][INFO][Info]
      
    • 日志轮转

      • 日志记录程序运行过程中的关键信息,当日志存储过大时们需要进行定期迁移(轮转)

      • 在logging下的handlers中有RotatingFileHandler类,用来日志轮转

        # 日志配置文件中handlers的一部分
        ‘default‘: {
                    ‘level‘: ‘DEBUG‘,
                    ‘class‘: ‘logging.handlers.RotatingFileHandler‘,  # 保存到文件,日志轮转
                    ‘formatter‘: ‘standard‘,
                    ‘filename‘: ‘a1.log‘,  # 日志文件
                    ‘maxBytes‘: 1024*1024*5,  # 日志大小 5M
                    ‘backupCount‘: 5,  # 最多产生份数
            		# 新内容总是会保存到a1.log中,如果超出,则自动将就内容拷贝至备份中,当达到最大份数,第一个备份文件会被覆盖
                    ‘encoding‘: ‘utf-8‘, 
         			},
        
    • 日志名的命名

      • 日志名是区分日志业务归属的重要的标识
      • 日志名即logger中的key名
        • 当找到相应key名时,调用该key的设置来产生日志
        • 当找不到相应的key名时,调用空key(‘‘)的设置来产生日志

5.13 re

  • 正则

    • 正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。或者说:正则就是用来描述一类事物的规则。
    • 在Python中,它内嵌在Python中,并通过 re 模块实现。正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。
  • 正则表达式

    元字符 作用
    * 匹配前一个字符匹配0次或任意多次
    . 匹配除了换行符意外任意一个字符
    ^ 匹配行首(如 ^Hi 会匹配以Hi开头的行)
    $ 匹配行末(如 Hi$ 会匹配以Hi结尾的行)
    [] 匹配中括号中指定的任意一个字符,只匹配一个字符(如[ab]会匹配任意a或b,[a-z][0-9] 会匹配小写字母和数字组成的两位字符)
    [^] 匹配除中括号中内容以外的任意一个字符,只匹配一个字符(如[ab]会匹配除过a或b的任意字符,[a-z][^0-9] 会匹配除过小写字母和数字组成的任意两位字符)
    \ 转义符
    {n} 表示其前面的字符恰好出现n次(如[0-9]{4},表示匹配四位数字)
    {n,} 表示其前面的字符出现不少于n次(如[0-9]{4,},表示匹配四位以上数字)
    {n,m} 表示其前面的字符至少出现n次,至多出现m次(如[0-9]{4,6},表示匹配4-6位数字)
    \w 匹配字母数字及下划线(也可匹配中文)
    \W 匹配非字母数字及下划线
    \s 匹配任意空白字符,等价于[\t\n\r\f]
    \S 匹配任意非空字符
    \d 匹配任意数字
    \D 匹配任意非数字
    \A 匹配字符串开始
    \z 匹配字符串结束
    \Z 匹配字符串结束,如果存在换行,只匹配到换行前的结束字符串
    \G 匹配最后匹配完成的位置
    \n 匹配一个换行符
    \t 匹配一个制表符
    + 匹配前一个字符匹配1次或任意多次
    ? 匹配前一个字符匹配0次或1次,非贪婪方式
    () 匹配括号内的表达式,也表示一个组
    a|b 匹配a或b
  • re.findall(元字符(串), 字符串, ...)

    • 返回所有满足匹配条件的结果,放在列表里
    • re.DOTALL ---- ‘.‘匹配所有(包括\n)
  • re.search(元字符(串), 字符串).group()

    • 只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None
  • re.match(元字符(串), 字符串)

    • 同search,不过在字符串开始处进行匹配,完全可以用search+^代替match
  • re.split(元字符(串), 字符串)

    • 以元字符对应的规则进行分割,形成列表

6. 面向对象思想基础

  • 核心是“对象”

6.1 对象

  • 对象是“容器”,用来盛放数据与功能
  • 对象就是将程序“整合”
    • 对象并非是类独有的,只要有整合程序的功能,例如函数、列表、字典等,都可称为对象

6.2 类

  • 类也是容器,该容器用来存放同类对象共有的数据与功能

    • 由定义可知,类也是对象
    • 类是对象相似数据与功能的集合体
    • 类体中最常见的是变量与函数的定义,但是类体可以包含任意代码
  • 类的使用思路

    • 先定义类

      • 类名使用驼峰体

        class ClassName:
            # 变量的定义
            # 功能的定义
        
      • 类体代码在定义阶段就会执行,产生类的名称空间

      • 在类中创建__init__方法,在定义阶段不会执行

        # self参数为自动传入,self即对象本身
        def __init__(self, argu1, argu2, ...):
            ...
        
    • 访问类

      • 本质上是从类的字典属性(__dict__)中调用名称空间
      • Python中可以从类中调用类属性 == 解释器将该属性名当作key传入字典属性中进行调用
      • 类可以访问数据、函数
        • 类的数据属性共享给所有对象使用,所有对象访问同一类数据的地址
        • 类的函数属性绑定给对象使用,不同的对象访问的地址不同,但功能相同,比如__init__方法属于函数属性

6.3 面向对象编程

  • 面向对象
    • 介绍:
      • 核心是“对象”二字
      • 对象就是容器,用来城防数据与功能
      • 对象就是将程序高度“整合”
    • 优点:提升程序的解耦合程度,进而增强程序的可扩展性
    • 缺点:设计复杂、容易过度设计
  • 面向对象编程
    • 现实生活中
      • 先找出生活中的对象
      • 再总结归纳出现实生活中的类
    • 程序中
      • 先定义类
      • 后调用类产生对象(实例化)
6.3.1 类的定义与实例化

调用类产生对象

  • object_1 = ClassName()
    
  • 调用类的过程又被称为实例化

    object_1 = ClassName(argu1, argu2, ...)
    
    1. 产生一个空对象
    2. Python会调用类中的__init__方法,将空对象调用类时已经传入的参数一同传给__init__
    3. 返回初始化完成的对象
  • 产生的对象也可以产生特定的变量

    object_1.x = 1  # x不是在ClassName定义时产生的
    

6.4 封装

  • 封装是面向对象三大题型最核心的特性
  • 封装即“整合”
6.4.1 隐藏属性
  • 什么是隐藏属性

    • 在类内可以使用隐藏属性

      • 可以是变量,也可以是函数
    • 在类外无法直接访问双下划线开头的属性,但知道了类名和属性名就可拼出名字,这种操作只是语法意义上的变形

    • 这种变形在检测类体语法时(即类的定义阶段)发生,在此之后定义__开头属性不会被隐藏

    • 在类的__dict__属性中,隐藏属性的key会变形成

      # "_ClassName.__var" 而非 "ClassName.__var"
      
  • 怎样隐藏属性

    • 在属性前加"__前缀"

    • class ClassName:
          __x = 1  # 隐藏属性
          
      
      ClassName.__x  # 报错
      
  • 为何要有隐藏属性

    • 无法直接从类外操作属性
    • 但可从类内使用函数接口操作属性
6.4.2 property
  • 是一种装饰器,用类实现,用来绑定给对象的方法伪造成一个数据属性

    class ClassName:
        def __init__(self, weight, height):
            self.weight = weight
            self.height = height
        
        @property
        def bmi(self):
            return self.weight / (self.height ** 2)
        
    
    info = ClassName(70, 1.83)
    print(info.bmi)  # bmi原本是函数,此时被装饰器伪装成数值
    
  • 可以将操作同一属性的函数对象进行封装,伪装成数据

    # 方法一:
    class ClassName:
        def __init__(self, name):
            self.__name = name
        
        
        def get_name(self):  # 无参数有返回值
            return self.__name
        
        def set_name(self, var):  # 有参数
            if type(var) is not str:
                return
            self.__name = var
         
        def del_name(self):  # 无参数无返回值
            del self.__name
    
    	name = property(get_name, set_name, del_name) # 将三个类型不同的函数封装到property,伪装为数据属性
    
    person_1 = ClassName(‘Stephane‘)
    print(person_1.name)  # 调用get_name
    person_1.name = ‘Zhang‘  # 调用set_name
    del person_1.name  # 调用del_name
    
    # 方法二:
    class ClassName:
        def __init__(self, name):
            self.__name = name
    
        @property
        def name(self):
            return self.__name
    	# name下有setter getter deleter...等访问操作属性
        # 操作同一属性的函数对象需命名相同,但功能不同
        
        @name.setter
        def name(self, var):
            if type(var) is not str:
                return
            self.__name = var
    
        @name.deleter
        def name(self):
            del self.__name
            print("The var has been deleted.")
    
    
    person_1 = ClassName(‘Stephane‘)
    print(person_1.name)
    person_1.name = ‘Zhang‘
    del person_1.name
    
    

6.5 继承

  • 什么是继承
    • 继承是创建类的方式新建的类为子类(派生类),被继承的类为父类(基类,超类)
    • 新建的类可以继承一个或多个父类(多继承)
      • 优点 可以同时遗传多个父类属性,最大限度地重用代码
      • 缺点
        • 与思维习惯相悖,降低代码可读性
        • 扩展性变差
        • 若涉及到一个子类不可避免地使用多个父类的属性,应使用Mixins机制
      • 如何更好地使用
        • 继承结构不要过于复杂
        • 要满足继承的 is 关系
    • Python2中有经典类和新式类
      • 新式类:继承了object类的子类,以及该子类的子类
      • 经典类:没有继承object类的子类,以及该子类的子类
    • Python3中
      • 没有继承任何类的会默认继承object类
      • Python3中所有类都是新式类
  • 为何要用继承
    • 子类会遗传父类的属性
    • 减少类与类之间代码冗余
class Parent1:  # 父类
    pass

class Parent2:
    pass

class Sub1(Parent1):  # 单继承
    pass

class Sub2(Parent1, Parent2):  # 多继承
    pass

print(Sub1.__bases__)  # Parent1
print(Sub2.__bases__)  # Parent1, Parent2
6.5.1 继承与抽象
  • 可以对父类中的函数进行调用(包括__init__方法),以此来对其进行补充
6.5.2 单继承下的属性查找
  • 会从对象所在类开始查找,即使在当前位置在父类中

    class Person:
        def out(self):
            print("Out from Person.")
        def out2(self):
            print("Out2 from Person.")
            self.out()
    
    class Student(Person):
        def out(self):
            print("Out from Student.")
    
    student_1 = Student()
    student_1.out2() # Out2 of Person. Out of Student.
    
  • 若使用隐藏属性,则会在查找时替换为_ClassName. __xxx

    class Person:
        def __out(self):
            print("Out from Person.")
        def out2(self):
            print("Out2 from Person.")
            self.__out()  # self._Person.__out()
    
    class Student(Person):
        def out(self):
            print("Out from Student.")
    
    student_1 = Student()
    student_1.out2()
    
6.5.3 继承的实现原理
  • 如果多继承是非菱形问题,Python1与Python3的属性查找顺序一样,都是一个分支一个分支找下去,最后找到object

    技术图片

  • 当多继承是菱形问题,那么经典类与新式类会有不同的MRO,分别对应属性的两种查找方式:深度优先和广度优先

6.5.4 MRO
  • 对于每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该列表是一个简单的所有基类的线性顺序列表
6.5.5 菱形问题
  • 一个子类继承多个类,而多个类最终都汇集于同一个非object类时,就容易引发“菱形问题”

    class A:
        def out(self):
            print("Out from A.")
    
    class B(A):
        def out(self):
            print("Out from B.")
    
    class C(A):
        def out(self):
            print("Out from C.")
            
    class D(B, C):
        pass
    
    obj = D()
    obj.out()  # out from B.
    
    D.mro()
    """
    [<class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>]
    """
    # 因此查找顺序为 D -> B -> C -> A -> object
    
6.5.6 多继承背景下的属性查找探讨
  • 子类优先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类
6.5.7 深度优先与广度优先
  • Python2中的经典类为深度优先查找,即在检索第一条分支时就“一条道走到黑”,会检索到共同父类

    技术图片

  • Python2的新式类和Python3为广度优先查找,会在检索最后一条分支时“一条道走到黑”,检索共同父类

    技术图片

6.5.8 Python Mixins机制
  • 核心:在多继承背景下尽可能地提升多继承的可读性,让多继承满足人的继承习惯

  • 使用Mixin类时,需注意

    • 它必须表示一种功能,Python对于Mixin类的命名方式一般以Mixin、able、ible等结尾
    • 必须责任单一,若有多个功能(不代表只能有一个函数),就多写Mixin类,一个类可以继承多个Mixin类,但为了保证is a原则,只能继承一个标识其归属含义的父类
    • 不依赖于类的实现
    • 子类不继承某个Mixin类时,不会影响到子类的工作,只是缺少了某些功能
    • Mixin类滥用不利于代码的可读性
    class Vehicle:
        pass
    
    
    # 添加后缀Mixin表示一种规范,表示混合,而非表示"is"
    class FlialeMixin:
        ...
    
    class CivilAirCraft(FlialeMixin, Vehicle):
        pass
    
    class Helicoper(FlialeMixin, Vehicle):
        pass
    
    class NormalCraft(Vehicle):
       	pass
    
    class Car(Vehicle):
        pass
    
6.5.9 派生与方法

在子类派生的新方法中如何重用父类的功能

  • 直接调用某个类下的函数,不依赖继承

  • 用super()调用父类的函数,严格依赖继承关系

    • super()默认等于super(当前类名, self)
    class A:
        def out(self):
            print("Out from A")
            super().out()
            # 依照C类的MRO列表,B在A之后,因此super().out()为B中的out()
            
    class B:
        def out(self):
            print("Out from B")
    class C(A, B):
        pass
    
    obj = C()
    obj.out()  # Out of A Out of B
    
    
6.5.10 组合
  • 在一个类中以另外一个类的对象作为数据属性,称为类的组合,表示"Has a"关系,当类之间显著不同,并且较小的类是较大的类所需要的组件时,应使用组合

6.6 多态性与鸭子类型

6.6.1 多态
  • 多态:同一事物有多种形态

  • 为何要有多态、多态性

    • 多态性指的是可以不考虑对象具体类型的情况下而直接使用对象
    • 多态性的好处在于增强了程序的灵活性和可扩展性
  • 如何使用多态

    • 多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象

      class Animals:
          def say(self):
              print("The frequency of voice.")
      
      class People(Animals):
          def say(self):
              super().say()
              print("Talk")
      
      class Pig(Animals):
          def say(self):
              super().say()
              print("Woof")
      
      class Dog(Animals):
          def say(self):
              super().say()
              print("Heng")
      
      def animals_say(type_val):  # 可以将这个方法单独提取出来,使用统一接口,方便调用
          type_val.say()
      
      obj_1 = People()
      obj_2 = Pig()
      obj_3 = Dog()
      
      obj_1.say()
      obj_2.say()
      obj_3.say()
      
      animals_say(obj_1)
      animals_say(obj_2)
      animals_say(obj_3)
      
    • 可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名

      import abc
      
      # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
      class Animal(metaclass=abc.ABCMeta):
          @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
          def talk(self): # 抽象方法中无需实现具体的功能
              pass
      
      class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
          def talk(self):
              pass
      
      cat=Cat() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
      
6.6.2 鸭子类型
  • 完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象。

  • 比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度

  • 只要两个类中有名字相同且功能相同的函数,就可以“蒙混过关”,起到与多态相同的效果

    class People:
        def say(self):
            print("The frequency of voice.")
            print("Talk")
    
    class Pig:
        def say(self):
            print("The frequency of voice.")
            print("Woof")
    

6.7 绑定方法与非绑定方法

6.7.1 绑定方法
  • 特殊之处在于将调用者本身当作第一个参数自动传入
6.7.1.1 绑定给对象的方法
  • 调用者是对象,即自动传入的是对象
6.7.1.2 绑定给类的方法
  • 调用者是类,即自动传入的是类

    • 绑定到类的方法就是专门给类用的,但其实对象也可以调用,只不过自动传入的第一个参数仍然是类,也就是说这种调用是没有意义的,并且容易引起混淆
    import ipSettings  # 含参数的文件
    # IP = "192.168.20.5"
    # PORT = 3306
    
    class Mysql:
        def __init__(self, ip, port):
            self.ip = ip
            self.port = port
    
        def output(self):
            print("The IP is %s, the port is %d" % (self.ip, self.port))
    
        @classmethod  # 将下面的函数装饰为绑定给类的方法
        def put(cls):  # cls即class,与self相似
            return cls(ipSettings.IP, ipSettings.PORT)
        	# 赋值给对象时,相当于调用__init__方法,并把相信的参数传入__init__()
    
    
    obj = Mysql.put()
    print(obj)
    obj.output()
    
  
  

##### 6.7.2 非绑定方法(静态方法)

- 为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法

- 该方法***不与类或对象绑定***,类与对象都可以来调用它,但它就是一个普通函数,没有自动传值那么一说

  ```python
  import ipSettings
  
  class Mysql:
      def __init__(self, IP, PORT):
          # self.nid
          self.IP = IP
          self.PORT = PORT
  
      @staticmethod  # 将该函数装饰为静态方法(非绑定方法)
      def createId():
          pass
  
      @classmethod
      def put(cls):
          return cls(ipSettings.IP, ipSettings.PORT)
  
      def output(self):
          print("The IP is %s, the port is %d" % (self.IP, self.PORT))
  
  
  obj = Mysql.put()
  obj.output()
  print(Mysql.createId())  # 不需实例化即可调用该方法

7. 面向对象高级

7.1 反射

  • python是动态语言,而反射(reflection)机制被视为动态语言的关键

  • 反射机制指的是在程序运行状态中

    • 对于任意一个类,都可以知道这个类的所有属性和方法

      对于任意一个对象,都能够调用他的任意方法和属性

    这种动态获取程序信息以及动态调用对象的功能称为反射机制

  • 什么是反射?

    • 指的是程序运行过程中可以“动态”获取对象的信息
  • 为何要用?

    • 为何获取一个对象中是否有某些属性
  • 如何使用?(步骤)

    1. dir(对象) 查看对象的属性(列表)

    2. 可以通过字符串反射到真正的属性上

    3. 四个内置函数的使用

      • hasattr(对象, 属性) 判断对象中是否有该属性
      • getattr(对象, 属性, 提示信息) 得到对象的属性,若没有该属性,返回默认参数
      • setattr(对象, 属性, 值) 将对象的属性值改为新值
      • delattr(对象, 属性) 删除对象中的属性
    class People:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def get(self):
            print(‘name is %s and age is %d‘ %(self.name, self.age))
    
    print(dir(obj))  # [‘__class__‘, ‘__delattr__‘, ‘__dict__‘,...]
    print(obj.__dict__[‘name‘])
    print(hasattr(obj, ‘name‘))
    print(getattr(obj, ‘name‘))
    print(setattr(obj, ‘name‘, ‘XXX‘))
    print(delattr(obj, ‘name‘))
    

7.2 内置方法

定义在类内部,以__开头,并以__结尾的方法

  • 特点 会在某种情况下自动触发执行

  • 为何要用?

    • 为定制化类或对象
  • 如何使用?(例子)

    • __str__ 在打印对象时自动触发,返回值须为字符串
    • __del__ 在清理对象时触发(程序结束时也会触发),会先执行该方法
  • __new__ 在创建空对象之后,调用元类__init__方法之前,用于创造空对象

    • __call__
    class People:
        def __init__(self, name, age):
            self.name = name
            self.age = age
        
        def __str__(self):
            res = ‘Name is %s and age is %d‘ %(self.name, self.age)
            return res  # 返回值须为字符串
    
    obj = People(‘Stephane‘, 19)
    print(obj)  # Name is Stephane and age is 19
    

7.3 元类

  • 什么是元类?

    • 在实例化对象(调用类)时格式为 objectname = ClassName(...)

    • 类本身也是对象,调用类的类格式为 ClassName1 = Classname2(),后者被称为元类(用来实例化产生类的类)

    • 元类 ---- 实例化 ----> 类 ---- 实例化 ----> 对象

      # 查看内置的元类type
      print(type(People))  # <class ‘type‘>
      
    • class关键字定义的所有的类以及内置类都是由元类type实例化产生

  • 如何使用元类?

  • class关键字创建类的步骤

    • 类名

      class ClassName = "..."
      
    • 类的基类(并非元类)

      class_bases = (object|...)
      
    • 执行类体代码拿到类的名称空间

      class_dic = {}  #类的名称空间
      class_body = "类体代码"
      exec(class_body, {}, class_dic)  # 第二个参数为类体中使用的的全局名称空间,没有则为空
      
    • 调用元类

      Classname = type(class_name, class_bases, class_dic)
      
  • 如何自定义元类控制类的产生

    class Mymeta(type): #只有继承了type类的类才是元类
        
        def __init__(cls, class_name, clas_sbases, class_dic):
            super().__init__(cls)
            pass
        def __new__(mcs, *args, **kwargs):
            # 参数为调用元类时传入的参数
            return super().__new__(mcs, *args, **kwargs)
    
    class People(metaclass=Mymeta):  # 调用Mymeta元类
        pass
    # People = Mymeta(class_name, class_bases, class_dic)
    
    • 调用自定义元类发生的事情

      调用自定义元类实质是在调用该元类的__call__方法

      1. 先造一个空对象(People),调用元类中的__new__方法
      2. 调用Mymeta类中的__init__方法,完成初始化对象的操作
      3. 返回初始化好的对象

7.4 re

# Python基础

标签:属性绑定   面向过程   重用   lse   case   star   开始   超过   形参与实参   

原文地址:https://www.cnblogs.com/stephane-zhang/p/14198513.html

踩
(0)
赞
(0)
   
举报
评论 一句话评论(0)
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!