内置函数

Python官方文档

python解释器可以使用Tab不全,通过ctrl+c或ctrl+z回车或quit()可以退出解释器,重新回到windows命令行

pass常常作为定义函数、类、循环等操作的占位符,为了满足语法规范,防止报错

enum库 定义常量

一、注释和变量命名规范

1.1 注释

  • 单行注释 #
  • 多行注释 三重引号
  • 特殊注释 类似于shell脚本指定shell和字符编码
#!/usr/bin/env python3
# -*- coding: cp1252 -*-

1.2 变量命名规则

  • 只能包含数字、字母、下划线,且不能以数字开头
  • 区分大小写,变量一般使用小写
  • 变量、函数名使用蛇形命名法
  • 类名使用大驼峰命名法
  • 常量命名时字母全大写
  • 见名知意
  • 命名时谨慎使用小写字母l和大写字母O,以免和数字混淆
  • 不能和python关键字重名

二、内置函数

  • print()函数
  • input()函数 返回用户输入的字符串
  • type()函数 返回数据类型
  • range()函数 生成等差数列,左闭右开区间,返回range对象,这是一个可迭代对象iterable
    • range(num) 返回0到num之间的整数序列
    • range(num1,num2) 返回两个数字之间的整数序列
    • range(num1,num2,step) 返回两个数字之间的整数序列,通过step指定间隔大小
  • 类型转换函数
    • int() 函数 转换为整型,不是四舍五入,而是舍弃小数点之后的部分
    • float() 转换成小数
    • str() 转换成字符串
    • list() 函数
    • str() 函数
    • tuple() 函数
    • set() 函数

三、运算符

3.1 算术运算符和数学计算

  • + 加
  • - 减
  • * 乘
  • /除 计算结果是浮点数
  • // 整除
  • % 取余
  • ** 乘方

如果参与运算的数字中有浮点数,返回的结果也一定是浮点数

交互模式下,上次输出的表达式会赋给变量 _

>>> 2+3
5
>>> 10 + _
15

除了 int 和 float,Python 还支持其他数字类型,例如 Decimal 或 Fraction。Python 还内置支持 复数,后缀 j 或 J 用于表示虚数(例如 3+5j )

3.2 赋值运算符

  • = 赋值
  • += 加法赋值运算符
  • -= 减法赋值运算符
  • *= 乘法赋值运算符
  • /= 除法赋值运算符
  • %= 取模赋值运算符
  • **= 幂赋值运算符
  • //= 取整除赋值运算符

3.3 比较运算符

  • == 判断内容是否相等,满足为True,不满足为False
  • != 判断内容是否不相等,满足为True,不满足为False
  • > 判断运算符左侧内容是否大于右侧,满足为True,不满足为False
  • < 判断运算符左侧内容是否小于右侧,满足为True,不满足为False
  • >= 判断运算符左侧内容是否大于等于右侧,满足为True,不满足为False
  • <= 判断运算符左侧内容是否小于等于右侧,满足为True,不满足为False

3.4 逻辑运算符

  • 与 and
  • 或 or
  • 非 not

基本数据类型

四、数据类型

基本数据类型有 整型int 浮点数float 布尔数bool -> True/False 字符串str 复数complex 空NoneType -> None 列表list 集合set 元组tuple 字典dict。整数和浮点数可以适当添加下划线让数字更易读。可以通过使用逗号分割同时为多个变量赋值。

数据容器:一种可以容纳多份数据的数据类型,容纳的每一份数据称之为1个元素,每一个元素,可以是任意类型的数据,如字符串、数字、布尔等。五种数据容器分别是 列表list 集合set 元组tuple 字典dict 字符串str

4.1 空类型 <class 'NoneType'>

None是python中一个特殊的字面量,表示空,无意义。无返回值的函数返回的实际上是None,在if判断语句中None相当于False,定义变量但是暂时不想赋值时使用None当占位符,条件判断中判断是否是None,使用is关键字,而不用相等运算符 ==

4.2 数据容器通用操作

  1. 遍历 全都可以使用for循环,集合和字典不能使用while循环
  2. 共有的函数
    • len() 函数
    • max() 函数
    • min() 函数
    • sorted(容器, [reverse=True]) 函数 排序,reverse=True 表示降序,返回一个排好序的列表

4.3 字符串<class 'str'>

使用单引号、双引号、三重引号包裹,可以使用 + 进行字符串的拼接,使用 * n 进行字符串的重复,n 表示重复次数

三重引号用于包裹多行字符串。一个引号的写法也可以跨行,只需要在每行的末尾加入表示连接的字符 \ 即可

字符串是不可变对象immutable,不能单独修改字符串某个字符,只能修改整体,字符串str更像是元组tuple而不是列表list

转义字符

  • \n 换行符
  • \t 制表符

可以切片,也可以使用while循环或for循环遍历

4.3.1 字符串格式化

  • f字符串
  • 占位符字符串

在不需要控制数字的精度时,采用 f字符串 明显比 占位符字符串 简单。以下重点介绍占位符字符串

占位符

  • %s 将内容转换成字符串,放入占位位置
  • %d 将内容转换成整数,放入占位位置
  • %f 将内容转换成浮点型,放入占位位置

用于控制精度的符号

  • m 控制宽度,要求是数字(很少使用),设置的宽度小于数字自身,不生效。可以在 m 前添加 0 来使用 0 来补位
  • .n 控制小数点精度,要求是数字,会进行小数的四舍五入
  • - 配合 m 使用,控制宽度不够时在右边补空格,默认在左边补空格

f-string 格式控制

基本语法 f"{变量名:格式控制标记}"

格式控制标记 <填充字符><对齐方式><宽度><,><.精度><类型>

  • 填充字符 默认是空格,只能是单个字符,需要配合 宽度 使用
  • 对齐方式 需要配合宽度使用:
    • ^ 居中
    • > 右对齐
    • < 左对齐
  • 宽度 整数,表示这个字符串的长度
  • , 千位分隔符

4.3.2 字符串操作

常用函数和方法

  • replace(str1,str2) 方法 将字符串内的全部:str1,替换为str2。不改变原字符串
  • split(分隔str) 方法 以分隔str为分隔符,将原字符串拆分,返回一个列表。不改变原字符串
  • strip() 方法 删除字符串前后的空白字符,传入参数时,删除参数字符串中的字母,比如传入'ab',删除的是字符串前后的字母a和字母b
    • rstrip() 方法
    • lstrip() 方法
  • upper()方法 将字符串所以字母转换为大写
  • lower()方法 将字符串所有字母转换为小写
  • title()方法 单词首字母大写
  • removeprefix()方法 删除前缀,需要传入需要删除的前缀

字符串特点

  • 只可以存储字符串
  • 长度任意(取决于内存大小)
  • 支持下标索引
  • 允许重复字符串存在
  • 不可以修改(增加或删除元素等)
  • 支持for循环

4.4 列表 <class 'list'>

使用方括号 [] 包裹,元素之间使用逗号分隔,列表中的数据可以是不同的数据类型,有顺序,使用下标获取元素。正向下标从 0 开始,反向下标从 -1 开始,列表命名时建议使用名词复数形式

可以使用for循环或while循环遍历

列表和字符串一样,也可以进行加法运算和乘法运算,列表是可变对象mutable

操作列表

  • index(元素) 方法 返回查询元素的下标(正向下标)
  • 列表名[下标] = 新值 更改元素的值
  • insert(下标,值) 方法 在指定位置插入元素
  • append(值) 方法 在列表末尾追加元素
  • extend(数据容器) 方法 将其它数据容器的内容取出,依次追加到列表尾部
  • pop(下标) 方法 删除元素,不传入参数时删除末尾的元素,返回删除的元素
  • remove(元素) 方法 删除某元素在列表中的第一个匹配项,根据值删除元素,可以与while循环配合使用删除所有匹配项
  • clear() 方法 将列表转换为空列表 []
  • count(元素) 方法 统计某元素在列表中出现次数
  • sort()方法 正序排列,传入reverse=True时倒序排列(按照数字或字母顺序)
  • reverse()方法 倒置,将原顺序倒置

del 列表名[下标] 删除元素

  • len() 函数 返回列表长度
  • sorted()函数 正序排列,传入参数reverse=True时倒序排列
  • max(lst)函数 取列表中的最大值
  • min(lst)函数 取列表中的最小值
  • sum(lst)函数 计算列表元素之和
  • zip(lst1, lst2)函数 同时遍历多个列表
  • enumerate(lst, start) 函数 同时遍历列表下标和元素,可选参数 start 表示列表下标开始值,默认0
  • 列表推导式

列表特点

  • 可以容纳多个元素
  • 可以容纳不同类型的元素(混装)
  • 数据是有序存储的(有下标序号)
  • 允许重复数据存在
  • 可以修改(增加或删除元素等)

切片

序列是指:内容连续、有序,可使用下标索引的一类数据容器,列表、元组、字符串,均可以可以视为序列。序列的典型特征就是:有序并可用下标索引,字符串、元组、列表均满足这个要求

切片实际上是浅拷贝

通过类似于切片的写法,列表可以一次性修改多个元素的值

语法:序列[起始下标: 结束下标: 步长] 左闭右开区间

  • 起始可以省略,省略从头开始
  • 结束可以省略,省略到尾结束
  • 步长可以省略,省略步长为1(可以为负数,表示倒序执行)
  • 使用切片创建列表副本 序列[:]

4.5 元组 <class 'tuple'>

使用小括号包裹,元素之间使用逗号分隔,单元素元组需要在元素后添加逗号。元组相当于不可修改的列表,元组中的数据可以是不同的数据类型

可以切片,可以使用while循环或for循环遍历

常用函数和方法

  • len()函数
  • index(元素)方法
  • count(元素)方法

元组特点

  • 不可以修改元组的内容,否则会直接报错
  • 可以修改元组内嵌套的list的内容(修改元素、增加、删除、反转等)但是不可以替换list为其它list或其它类型
  • 可以容纳多个数据
  • 可以容纳不同类型的数据(混装)
  • 数据是有序存储的(下标索引)
  • 允许重复数据存在
  • 支持for循环

4.6 集合 <class 'set'>

使用花括号 {} 包裹,元素使用逗号分隔。元素无序,互不相同,没有下标索引。

只能使用for循环,不能使用while循环遍历。

常用函数和方法

  • add(元素) 方法 添加元素,会导致集合本身遭到修改
  • remove(元素) 方法 移除元素,会导致集合本身遭到修改
  • pop() 方法 从集合中随机取出一个元素,会导致集合本身遭到修改
  • clear() 方法 清空集合,返回空集 set(),会导致集合本身遭到修改变为 set()
  • 集合1.difference(集合2) 方法 取出集合1和集合2的差集(集合1有而集合2没有的),得到一个新集合,不改变原来的两个集合
  • 集合1.difference_update(集合2) 方法 消除2个集合的差集,对比集合1和集合2,在集合1内,删除和集合2相同的元素。会导致集合1被修改,集合2不变
  • 集合1.union(集合2) 方法 得到两个集合的并集,不改变原来两个集合
  • len() 函数

集合特点

  • 可以容纳多个数据
  • 可以容纳不同类型的数据(混装)
  • 数据是无序存储的(不支持下标索引)
  • 不允许重复数据存在
  • 可以修改(增加或删除元素等)
  • 支持for循环

4.7 字典dict

通过键获取值,使用花括号包裹,键值对使用逗号分隔,键和值使用冒号连接。

常用操作

  • 新增元素 字典名[Key] = Value

  • 更新元素 字典名[Key] = Value 对已存在的键使用

  • 删除元素 pop(Key) 方法 字典被修改,指定Key的键值对被删除,返回键对应的值value

  • 清空字典 clear() 方法 字典被修改为空字典{},元素被清空

  • 字典长度 len()函数

  • 删除字典中的键值对:使用 del 语句将指定字典名中要删除的键相应的键值对彻底删除。例如 使用 del mydict['name'] 删除mydict字典中的name对应的键值对

  • get() 方法

    • 用来访问字典某个键对应的值,如果键不存在,返回默认值或None
    • my_dict.get(key, [default]):根据键获取值,默认值 default 实参是可选的
  • {==items()==}方法 用于同时遍历字典的键和值

    ??? example

      代码
      ```py
      languages = {'python': 'py', 'javascript': 'js', 'c++': 'cpp'}
      for lang, extension in languages.items():
          print(f'{lang}语言的文件后缀名是{extension}')
      ```
      输出结果
      ```
      python语言的文件后缀名是py
      javascript语言的文件后缀名是js
      c++语言的文件后缀名是cpp
      ```
    
  • keys()方法 返回键的列表,一般用于遍历所有键,直接使用字典名遍历,默认是遍历字典的键的列表

  • values()方法,返回值的列表,一般用于遍历所有的值

  • 字典推导式 能明显简化代码

    ???+ example

      ```py
      langs = ['python', 'javascripts', 'c++']
      extensions = ['py', 'js', 'cpp']
      languages = {lang: extension for lang, extension in zip(langs, extensions)}
      print(languages) # 输出结果 {'python': 'py', 'javascripts': 'js', 'c++': 'cpp'}
      ```
    

??? note "字典特点"

- 可以容纳多个数据
- 可以容纳不同类型的数据
- 每一份数据是KeyValue键值对
- 可以通过Key获取到Value,Key不可重复(重复会覆盖)
- 不支持下标索引
- 可以修改(增加或删除更新元素等)
- 支持for循环,不支持while循环

语句和函数

五、判断语句和循环语句

5.1 判断语句

判断语句if,和布尔数以及比较运算符一起使用。关键字in 检查特定值是否在列表、元组中。将数值 0、空值 None、空字符串、空列表[]、空元组()、空字典{} 用作条件表达式时,Python 都会返回 False

三种if语句

  • if 语句
  • if else 语句和 双目运算符
  • if elif else 语句

match case 语句

5.2 循环语句

while循环 不断地运行,直到指定的条件不再满足为止。用途比for循环更广泛

for循环 适用于可迭代类型(列表,字符串,元组,字典,range()函数生成的数字序列等)

循环中断

  • continue 中断本次循环,进入下一次循环
  • break 结束循环

六、函数function和模块

使用 def 关键字定义函数,函数必须先定义再使用。使用 return 关键字设置返回值,没有定义返回值的函数返回 None,使用蛇形命名法

定义函数之后,在 def 的下一行使用三重引号作为函数的文档字符串

使用 global 关键字 可以在函数内部声明变量为全局变量

多返回值 return语句得到返回值,如果想要返回多个可以使用逗号分隔,实际上返回的是元组

传参方式

  1. "位置参数"
    • 调用函数时根据函数定义的参数位置来传递参数
    • 传递的参数和定义的参数的顺序及个数必须一致
  2. "关键字参数"
    • 函数调用时通过 "键=值" 形式传递参数,不受传参顺序影响,关键字参数必须写在位置参数之后默认值
    • 定义函数时可以为参数设置默认值,所有位置参数必须放在默认值参数之前
    • 通过将参数的默认值设为 '' 或 None,可以使其成为可选参数,具体用哪个当做占位符取决于参数的数据类型
  3. "不定长参数"
    • 多个位置参数 *args,args是多个位置参数的习惯性写法。传入的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组(tuple),args是元组类型,这就是位置传递
    • 多个关键字参数 **kwargs,kwargs是习惯性写法,可以自己命名。参数是“键=值”形式的形式的情况下, 所有的“键=值”都会被kwargs接受, 同时会根据“键=值”组成字典.

当函数的参数是列表时,为了防止原列表被函数修改,传参数可以传入原列表的副本进行操作

函数编写指南

  • 要给函数和模块一个描述性名称,且只使用小写字母和下划线。
  • 每个函数都应包含简要阐述其功能的注释,该注释应紧跟在函数定义后面,并采用文档字符串docstring的格式。
  • 给形参指定默认值和调用函数传入实参时,等号两边不要有空格
  • 应使用两个空行分开不同的函数块
  • 所有的 import 语句都应放在文件开头,在文件开头使用注释来描述整个程序。

匿名函数

  • "将函数作为参数"

  • "lambda函数"

    lambda匿名函数 临时使用的函数,只能使用一次

    语法 lambda 参数 : 函数体 传入参数表示匿名函数的形式参数,函数体只能写一行代码

导入模块

  • import module_name 通过module_name.function_name()使用模块中的函数
  • from module_name import function_0, function_1, function_2 直接通过function_0()使用模块中的函数,这种用法需要防止有函数重名
  • from module_name import function_name as fn 使用as为函数取别名
  • import module_name as mn 使用as为模块取别名
  • from module_name import * 导入模块中所有函数(不推荐使用)

文件和异常

七、文件操作

7.1 文件操作基本流程

  1. 打开文件
  2. 读写文件
  3. 关闭文件

7.2 通过open函数操作文件

  1. 打开文件

    • open(file, mode, encoding) 函数,返回文件对象,用于打开文件或创建文件
    • file参数 文件路径字符串
    • mode参数 文件访问模式
    • encoding参数 编码方式,推荐使用utf-8

    mode参数的可选值

    • 'r' 只读模式(默认)
    • 'w' 覆写模式,覆盖文件原有内容,写入新内容。文件不存在时,自动创建文件
    • 'a' 追加模式,在原文件末尾写入内容。文件不存在时,自动创建文件
    • 'rb' 使用只读模式打开二进制文件(图片等)
    • 'wb' 使用覆写模式写入二进制文件
    • + 配合以上方法使用,在原有功能上增加同时读写功能
  2. 读写文件

    • read()方法 读取文件内容,默认读取全部内容。传入数字可指定读取的字节长度
    • readlines()方法 按行读取,返回列表,如果返回的列表需要遍历,则直接遍历文件对象也可以实现这个功能,并且更高效
    • readline()方法 一次读取一行,此方法和 for i in open()相同,一次读取一行内容,大多数行末尾会有一个换行符
    • write()方法 将内容写入文件
    • flush()方法 刷新内容,使write()函数的内容成功写入文件
    • write()函数将文件内容写到缓冲区,返回写入的字符数,需要使用flush()方法或者close()方法才可以完全写入文件
    • writelines()方法 传入字符串列表,将此列表写入文件
    • seek()方法 传入数字,改变当前文件操作指针的位置,英语占一个字节,GBK编码中文占2个字节,UTF-8编码中文占3个字节
  3. 关闭文件 close()方法

快捷操作 with open() as f 使用with open语句对文件操作结束后,会自动关闭文件

7.3 通过pathlib操作文件

  1. 导入标准库 pathlib

     from pathlib import Path
    
  2. 创建 Path 对象

     path = Path() # 传入文件路径
    
  3. 对 Path 对象进行操作

    Path 对象的常用方法:

    • read_text() 方法 读取path对象路径文件的所有内容,可选参数encoding指定读取文件使用的编码,常用utf-8
    • write_text() 方法 将字符串写入文件,如果指定路径的文件不存在,则会创建它,使用此方法写入时会删除文件原有内容
    • exists() 方法 判断文件是否存在
    • path对象可以通过str()函数转换为路径字符串

常和字符串的 splitlines() 方法一起使用,按行分割字符串,返回列表

八、异常

Python 使用称为异常(exception)的特殊对象来管理程序执行期间发生的错误。

try except else finally语句

  • try 语句 可能引发异常的代码
  • except 语句 出现异常时,不出现traceback终止程序
    • 捕获一种异常 except NameError as e:
    • 捕获多种异常 except (NameError, ZeroDivisionError):
    • 捕获所有异常 except Exception as e:
  • else 语句(可选) 不出现异常继续执行的代码
  • finally 语句(可选) 无论是否异常都要执行的代码

OOP面向对象

要使用tkinter进行GUI设计,就不得不用到面向对象的编程思想。Tkinter提供的小部件非常有限,而且大多数小部件的功能往往不足以完成相应的任务。在这种情况之下,就不得不使用继承来加强小部件的功能,或者创建新的小部件。

面向对象编程(object-oriented programming,OOP)是最有效的软件编写方法之一,在面向对象编程中,我们可以:编写表示现实世界中事物或情景的类(class),并基于这些类来创建对象(object)。通过类的实例化来根据类创建对象,并在代码中使用类的实例(instance)对象。

class关键字,类名以大写字母开头(大驼峰命名法)。 类名后面不需要加括号,但是子类后面需要加括号

一、属性

使用 dir() 函数可以查看一个对象所有的方法和属性

1.1 类属性

通俗来说,类属性就是定义在类中的变量。不需要创建实例就能直接使用,当然实例也可以使用类属性。通过类名就可以调用,可以更改属性值,还可以创建新属性。

我们来创建一个 Banana 类:

class Banana:
    """一种美味的热带水果"""
    food_group = 'fruit'
    colors = ['green', 'green-yellow', 'yellow', 'brown spotted', 'black']

上面代码中的 food_group 和 colors 就是类属性。它们不需要创建实例就可以使用,对二者调用 print() 函数:

print(Banana.food_group) # fruit 
print(Banana.colors) # ['green', 'green-yellow', 'yellow', 'brown spotted', 'black']

1.2 实例属性

使用初始化方法 __init__() 创建实例属性。self 形参:它是一个指向实例本身的引用,实例的每次调用都会自动传入自身,这样使得实例能够访问类中的属性和方法,self 形参必不可少,而且必须位于其他形参的前面。

所有的实例属性定义时都有 self. 的前缀,有些属性无须通过形参来定义,可以在 __init__() 方法中为其指定默认值。

实例属性就是定义在构造方法(又称初始化方法,属于魔术方法的一种) __init__() 中的带有 self. 前缀的变量,必须创建实例之后才能使用。不创建实例通过类名调用会报错。需要先创建实例,才能调用,创建实例后,可以对属性重新赋值,还可以创建新属性。

class Banana:
    # --snip--
    def __init__(self, color='green'):
        self.color = color

此处的 self.color 就是实例属性,它是定义在 __init__() 方法中的一个变量。

  • __init__(self) 方法的第一个参数必须是 self,用来表示实例本身
  • self.color 和 color 并不是同一个变量,而是两个不同的变量,self.color 是一个不可分割的整体

为了使用 self.color 属性,我们需要创建实例:

my_banana = Banana()
print(my_banana.color) # green

二、方法

2.1 实例方法

定义在类中的函数,和 __init__() 方法一样,第一个参数必须是 self。需要先创建实例,才能调用。创建实例后,可以新建方法(先定义一个函数,再使用self.的方式将创建的函数名赋值给self)。

class Banana:
    # --snip--
    def print_color(self):
        print(self.color)

需要创建实例才可以使用:

my_banana.print_color() # green

2.2 类方法

使用装饰器 @classmethod 修饰的方法,类方法的第一个参数必须是 cls,表示这个类本身。不能使用实例属性和实例方法,但是可以通过 cls. 来访问类属性,类似与实例属性的访问。

不需要创建实例就可以调用类方法,在类中也可以通过 self. 来调用类方法,类似于实例方法在类中的调用。当然实例也可以调用类方法。

cls() 函数,在类方法中使用可以创建实例对象。

class Banana:
    # --snip--
    @classmethod
    def check_color(cls, color):
        return color in cls.colors

这个方法通过类名就可以直接调用:

print(Banana.check_color('blue')) # False

2.3 静态方法

使用装饰器@staticmethod修饰的方法,定义静态方法时,不需要传入参数 self或 cls 。静态方法类似于普通的函数。不能在静态方法中使用实例属性和实例方法。

直接通过类名,就可以调用。当然也可以通过实例调用静态方法。

class Banana:
    # --snip--
    @staticmethod
    def estimate_calories(num_bananas):
        return num_bananas * 105

这个方法通过类名就可以直接调用:

print(Banana.estimate_calories(10)) # 1050

三、类的三个特性

封装、多态、继承

3.1 继承

代码复用。

在编写类时,并非总是要从头开始,如果要编写的类是一个已有类的特殊版本,那么我们就可以使用继承(inheritance)。被继承的类称为父类(parent class)或超类,而继承的类称为子类(child class)。子类可以继承父类的所有属性和方法,也可以定义自己的属性和方法。

父类代码必须放在在子类代码的前面。需要在子类名称后添加括号,并在其中指定父类的名称,括号中指定了当前类继承的父类名称。在子类的初始化方法中, 我们使用了 super() 函数。这是一个特殊的函数,让你能够调用父类的方法,继承父类的实例属性。当父类中定义的一些方法不能满足子类的要求时,可以在子类中随时重写父类的方法:定义一个同名的方法。当调用子类生成的实例时,Python 首先会查找子类中的方法,从而忽略父类中被重写的同名方法。也可以把实例当作属性

单继承 使用 super().__init__() 继承父类属性和方法

多继承 使用 父类类名.__init__ () 继承父类方法和属性

在子类中重写方法 可以使用 super() 从父类继承原方法

class RedBanana(Banana):
    def __init__(self, color='green'):
        super().__init__(color)

通过继承 RedBanana 类获得了 Banana 类的所有属性和方法。

3.2 多态

提高程序的可扩展性。

多个不同的类有同名的方法,通过创建一个参数是类的函数,这个函数中调用这些类同名的方法,可以实现多态(同一个方法,适用于不同的对象产生不同的行为)。

3.3 封装

保护程序的安全。

3.3.1 受保护成员(单下划线)

使用单下划线开头的属性和方法表示受保护的成员,只能在类的内部使用,子类可以通过继承获得受保护成员,实际上在外部也可以使用。

3.3.2 私有成员(双下划线)

使用双下划线开头的变量或方法表示私有成员,只能在类的内部使用,子类无法通过继承获得私有成员

3.3.3 魔术方法和魔术属性(首尾双下划线)

魔术方法

  • 构造方法 __init__
  • __str__ 对实例调用 print() 函数时,返回的内容

魔术属性

  • obj.__dict__属性 存储对象的属性字典
  • obj.__class__属性 存储对象所属于的类,相当于调用了type()函数。在此基础上还可以再使用__name__属性,返类名
  • obj.__bases__属性 存储对象父类的元组
  • obj.__base__属性 存储对象继承的第一个父类
  • obj.__mro__属性 存储直接或间接继承的父类的元组

四、类型注解

类的编写风格

  • 类名中的每个单词的首字母都应大写,并且不使用下划线。这种命名规则叫做大驼峰式命名法(也称为 Pascal 命名法)。实例名和模块名都应该使用全小写,并在单词之间加上下划线(_)。 这种命名规则叫做下划线命名法(也称为 蛇形命名法)
  • 每个类定义后的第一行和模块文件的首行,都应该包含一个文档字符串,简要地描述类的功能,以帮助其他人理解我们的代码
  • 使用两个空行来分隔不同类,使用一个空行分隔类内的不同方法
  • 当使用 import 语句时,使用一个空行分隔标准库和自己的库

适用类型注解可以让 PyCharm 为自定义的方法、变量等添加代码提示。

类型注解在代码简单时,几乎不使用,但是当代码十分复杂时,可以使用类型注解帮助自己更好的记住自己的思路,从而提高自己的工作效率。

4.1 对类中的变量添加类型注解

写法一

my_list: list = [1, 2, 3]
my_tuple: tuple = (1,2,3)

写法二

my_list = [1, 2, 3] # type: list
my_tuple = (1,2,3) # type: tuple

分别对my_list和my_tuple两个变量进行了类型注解。

4.2 对类中的方法添加类型注解

例如:

@staticmethod
def estimate_calories(num_bananas: int) -> int:
   return num_bananas * 105

为传入的参数 num_bananas 和 方法的返回值添加了类型注解。

4.3 Union联合类型注解

如果一个变量中存储了不同类型的数据,那就需要使用联合类型注解,常用于字典、列表等。

使用前先导入模块 from typing import Union

# 旧版本Python
from typing import Union
my_list: list[Union[int, str]] = [1, 2, "hello", "world"]
# 新版本Python可以不导入typing
my_list2: list[int | str] = [1, 2, "hello", "world"]

因为 my_list 变量存储的数据同时有 int 和 str,所以需要使用联合类型注解。


测试

十、测试unittest/pytest

可以使用 标准库unittest 或 第三方库pytest 进行单元测试。一种最简单的测试是单元测试(unit test),用于核实函数的某个方面没有问题。测试用例(test case)是一组单元测试,这些单元测试一道核实函数在各种情况下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情况的测试。全覆盖(full coverage)测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。

使用 pytest 进行测试,会让单元测试编写起来非常简单。我们将编写一个测试函数,它会调用要测试的函数,并做出有关返回值的断言 (assert 语句)。如果断言正确,表示测试通过;如果断言不正确,表示测试未通过。

测试函数和测试文件必须以 test_ 开头

几种常用的断言(类似于if语句的条件判断):

unittest断言 pytest断言 用途
assertEqual(a, b) assert a == b 断言两个值相等
assertNotEqual(a, b) assert a != b 断言两个值不等
assertTrue(a) assert a 断言 a 的布尔求值为 True
assertFalse(a) assert not a 断言 a 的布尔求值为 False
assertIn(element, list) assert element in list 断言元素在列表中
assertNotIn(element, list) assert element not in list 断言元素不在列表中

10.1 对函数进行单元测试pytest

测试文件和被测试文件要放在同一个目录下,此处都放置在 code/08-pytest 目录下。

需要被测试的函数

name_func.py

def get_format_name(first: str, last: str) -> str:
    """返回完整格式的姓名"""
    return first + ' ' + last

测试函数

test_name_func.py

运行测试

Pycharm运行测试非常简单,测试函数旁边会出现运行按钮,点击即可运行测试

通用方法:终端使用cd将当前工作目录切换到 code/08-pytest,输入pytest,回车

10.1 对函数进行单元测试unittest

测试文件和被测试文件要放在同一个目录下,此处都放置在 code/08-unittest 目录下。

需要被测试的函数

name_func.py

测试函数

test_name_func.py

运行测试

直接运行 test_name_func.py


如果我们修改被测试函数,使它能够处理有中间名的姓名:

def get_format_name(first, middle, last):
    """返回完整格式的姓名"""
    return first + ' ' + middle + ' ' + last

此时运行测试,测试将不会通过

测试不通过,说明我们编写的用于获取全名的函数 name_func() 并没有完成我们预期的行为:将只有姓和名的名字合成为全名,以及将有姓、名以及中间名的名字合成为全名。这种情况下我们应该修改 name_func() 这个函数,而不是修改测试。

修改后的 name_func.py

def get_format_name(first, last, middle=''):
    """返回完整格式的姓名"""
    if middle:
        return first + ' ' + middle + ' ' + last
    else:
        return first + ' ' + last

此时运行测试,测试将通过。但是我们编写的测试函数只会测试只有姓和名的情况,并不会测试有中间名的情况,因此我们要添加一个用于测试含有中间名名字的测试。

10.2 对类进行单元测试

  1. 一个需要被测试的类 survey.py

  2. 编写测试函数 test_survey.py

    在测试中,夹具(fixture)可帮助我们搭建测试环境。这通常意味着创建供多个测试使用的资源。在 pytest 中,要创建夹具,可编写一个使用装饰器 @pytest.fixture装饰的函数。装饰器(decorator)是放在函数定义前面的指令。在运行函数前,Python将该指令应用于函数,以修改函数代码的行为。

  3. 运行测试