Python基础语法

1、基础语法

1.1 字面量

  • 字面量:在代码中,被写下来的的固定的,称之为字面量。

  • Python中常用的有6种值(数据)的类型:

1.2 注释

  • 注释:在程序代码中对程序代码进行解释说明的文字。

  • 作用:注释不是程序,不能被执行,只是对程序代码进行解释说明,让别人可以看懂程序代码的作用,能够大大增强程序的可读性。

  • 注释的分类

    • 单行注释:以#开头,#右边的所有文字当作说明,而不是真正要执行的程序,起辅助说明作用。

    • 多行注释: 以一对三个双引号引起来 来解释说明一段代码的作用使用方法。

1.3 变量

  • 变量:在程序运行时,能储存计算结果或能表示值的抽象概念。简单的说,变量就是在程序运行时,记录数据用的。

  • 变量的特征

    • 变量,从名字中可以看出,表示“量”是可变的。
    • 所以,变量的特征就是,变量存储的数据,是可以发生改变的。

1.4 数据类型

  • 我们主要接触如下三类数据类型:

    • string、int、float这三个英文单词,就是类型的标准名称。
  • 我们可以通过type()语句来得到数据的类型:type(被查看类型的数据)

  • **type()**语句的使用方式:

    • 在print语句中,直接输出类型信息:

    • 用变量存储type()的结果(返回值):

  • 字符串有3种不同的定义方式:

    • 双引号定义法:”字符串”

    • 单引号定义法:’字符串’

    • 三引号定义法:”””字符串”””,表示在一堆三个双引号的范围内,均是字符串,如下(要注意的是,包含范围是从三个引号开始,到下一个三个引号结束):

1.5 数据类型转换

  • 常见的转换语句

  • 类型转换注意事项

    • 任何类型,都可以通过str(),转换成字符串。
    • 字符串内必须真的是数字,才可以将字符串转换为数字。

1.6 标识符

  • 什么是标识符

    • 在Python程序中,我们可以给很多东西起名字,比如:
      • 变量的名字
      • 方法的名字
      • 类的名字,等等
    • 这些名字,我们把它统一的称之为标识符,用来做内容的标识。所以,标识符是用户在编程的时候所使用的一系列名字,用于给变量、类、方法等命名。
  • 标识符命名规则。Python中,标识符命名的规则主要有3类:

    • 内容限定

      • 标识符命名中,只允许出现:
        • 英文
        • 中文
        • 数字
        • 下划线(_)
    • 大小写敏感

      • 以定义变量为例:
        • Andy = “安迪1”
        • andy = “安迪2”
      • 字母a的大写和小写,是完全能够区分的。
    • 不可使用关键字

      • Python中有一系列单词,称之为关键字关键字在Python中都有特定用途我们不可以使用它们作为标识符。

1.7 运算符

  • 算术(数学)运算符

  • 赋值运算符

  • 复合赋值运算符

1.8 字符串扩展

1.8.1 字符串的三种定义方式

  • 字符串在Python中有多种定义形式:

    • 单引号定义法
    • 双引号定义法
    • 三引号定义法
      • 和多行注释的写法一样,同样支持换行操作。使用变量接收它,它就是字符串;不使用变量接收它,就可以作为多行注释使用。
  • 字符串的引号嵌套。如果想要定义的字符串本身,是包含:单引号、双引号自身呢?如何写?

    • 单引号定义法,可以内含双引号。
    • 双引号定义法,可以内含单引号。
    • 可以使用转移字符(\)来将引号解除效用,变成普通字符串。

1.8.2 字符串拼接

  • 如果我们有两个字符串(文本)字面量,可以将其拼接成一个字符串,通过+号即可完成;不过一般,单纯的2个字符串字面量进行拼接显得很呆,一般,字面量和变量或变量和变量之间会使用拼接。

  • 既然可以和字符串变量完成拼接,那么,是否可以和其它变量类型如数字类型完成拼接呢?

    • 字符串无法和非字符串变量进行拼接,因为类型不一致。

1.8.3 字符串格式化

  • 可以通过如下语法,完成字符串和变量的快速拼接。

    • %表示:我要占位。
    • s表示:将变量变成字符串放入占位的地方。
  • 多个变量占位变量要用括号括起来,并按照占位的顺序填入。

    • 这里是将数字转换成了字符串,也就是数字57,变成了字符串”57”被放入占位的地方。
  • Python中,其实支持非常多的数据类型占位,最常用的是如下三类:

    • 如下代码,完成字符串、整数、浮点数,三种不同类型变量的占位:

1.8.4 精度控制

  • 我们可以使用辅助符号”m.n”来控制数据的宽度和精度:

    • m,控制宽度,要求是数字(很少使用),设置的宽度小于数字自身,不生效。
    • n,控制小数点精度,要求是数字,会进行小数的四舍五入。
  • 示例:

    • %5d:表示将整数的宽度控制在5位,如数字11,被设置为5d,就会变成:[空格][空格][空格]11,用三个空格补足宽度。
    • %5.2f:表示将宽度控制为5,将小数点精度设置为2.
      • 小数点和小数部分也算入宽度计算。如,对11.345设置了%7.2f后,结果是:[空格][空格]11.35。2个空格补足宽度,小数部分限制2位精度后,四舍五入为.35。
    • %.2f:表示不限制宽度,只设置小数点精度为2,如11.345设置%.2f后,结果是11.35。

1.8.5 字符串格式化方式2

  • 目前通过%符号占位已经很方便了,还能进行精度控制。可是追求效率和优雅的Python,是否有更加优雅的方式解决问题呢?答案是有的,通过语法:f”内容{变量}”的格式来快速格式化。

  • 这种方式:

    • 不理会类型。
    • 不做精度控制。
    • 适合对精度没有要求的时候快速使用。

1.8.6 对表达式进行格式化

  • 表达式:一条具有明确执行结果的代码语句。

    • 1 + 1、5 * 2,就是表达式,因为有具体的结果,结果是一个数字。
    • 又或者,常见的变量定义:name = “张三”或age = 11 + 11。等号右侧的都是表达式呢,因为它们有具体的结果,结果赋值给了等号左侧的变量。
  • 在无需使用变量进行数据存储的时候,可以直接格式化表达式,简化代码:

1.9 input语句(函数)的使用

  • 我们前面学习过print语句(函数),可以完成将内容(字面量、变量等)输出到屏幕上。

  • 在Python中,与之对应的还有一个input语句,用来获取键盘输入。

    • 数据输出:print
    • 数据输入:input
  • 使用上也非常简单:

    • 使用input()语句可以从键盘获取输入。
    • 使用一个变量接收(存储)input语句获取的键盘输入数据即可。
  • 在前面的代码中,输出”请告诉我你是谁?“的print语句其实是多余的:

  • input()语句其实是可以在要求使用者输入内容前,输出提示内容的,方式如下:

  • input语句获取的数据类型:无论键盘输入何种类型的数据,最终的结果都是:字符串类型的数据

2、判断语句

2.1 布尔类型和比较运算符

  • Python中常用的有6种值(数据)的类型:

  • 布尔类型的字面量:

    • True表示真(是、肯定)
    • False表示假 (否、否定)
  • 布尔类型的数据,不仅可以通过定义得到,也可以通过比较运算符进行内容比较得到。

  • 比较运算符:

2.2 if语句的基本格式

  • if判断语句

    • 注意点:

      • 判断语句的结果,必须是布尔类型True或False,True会执行if内的代码语句,False则不会执行。

      • 归属于if判断的代码语句块,需在前方填充4个空格缩进Python通过缩进判断代码块的归属关系。

2.3 if else语句

  • if else语句

    • 注意点:

      • else后,不需要判断条件。
      • 和if的代码块一样,else的代码块同样需要4个空格作为缩进。

2.4 if elif else语句

  • if elif else语句

    • 注意点:

      • elif 语句可以写多个。

      • 判断是互斥且有顺序的。

        • 满足1(如图编号)将不会理会2和3。
        • 满足2,将不会理会3。
        • 1、2、3均不满足,进入else。
        • else也可以省略不写,效果等同3个独立的if判断。
      • 空格缩进同样不可省略。

      • 可以如图,将input输入语句直接写入判断条件中,节省代码量。

2.5 判断语句的嵌套

  • 有很多场景,不仅仅是多个并列条件,还会有满足前置条件才会二次判断的多层判断需求。对这种需求,嵌套判断语句可以实现。

    • 许多逻辑的判断,是嵌套的,多层次的。对于这种需求,我们可以:自由组合if elif else,完成特定需求的要求。
  • 基础语法格式如下:

    • 如上图,第二个if,属于第一个if内,只有第一个if满足条件,才会执行第二个if。
    • 嵌套的关键点,在于:空格缩进。通过空格缩进,来决定语句之间的:层次关系。

3、循环语句

  • 循环普遍存在于日常生活中,同样,在程序中,循环功能也是至关重要的基础功能。

3.1 while循环的基础语法

  • 程序中的循环:

  • while循环注意点:

    • while的条件需得到布尔类型,True表示继续循环,False表示结束循环。
    • 需要设置循环终止的条件,如i += 1配合 i < 100,就能确保100次后停止,否则将无限循环。
    • 空格缩进和if判断一样,都需要设置。

3.2 while循环的嵌套应用

  • 程序中的循环:

  • while循环的嵌套-注意点:
    • 同判断语句的嵌套一样,循环语句的嵌套,要注意空格缩进。基于空格缩进来决定层次关系。
    • 注意条件的设置,避免出现无限循环(除非真的需要无限循环)。

3.3 for循环的基础语法

3.3.1 基础语法

  • 除了while循环语句外,Python同样提供了for循环语句。两者能完成的功能基本差不多,但仍有一些区别:

    • while循环的循环条件是自定义的,自行控制循环条件。
    • for循环是一种”轮询”机制,是对一批内容进行”逐个处理”。
  • 遍历字符串:

    • 可以看出,for循环是将字符串的内容:依次取出所以,for循环也被称之为:遍历循环。
  • for循环注意点:

    • 同while循环不同,for循环是无法定义循环条件的。只能从被处理的数据集中,依次取出内容进行处理。
    • 所以,理论上讲,Python的for循环无法构建无限循环(被处理的数据集不可能无限大)。

3.3.2 range语句

  • 语法中的:待处理数据集,严格来说,称之为:可迭代类型可迭代类型指,其内容可以一个个依次取出的一种类型,包括:

    • 字符串
    • 列表
    • 元组等
  • for循环语句,本质上是遍历:可迭代对象。尽管除字符串外,其它可迭代类型目前没学习到,但不妨碍我们通过学习range语句,获得一个简单的数字序列(可迭代类型的一种)。

    • 语法1:

      • 获取一个从0开始,到num结束的数字序列(不含num本身) 如range(5)取得的数据是:[0, 1, 2, 3, 4]。
    • 语法2:

      • 获得一个从num1开始,到num2结束的数字序列(不含num2本身) 如,range(5, 10)取得的数据是:[5, 6, 7, 8, 9]。
    • 语法3:

      • 获得一个从num1开始,到num2结束的数字序列(不含num2本身) 数字之间的步长,以step为准(step默认为1) 如,range(5, 10, 2)取得的数据是:[5, 7, 9]。
  • for循环遍历range序列:

3.3.3 变量作用域

  • 临时变量,在编程规范上,作用范围(作用域),只限定在for循环内部。

  • 如果在for循环外部访问临时变量:

    • 实际上是可以访问到的。
    • 在编程规范上,是不允许、不建议这么做的。
  • 如果实在需要在循环外访问循环内的临时变量,可以在循环外预先定义:

    • 如图,每一次循环的时候,都会将取出的值赋予i变量。
      • 由于i变量是在循环之前(外)定义的。
      • 在循环外访问i变量是合理的、允许的。

3.4 循环中断 : break和continue

  • 无论是while循环或是for循环,都是重复性的执行特定操作。在这个重复的过程中,会出现一些其它情况让我们不得不:
    • 暂时跳过某次循环,直接进行下一次。
    • 提前退出循环,不再继续。
  • 对于这种场景,Python提供continue和break关键字用以对循环进行临时跳过直接结束

3.4.1 continue关键字

  • continue关键字用于:中断本次循环,直接进入下一次循环。

  • continue可以用于:for循环和while循环,效果一致。

  • continue关键字只可以控制:它所在的循环临时中断。continue只能控制左图编号1的for循环,对编号2的for循环,无影响。

3.4.2 break关键字

  • break关键字用于:直接结束所在循环。

  • break可以用于:for循环和while循环,效果一致。

  • break关键字同样只可以控制:它所在的循环永久中断。break只能控制左图编号1的循环,对编号2的循环,无影响。

4、函数

4.1 函数介绍

  • 函数:是组织好的,可重复使用的,用来实现特定功能的代码段。

4.2 函数的定义

  • 函数的定义:

    • 参数如不需要,可以省略。
    • 返回值如不需要,可以省略。
    • 函数必须先定义后使用。

4.3 函数的参数

  • 传入参数的功能是:在函数进行计算的时候,接受外部(调用时)提供的数据。

  • 有如下代码,完成了2个数字相加的功能:

  • 函数的功能非常局限,只能计算1 + 2。有没有可能实现:每一次使用函数,去计算用户指定的2个数字,而非每次都是1 + 2呢?可以的,使用函数的传入参数功能,即可实现。

  • 基于函数的定义语法:

  • 可以有如下函数定义:

    • 实现了,每次计算的是x + y,而非固定的1 + 2,x + y的值,可以在调用函数的时候指定。
  • 语法解析:

    • 函数定义中,提供的x和y,称之为:形式参数(形参),表示函数声明将要使用2个参数,参数之间使用逗号进行分隔
    • 函数调用中,提供的5和6,称之为:实际参数(实参),表示函数执行时真正使用的参数值,传入的时候,按照顺序传入数据,使用逗号分隔
    • 传入参数的数量是不受限制的。可以不使用参数,也可以仅使用任意N个参数。

4.4 函数的返回值

4.4.1 函数返回值的定义

  • 程序中的返回值:

    • 定义两数相加的函数功能。完成功能后,会将相加的结果返回给函数调用者。所以,变量r接收到了函数的执行结果。
    • 综上所述:所谓“返回值”,就是程序中函数完成事情后,最后给调用者的结果。
  • 语法格式如图:

    • 如图,变量就能接收到函数的返回值。语法就是:通过return关键字,就能向调用者返回数据。

4.4.2 None类型

  • 思考:如果函数没有使用return语句返回数据,那么函数有返回值吗?实际上是:有的。

  • Python中有一个特殊的字面量:None,其类型是:<class ‘NoneType’>,无返回值的函数,实际上就是返回了:None这个字面量。

  • None表示:空的、无实际意义的意思。函数返回的None,就表示,这个函数没有返回什么有意义的内容,也就是返回了空的意思。

  • 演示:

  • None可以主动使用return返回,效果等同于不写return语句:

  • None类型的应用场景,None作为一个特殊的字面量,用于表示:空、无意义,其有非常多的应用场景。

    • 用在函数无返回值上。

    • 用在if判断上。

      • 在if判断中,None等同于False。
      • 一般用于在函数中主动返回None,配合if判断做相关处理。
    • 用于声明无内容的变量上。

      • 定义变量,但暂时不需要变量有具体值,可以用None来代替。

4.5 函数说明文档

  • 函数是纯代码语言,想要理解其含义,就需要一行行的去阅读理解代码,效率比较低。

  • 我们可以给函数添加说明文档,辅助理解函数的作用。语法如下:

    • 通过多行注释的形式,对函数进行说明解释。
    • 内容应写在函数体之前。
  • 在PyCharm中查看函数说明文档:

4.6 函数的嵌套调用

  • 所谓函数嵌套调用指的是一个函数里面又调用了另外一个函数。

  • 如果函数A中,调用了另外一个函数B,那么先把函数B中的任务都执行完毕之后才会回到上次 函数A执行的位置。

4.7 变量的作用域

  • 变量作用域指的是变量的作用范围(变量在哪里可用,在哪里不可用)。主要分为两类:局部变量全局变量

4.7.1 局部变量

  • 所谓局部变量是定义在函数体内部的变量,即只在函数体内部生效。

    • 变量a是定义在testA函数内部的变量,在函数外部访问则立即报错。
  • 局部变量的作用:在函数体内部,临时保存数据,即当函数调用完成后,则销毁局部变量。

4.7.2 全局变量

  • 所谓全局变量,指的是在函数体内、外都能生效的变量。

  • 思考:如果有一个数据,在函数A和函数B中都要使用,该怎么办?可以将这个数据存储在一个全局变量里面。

4.7.3 global关键字

  • 思考:testB函数需要修改变量num的值为200,如何修改程序?

    • testB函数内部的 num = 200 是定义了一个局部变量。
  • 使用global关键字可以在函数内部声明变量为全局变量,如下所示:

5、数据容器

5.1 数据容器入门

  • Python中的数据容器:一种可以容纳多份数据的数据类型,容纳的每一份数据称之为1个元素。每一个元素,可以是任意类型的数据,如字符串、数字、布尔等。
  • 数据容器根据特点的不同,如:
    • 是否支持重复元素。
    • 是否可以修改。
    • 是否有序,等。
  • 分为5类,分别是:列表(list)、元组(tuple)、字符串(str)、集合(set)、字典(dict)。

5.2 数据容器:list(列表)

5.2.1 列表的定义

  • 基本语法:

  • 列表内的每一个数据,称之为元素:

    • 以 [] 作为标识。
    • 列表内每一个元素之间用, 逗号隔开。
  • 案例演示:使用[]的方式定义列表。

    • 嵌套列表的定义:

  • 注意:列表可以一次存储多个数据,且可以为不同的数据类型,支持嵌套。

5.2.2 列表的下标索引

  • 如何从列表中取出特定位置的数据呢?我们可以使用:下标索引。

    • 如图,列表中的每一个元素,都有其位置下标索引,从前向后的方向,从0开始,依次递增。

    • 我们只需要按照下标索引,即可取得对应位置的元素。

  • 或者,可以反向索引,也就是从后向前:从-1开始,依次递减(-1、-2、-3……)。

    • 如图,从后向前,下标索引为:-1、-2、-3,依次递减。

  • 如果列表是嵌套的列表,同样支持下标索引:

    • 如图,下标就有2个层级了。

5.2.3 列表的常用操作

  • 列表除了可以:

    • 定义
    • 使用下标索引获取值
  • 以外,列表也提供了一系列功能:

    • 插入元素
    • 删除元素
    • 清空列表
    • 修改元素
    • 统计元素个数
  • 等等功能,这些功能我们都称之为:列表的方法

  • 列表的查询功能(方法)

    • 在Python中,如果将函数定义为class(类)的成员,那么函数会称之为:方法。

    • 方法和函数功能一样, 有传入参数,有返回值,只是方法的使用格式不同:

      • 函数的使用:

      • 方法的使用:

    • 查找某元素的下标:查找指定元素在列表的下标,如果找不到,报错ValueError。语法:**列表.index(元素)**。index就是列表对象(变量)内置的方法(函数)。

    • 统计列表内,有多少元素。语法:**len(列表)**。可以得到一个int数字,表示列表内的元素数量。

  • 列表的修改功能(方法)

    • 修改特定位置(索引)的元素值。语法:列表[下标] = 值。可以使用如上语法,直接对指定下标(正向、反向下标均可)的值进行:重新赋值(修改)。

    • 插入元素。语法:**列表.insert(下标, 元素)**,在指定的下标位置,插入指定的元素。

    • 追加元素。语法:**列表.append(元素)**,将指定元素,追加到列表的尾部。

    • 追加元素方式2。语法:**列表.extend(其它数据容器)**,将其它数据容器的内容取出,依次追加到列表尾部。

    • 删除元素。语法1: **del 列表[下标]。语法2:列表.pop(下标)**。

    • 删除某元素在列表中的第一个匹配项。语法:**列表.remove(元素)**。

    • 清空列表内容,语法:**列表.clear()**。

    • 统计某元素在列表内的数量。语法:**列表.count(元素)**。

  • 经过上述对列表的学习,可以总结出列表有如下特点:

    • 可以容纳多个元素(上限为2**63-1、9223372036854775807个)。
    • 可以容纳不同类型的元素(混装)。
    • 数据是有序存储的(有下标序号)。
    • 允许重复数据存在。
    • 可以修改(增加或删除元素等)。

5.3 list(列表)的遍历

  • 既然数据容器可以存储多个元素,那么,就会有需求从容器内依次取出元素进行操作。将容器内的元素依次取出进行处理的行为,称之为:遍历、迭代。

5.3.1 while循环

  • 定义一个变量表示下标,从0开始。循环条件为 下标值 < 列表的元素数量。

5.3.2 for循环

  • 除了while循环外,Python中还有另外一种循环形式:for循环。对比while,for循环更加适合对列表等数据容器进行遍历。

    • 表示,从容器内,依次取出元素并赋值到临时变量上。在每一次的循环中,我们可以对临时变量(元素)进行处理。

5.3.3 while循环和for循环的对比

  • 在循环控制上:
    • while循环可以自定循环条件,并自行控制。
    • for循环不可以自定循环条件,只可以一个个从容器内取出数据。
  • 在无限循环上:
    • while循环可以通过条件控制做到无限循环。
    • for循环理论上不可以,因为被遍历的容器容量不是无限的。
  • 在使用场景上:
    • while循环适用于任何想要循环的场景。
    • for循环适用于,遍历数据容器的场景或简单的固定次数循环场景。

5.4 数据容器:tuple(元组)

5.4.1 元组的定义

  • 为什么需要元组:列表是可以修改的,如果想要传递的信息,不被篡改,列表就不合适了。

  • 元组同列表一样,都是可以封装多个、不同类型的元素在内。但最大的不同点在于:元组一旦定义完成,就不可修改。所以,当我们需要在程序内封装数据,又不希望封装的数据被篡改,那么元组就非常合适了。

  • 元组定义:定义元组使用小括号,且使用逗号隔开各个数据,数据可以是不同的数据类型。

    • 注意:元组只有一个数据,这个数据后面要添加逗号。

    • 元组也支持嵌套:

5.4.2 元组的操作

  • 元组由于不可修改的特性,所以其操作方法非常少。

  • 注意事项

    • 不可以修改元组的内容,否则会直接报错。

    • 可以修改元组内的list的内容(修改元素、增加、删除、反转等)。

    • 不可以替换list为其它list或其它类型。

5.4.3 元组的遍历

  • 同列表一样,元组也可以被遍历。可以使用while循环和for循环遍历它。

5.4.4 元组的特点

  • 经过上述对元组的学习,可以总结出列表有如下特点:
    • 可以容纳多个数据。
    • 可以容纳不同类型的数据(混装)。
    • 数据是有序存储的(下标索引)。
    • 允许重复数据存在。
    • 不可以修改(增加或删除元素等)。
    • 支持for循环。
  • 多数特性和list一致,不同点在于不可修改的特性。

5.5 数据容器:str(字符串)

  • 尽管字符串看起来并不像:列表、元组那样,一看就是存放了许多数据的容器。但不可否认的是,字符串同样也是数据容器的一员。字符串是字符的容器,一个字符串可以存放任意数量的字符。

  • 字符串的下标(索引)

    • 和其它容器如:列表、元组一样,字符串也可以通过下标进行访问。

      • 从前向后,下标从0开始。
      • 从后向前,下标从-1开始。
    • 同元组一样,字符串是一个:无法修改的数据容器。所以如下操作均无法完成。如果必须要做,只能得到一个新的字符串,旧的字符串是无法修改。

      • 修改指定下标的字符。如:字符串[0] = “a”。
      • 移除特定下标的字符。如:del 字符串[0]、字符串.remove()、字符串.pop()等。
      • 追加字符等。如:字符串.append()。
  • 字符串的常用操作

    • 查找特定字符串的下标索引值。语法:**字符串.index(字符串)**。

    • 字符串的替换。语法:字符串.replace(字符串1,字符串2)。功能是将字符串内的全部:字符串1,替换为字符串2。注意不是修改字符串本身,而是得到了一个新字符串。

      • 可以看到,字符串name本身并没有发生变化,而是得到了一个新字符串对象。
    • 字符串的分割。语法:字符串.split(分隔符字符串)。功能是按照指定的分隔符字符串,将字符串划分为多个字符串,并存入列表对象中。注意字符串本身不变,而是得到了一个列表对象。

      • 可以看到,字符串按照给定的 <空格>进行了分割,变成多个子字符串,并存入一个列表对象中。
    • 字符串的规整操作(去前后空格)。语法:**字符串.strip()**。

    • 字符串的规整操作(去前后指定字符串)。语法:**字符串.strip(字符串)**。

      • 注意,传入的是“12” 其实就是:”1”和”2”都会移除,是按照单个字符。
    • 统计字符串中某字符串的出现次数。语法:**字符串.count(字符串)**。

    • 统计字符串的长度。语法:**len(字符串)**。

  • 字符串的遍历

    • 同列表、元组一样,字符串也支持while循环和for循环进行遍历。

  • 字符串的特点。作为数据容器,字符串有如下特点:

    • 只可以存储字符串。
    • 长度任意(取决于内存大小)。
    • 支持下标索引。
    • 允许重复字符串存在。
    • 不可以修改(增加或删除元素等)。
    • 支持for循环。
    • 基本和列表、元组相同。
      • 不同与列表和元组的在于:字符串容器可以容纳的类型是单一的,只能是字符串类型。
      • 不同于列表,相同于元组的在于:字符串不可修改。

5.6 数据容器(序列)的切片

  • 序列是指:内容连续、有序,可使用下标索引的一类数据容器。列表、元组、字符串,均可以可以视为序列。

    • 如图,序列的典型特征就是:有序并可用下标索引,字符串、元组、列表均满足这个要求。
  • 序列支持切片,即:列表、元组、字符串,均支持进行切片操作。切片:从一个序列中,取出一个子序列。语法:**序列[起始下标:结束下标:步长]**。表示从序列中,从指定位置开始,依次取出元素,到指定位置结束,得到一个新序列:

    • 起始下标表示从何处开始,可以留空,留空视作从头开始。
    • 结束下标(不含)表示何处结束,可以留空,留空视作截取到结尾。
    • 步长表示,依次取元素的间隔:
      • 步长1表示,一个个取元素。
      • 步长2表示,每次跳过1个元素取。
      • 步长N表示,每次跳过N-1个元素取。
      • 步长为负数表示,反向取(注意,起始下标和结束下标也要反向标记)。
    • 注意,此操作不会影响序列本身,而是会得到一个新的序列(列表、元组、字符串)。
  • 序列的切片演示

    • 可以看到,这个操作对列表、元组、字符串是通用的。同时非常灵活,根据需求,起始位置,结束位置,步长(正反序)都是可以自行控制的

5.7 数据容器:set(集合)

  • 我们目前接触到了列表、元组、字符串三个数据容器了。基本满足大多数的使用场景。为何又需要学习新的集合类型呢?

  • 通过特性来分析:

    • 列表可修改、支持重复元素且有序。
    • 元组、字符串不可修改、支持重复元素且有序。
  • 局限就在于:它们都支持重复元素。如果场景需要对内容做去重处理,列表、元组、字符串就不方便了。而集合,最主要的特点就是:不支持元素的重复(自带去重功能)、并且内容无序。

  • 基本语法:

    • 结果中可见:去重且无序。因为要对元素做去重处理,所以无法保证顺序和创建的时候一致
  • 首先,因为集合是无序的,所以集合不支持:下标索引访问。但是集合和列表一样,是允许修改的,所以我们来看看集合的修改方法。

    • 添加新元素。语法:**集合.add(元素)**。将指定元素,添加到集合内。结果:集合本身被修改,添加了新元素。

    • 移除元素。语法:**集合.remove(元素)**,将指定元素,从集合内移除。结果:集合本身被修改,移除了元素。

    • 从集合中随机取出元素。语法:**集合.pop()**,功能,从集合中随机取出一个元素。结果:会得到一个元素的结果。同时集合本身被修改,元素被移除。

    • 清空集合。语法:**集合.clear()**,功能,清空集合。结果:集合本身被清空。

    • 取出2个集合的差集。语法:**集合1.difference(集合2)**,功能:取出集合1和集合2的差集(集合1有而集合2没有的)。结果:得到一个新集合,集合1和集合2不变。

    • 消除2个集合的差集。语法:**集合1.difference_update(集合2)**,功能:对比集合1和集合2,在集合1内,删除和集合2相同的元素。结果:集合1被修改,集合2不变。

    • 2个集合合并。语法:**集合1.union(集合2)**。功能:将集合1和集合2组合成新集合。结果:得到新集合,集合1和集合2不变。

    • 查看集合的元素数量。语法:**len(集合)**。功能:统计集合内有多少元素。结果:得到一个整数结果。

    • 集合同样支持使用for循环遍历。要注意:集合不支持下标索引,所以也就不支持使用while循环

  • 经过上述对集合的学习,可以总结出集合有如下特点:

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

5.8 数据容器:dict(字典、映射)

5.8.1 字典的定义

  • 字典的定义,同样使用{},不过存储的元素是一个个的:键值对,如下语法:

    • 使用{}存储原始,每一个元素是一个键值对。
    • 每一个键值对包含Key和Value(用冒号分隔)。
    • 键值对之间使用逗号分隔。
    • Key和Value可以是任意类型的数据(key不可为字典)。
    • Key不可重复,重复会对原有数据覆盖。
  • 字典同集合一样,不可以使用下标索引。但是字典可以通过Key值来取得对应的Value。

  • 字典的Key和Value可以是任意数据类型(Key不可为字典)。那么,就表明,字典是可以嵌套的。需求如下:记录学生各科的考试信息。

    优化一下可读性,可以写成:

    嵌套字典的内容获取,如下所示:

5.8.2 字典的常用操作

  • 新增元素。语法:字典[Key] = Value,结果:字典被修改,新增了元素。

  • 更新元素。语法:字典[Key] = Value,结果:字典被修改,元素被更新。注意:字典Key不可以重复,所以对已存在的Key执行上述操作,就是更新Value值。

  • 删除元素。语法:**字典.pop(Key)**,结果:获得指定Key的Value,同时字典被修改,指定Key的数据被删除。

  • 清空字典。语法:**字典.clear()**,结果:字典被修改,元素被清空。

  • 获取全部的key。语法:**字典.keys()**,结果:得到字典中的全部Key。

  • 遍历字典。语法:**for key in 字典.keys()**。注意:字典不支持下标索引,所以同样不可以用while循环遍历。

  • 计算字典内的全部元素(键值对)数量。语法:**len(字典)**。结果:得到一个整数,表示字典内元素(键值对)的数量。

  • 经过上述对字典的学习,可以总结出字典有如下特点:

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

5.9 数据容器对比总结

  • 数据容器可以从以下视角进行简单的分类:

    • 是否支持下标索引
      • 支持:列表、元组、字符串 - 序列类型
      • 不支持:集合、字典 - 非序列类型
    • 是否支持重复元素:
      • 支持:列表、元组、字符串 - 序列类型
      • 不支持:集合、字典 - 非序列类型
    • 是否可以修改
      • 支持:列表、集合、字典
      • 不支持:元组、字符串
  • 数据容器特点对比

5.10 数据容器的通用操作

  • 数据容器尽管各自有各自的特点,但是它们也有通用的一些操作。

    • 首先,在遍历上:
      • 5类数据容器都支持for循环遍历。
      • 列表、元组、字符串支持while循环,集合、字典不支持(无法下标索引)。
    • 尽管遍历的形式各有不同,但是,它们都支持遍历操作。
  • 除了遍历这个共性外,数据容器可以通用非常多的功能方法。

  • 除了下标索引这个共性外,还可以通用类型转换。

    • list(容器):将给定容器转换为列表。
    • tuple(容器):将给定容器转换为元组。
    • str(容器):将给定容器转换为字符串。
    • set(容器):将给定容器转换为集合。
  • 通用排序功能:

    • sorted(容器, [reverse=True]):将给定容器进行排序。注意,排序后都会得到列表(list)对象。

5.11 字符串大小比较

  • 在程序中,字符串所用的所有字符如下,都有其对应的ASCII码表值:

    • 大小写英文单词。
    • 数字。
    • 特殊符号(!、\、|、@、#、空格等)。
  • 每一个字符都能对应上一个:数字的码值。字符串进行比较就是基于数字的码值大小进行比较的。

6、函数进阶

6.1 函数多返回值

  • 如果一个函数要有多个返回值,该如何书写代码?

    • 按照返回值的顺序,写对应顺序的多个变量接收即可。
    • 变量之间用逗号隔开。支持不同类型的数据return。

6.2 函数多种传参方式

  • 函数参数种类。使用方式上的不同,函数有4中常见参数使用方式:
    • 位置参数。
    • 关键字参数。
    • 缺省参数。
    • 不定长参数。

6.2.1 位置参数

  • 调用函数时根据函数定义的参数位置来传递参数。

    • 传递的参数和定义的参数的顺序及个数必须一致。

6.2.2 关键字参数

  • 函数调用时通过“键=值”形式传递参数。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。

    • 函数调用时,如果有位置参数时,位置参数必须在关键字参数的前面,但关键字参数之间不存在先后顺序。

6.2.3 缺省参数

  • 缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)。当调用函数时没有传递参数, 就会使用默认是用缺省参数对应的值。

    • 函数调用时,如果为缺省参数传值则修改默认参数值, 否则使用这个默认值。

6.2.4 不定长参数

  • 不定长参数也叫可变参数. 用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。当调用函数时不确定参数个数时, 可以使用不定长参数。

  • 不定长参数的类型

    • 位置传递:传进的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组(tuple),args是元组类型,这就是位置传递。

    • 关键字传递:参数是“键=值”形式的形式的情况下, 所有的“键=值”都会被kwargs接受, 同时会根据“键=值”组成字典。

6.3 匿名函数

6.3.1 函数作为参数传递

  • 在前面的函数学习中,我们一直使用的函数,都是接受数据作为参数传入:

    • 数字
    • 字符串
    • 字典、列表、元组等
  • 其实,我们学习的函数本身,也可以作为参数传入另一个函数内。如下代码:

    • 函数compute,作为参数,传入了test_func函数中使用。
      • test_func需要一个函数作为参数传入,这个函数需要接收2个数字进行计算,计算逻辑由这个被传入函数决定。
      • compute函数接收2个数字对其进行计算,compute函数作为参数,传递给了test_func函数使用。
      • 最终,在test_func函数内部,由传入的compute函数,完成了对数字的计算操作。
      • 所以,这是一种,计算逻辑的传递,而非数据的传递。就像上述代码那样,不仅仅是相加,相见、相除、等任何逻辑都可以自行定义并作为函数传入。

6.3.2 lambda匿名函数

  • 函数的定义中

    • def关键字,可以定义带有名称的函数。
    • lambda关键字,可以定义匿名函数(无名称)。
  • 有名称的函数,可以基于名称重复使用。无名称的匿名函数,只可临时使用一次。

  • 匿名函数定义语法:

    • lambda 是关键字,表示定义匿名函数。
    • 传入参数表示匿名函数的形式参数,如:x, y 表示接收2个形式参数。
    • 函数体,就是函数的执行逻辑,要注意:只能写一行,无法写多行代码。
  • 如下图代码,我们可以:

    • 通过def关键字,定义一个函数,并传入,如下图:

    • 也可以通过lambda关键字,传入一个一次性使用的lambda匿名函数。

      • 使用def和使用lambda,定义的函数功能完全一致,只是lambda关键字定义的函数是匿名的,无法二次使用。

7、文件操作

7.1 文件的编码

  • 编码技术即:翻译的规则,记录了如何将内容翻译成二进制,以及如何将二进制翻译回可识别内容。
  • 计算机中有许多可用编码,不同的编码,将内容翻译成二进制也是不同的:
    • UTF-8
    • GBK
    • Big5
  • 编码有许多,所以要使用正确的编码, 才能对文件进行正确的读写操作。

7.2 文件的读取

  • 想想我们平常对文件的基本操作,大概可以分为三个步骤(简称文件操作三步走):

    • ① 打开文件
    • ② 读写文件
    • ③ 关闭文件
  • open()打开函数,在Python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件,语法如下:

    • name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)。

    • mode:设置打开文件的模式(访问模式):只读、写入、追加等。mode常用的三种基础访问模式:

    • encoding:编码格式(推荐使用UTF-8)

    注意:此时的fopen函数的文件对象,对象是Python中一种特殊的数据类型,拥有属性和方法,可以使用对象.属性或对象.方法对其进行访问。

  • 读操作相关方法

    • read()方法:**文件对象.read(num)**。num表示要从文件中读取的数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据。

    • readlines()方法:readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素。

    • readline()方法:一次读取一行内容。

    • for循环读取文件行。

    • close()关闭文件对象。

    • with open语法。

7.3 文件的写入

  • 案例演示:

    • 直接调用write,内容并未真正写入文件,而是会积攒在程序的内存中,称之为缓冲区。

    • 当调用flush的时候,内容会真正写入文件。

    • 这样做是避免频繁的操作硬盘,导致效率下降(攒一堆,一次性写磁盘)。

  • 写操作注意

    • 文件如果不存在,使用”w”模式,会创建新文件。
    • 文件如果存在,使用”w”模式,会将原有内容清空。

7.4 文件的追加

  • 案例演示:

    • a模式,文件不存在会创建文件。
    • a模式,文件存在会在最后,追加写入文件。

8、异常、模块与包

8.1 异常的捕获方法

  • 当我们的程序遇到了BUG, 那么接下来有两种情况:

    • ① 整个程序因为一个BUG停止运行。
    • ② 对BUG进行提醒,整个程序继续运行。
  • 显然在之前的学习中, 我们所有的程序遇到BUG就会出现①的这种情况, 也就是整个程序直接奔溃。但是在真实工作中, 我们肯定不能因为一个小的BUG就让整个程序全部奔溃, 也就是我们希望的是达到② 的这种情况。那这里我们就需要使用到捕获异常

  • 捕获异常的作用在于:提前假设某处会出现异常,做好提前准备,当真的出现异常的时候,可以有后续手段。

  • 基本语法:

    • 需求:尝试以r模式打开文件,如果文件不存在,则以w方式打开。

  • 捕获指定异常

    • ① 如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
    • ② 一般try下方只放一行尝试执行的代码。
  • 捕获多个异常,当捕获多个异常时,可以把要捕获的异常类型的名字,放到except 后,并使用元组的方式进行书写。

  • 捕获异常并输出描述信息

  • 捕获所有异常

  • 异常else,else表示的是如果没有异常要执行的代码。

  • 异常的finally,finally表示的是无论是否异常都要执行的代码,例如关闭文件。

8.2 异常的传递

  • 异常是具有传递性的,当函数func01中发生异常, 并且没有捕获处理这个异常的时候, 异常会传递到函数func02, 当func02也没有捕获处理这个异常的时,main函数会捕获这个异常, 这就是异常的传递性。

    • 当所有函数都没有捕获异常的时候, 程序就会报错。
    • 利用异常具有传递性的特点, 当我们想要保证程序不会因为异常崩溃的时候, 就可以在main函数中设置异常捕获, 由于无论在整个程序哪里发生异常, 最终都会传递到main函数中, 这样就可以确保所有的异常都会被捕获。

8.3 Python模块

8.3.1 模块的导入

  • Python 模块(Module),是一个 Python 文件,以 .py 结尾. 模块能定义函数,类和变量,模块里也能包含可执行的代码。

  • 模块的作用:python中有很多各种不同的模块, 每一个模块都可以帮助我们快速的实现一些功能, 比如实现和时间相关的功能就可以使用time模块。我们可以认为一个模块就是一个工具包, 每一个工具包中都有各种不同的工具供我们使用进而实现各种不同的功能。模块就是一个Python文件,里面有类、函数、变量等,我们可以拿过来用(导入模块去使用)。

  • 模块在使用前需要先导入,导入的语法如下:

    • import 模块名。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import 模块名
      import 模块名1,模块名2
      模块名.功能名()

      # 导入时间模块
      import time
      print("开始")
      # 让程序睡眠1秒(阻塞)
      time.sleep(1)
      print("结束")
    • from 模块名 import 类、变量、方法等。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      from 模块名 import 功能名
      功能名()

      # 导入时间模块中的sleep方法
      from time import sleep
      print("开始")
      # 让程序睡眠1秒(阻塞)
      sleep(1)
      print("结束")
    • from 模块名 import *。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      from 模块名 import *
      功能名()

      # 导入时间模块中所有的方法
      from time import *
      print("开始")
      # 让程序睡眠1秒(阻塞)
      sleep(1)
      print("结束")
    • import 模块名 as 别名。

      1
      2
      3
      4
      5
      6
      7
      # 模块定义别名
      import 模块名 as 别名

      # 模块别名
      import time as tt
      tt.sleep(2)
      print('hello')
    • from 模块名 import 功能名 as 别名。

      1
      2
      3
      4
      5
      6
      7
      # 功能定义别名
      from 模块名 import 功能 as 别名

      # 功能别名
      from time import sleep as sl
      sl(2)
      print('hello')

8.3.2 自定义模块

  • Python中已经帮我们实现了很多的模块,不过有时候我们需要一些个性化的模块,这里就可以通过自定义模块实现,也就是自己制作一个模块。

  • 新建一个Python文件,命名为my_module1.py,并定义test函数。

    • 每个Python文件都可以作为一个模块,模块的名字就是文件的名字. 也就是说自定义模块名必须要符合标识符命名规则。
  • 在实际开发中,当一个开发人员编写完一个模块后,为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息,例如,在my_module1.py文件中添加测试代码test(1,1)。

    1
    2
    3
    4
    5
    def test(a, b):
    print(a + b)


    test(1, 1)
    • 此时,无论是当前文件,还是其他已经导入了该模块的文件,在运行的时候都会自动执行test函数的调用。

      1
      2
      3
      4
      5
      6
      def test(a, b):
      print(a + b)

      # 只在当前文件中调用该函数,其他导入的文件内不符合该条件,则不执行test函数调用
      if __name__ == '__main__':
      test (1, 1)
  • 注意事项:

    • 当导入多个模块的时候,且模块内有同名功能. 当调用这个同名功能的时候,调用到的是后面导入的模块的功能。
  • 如果一个模块文件中有__all__变量,当使用from xxx import *导入时,只能导入这个列表中的元素。

8.4 Python包

8.4.1 自定义包

  • 基于Python模块,我们可以在编写代码的时候,导入许多外部代码来丰富功能。但是,如果Python的模块太多了,就可能造成一定的混乱,那么如何管理呢?通过Python包的功能来管理。

  • 从物理上看,包就是一个文件夹,在该文件夹下包含了一个 init.py 文件,该文件夹可用于包含多个模块文件。从逻辑上看,包的本质依然是模块。

  • 包的作用:当我们的模块文件越来越多时,包可以帮助我们管理这些模块, 包的作用就是包含多个模块,但包的本质依然是模块。

  • 步骤如下

    • ① 新建包my_package

    • ② 新建包内模块:my_module1my_module2

    • ③ 模块内代码如下:

  • 导入包

    • 方式一:

      1
      2
      3
      import 包名.模块名

      包名.模块名.目标
    • 方式二:

      1
      2
      3
      4
      # 注意:必须在`__init__.py`文件中添加`__all__ = []`,控制允许导入的模块列表
      from 包名 import *

      模块名.目标

8.4.2 安装第三方包

  • 我们知道,包可以包含一堆的Python模块,而每个模块又内含许多的功能。所以,我们可以认为:一个包,就是一堆同类型功能的集合体。

  • 在Python程序的生态中,有许多非常多的第三方包(非Python官方),可以极大的帮助我们提高开发效率,如:

    • 科学计算中常用的:numpy包。
    • 数据分析中常用的:pandas包。
    • 大数据计算中常用的:pyspark、apache-flink包。
    • 图形可视化常用的:matplotlib、pyecharts。
    • 人工智能常用的:tensorflow。
  • 这些第三方的包,极大的丰富了Python的生态,提高了开发效率。但是由于是第三方,所以Python没有内置,所以我们需要安装它们才可以导入使用哦。

  • 第三方包的安装非常简单,我们只需要使用Python内置的pip程序即可。打开我们许久未见的:命令提示符程序,在里面输入:pip install 包名称。即可通过网络快速安装第三方包。

  • 由于pip是连接的国外的网站进行包的下载,所以有的时候会速度很慢。我们可以通过如下命令,让其连接国内的网站进行包的安装:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名称。(https://pypi.tuna.tsinghua.edu.cn/simple 是清华大学提供的一个网站,可供pip程序下载第三方包)

  • PyCharm也提供了安装第三方包的功能:

9、面向对象

9.1 初识对象

  • 在程序中是可以做到和生活中那样,设计表格、生产表格、填写表格的组织形式的。

    • 在程序中设计表格,我们称之为:设计类(class)。

    • 在程序中打印生产表格,我们称之为:创建对象。

    • 在程序中填写表格,我们称之为:对象属性赋值。

9.2 成员方法

  • 在上一节中,我们简单了解到可以使用类去封装属性,并基于类创建出一个个的对象来使用。现在我们来看看类的使用语法:

    image-20240330133939594
    • class是关键字,表示要定义类了。
    • 类的属性,即定义在类中的变量(成员变量)。
    • 类的行为,即定义在类中的函数(成员方法)。
  • 创建类对象的语法:

  • 那么,什么是类的行为(方法)呢?

  • 可以看出,类中:

    • 不仅可以定义属性用来记录数据。
    • 也可以定义函数,用来记录行为。
  • 其中:

    • 类中定义的属性(变量),我们称之为:成员变量。
    • 类中定义的行为(函数),我们称之为:成员。
  • 在类中定义成员方法和定义函数基本一致,但仍有细微区别:

    • 可以看到,在方法定义的参数列表中,有一个:self关键字。

    • self关键字是成员方法定义的时候,必须填写的。

      • 它用来表示类对象自身的意思。

      • 当我们使用类对象调用方法的是,self会自动被python传入。

      • 在方法内部,想要访问类的成员变量,必须使用self。

    • self关键字,尽管在参数列表中,但是传参的时候可以忽略它。

      • 可以看到,在传入参数的时候,self是透明的,可以不用理会它。

9.3 类和对象

  • 基于类创建对象的语法:

  • 基于类创建对象

9.4 构造方法

  • 属性(成员变量)的赋值

    • 上面代码中,为对象的属性赋值需要依次进行,略显繁琐。有没有更加高效的方式,能够一行代码就完成呢?可以,需要使用构造方法:_init_()。
  • Python类可以使用:_init_()方法,称之为构造方法。可以实现:

    • 在创建类对象(构造类)的时候,会自动执行。
    • 在创建类对象(构造类)的时候,将传入参数自动传递给__init__方法使用。
  • 构造方法注意事项

    • 千万不要忘记init前后都有2个下划线。

    • 构造方法也是成员方法,不要忘记在参数列表中提供:self。

    • 在构造方法内定义成员变量,需要使用self关键字。

      • 这是因为:变量是定义在构造方法内部,如果要成为成员变量,需要用self来表示。

9.5 其它内置方法

  • 上文学习的__init__构造方法,是Python类内置的方法之一。这些内置的类方法,各自有各自特殊的功能,这些内置方法我们称之为:魔术方法。

    • _str_ 字符串方法

      • 当类对象需要被转换为字符串之时,会输出如上结果(内存地址)。内存地址没有多大作用,我们可以通过__str__方法,控制类转换为字符串的行为。

    • _lt_ 小于符号比较方法

      • 直接对2个对象进行比较是不可以的,但是在类中实现__lt__方法,即可同时完成:小于符号 和 大于符号 2种比较。

    • _le_ 小于等于比较符号方法

    • _eq_ 比较运算符实现方法

      • 不实现__eq__方法,对象之间可以比较,但是是比较内存地址,也即是:不同对象==比较一定是False结果。
      • 实现了__eq__方法,就可以按照自己的想法来决定2个对象是否相等了。

9.6 封装

  • 面向对象编程,是许多编程语言都支持的一种编程思想。简单理解是:基于模板(类)去创建实体(对象),使用对象完成功能开发。

  • 面向对象包含3大主要特性:

    • 封装
    • 继承
    • 多态
  • 封装表示的是,将现实世界事物的:

    • 属性
    • 行为
  • 封装到类中,描述为:

    • 成员变量
    • 成员方法
  • 从而完成程序对现实世界事物的描述。

  • 现实世界中的事物,有属性和行为。但是不代表这些属性和行为都是开放给用户使用的。

  • 既然现实事物有不公开的属性和行为,那么作为现实事物在程序中映射的类,也应该支持。类中提供了私有成员的形式来支持。

    • 私有成员变量。
    • 私有成员方法。
  • 定义私有成员的方式非常简单,只需要:

    • 私有成员变量:变量名以__开头(2个下划线)。
    • 私有成员方法:方法名以__开头(2个下划线)。
  • 私有方法无法直接被类对象使用。

  • 私有变量无法赋值,也无法获取值。

  • 私有成员无法被类对象使用,但是可以被其它的成员使用。

9.7 继承

9.7.1 继承的基础语法

  • 继承分为:单继承和多继承。

    • 使用如图语法,可以完成类的单继承。继承表示:将从父类那里继承(复制)来成员变量和成员方法(不含私有)。

    • Python的类之间也支持多继承,即一个类,可以继承多个父类。

      • 多个父类中,如果有同名的成员,那么默认以继承顺序(从左到右)为优先级。即:先继承的保留,后继承的被覆盖。

9.7.2 复写和使用父类成员

  • 子类继承父类的成员属性和成员方法后,如果对其“不满意”,那么可以进行复写。即:在子类中重新定义同名的属性或方法即可。

  • 一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员。如果需要使用被复写的父类的成员,需要特殊的调用方式:

    • 方式1:调用父类成员。
      • 使用成员变量:父类名.成员变量。
      • 使用成员方法:父类名.成员方法(self)。
    • 方式2:使用super()调用父类成员。
      • 使用成员变量:super().成员变量。
      • 使用成员方法:super().成员方法()。

    只能在子类内调用父类的同名成员。子类的类对象直接调用会调用子类复写的成员。

9.8 类型注解

9.8.1 变量的类型注解

  • Python在3.5版本的时候引入了类型注解,以方便静态类型检查工具,IDE等第三方工具。类型注解:在代码中涉及数据交互的地方,提供数据类型的注解(显式的说明)。主要功能:

    • 帮助第三方IDE工具(如PyCharm)对代码进行类型推断,协助做代码提示。
    • 帮助开发者自身对变量进行类型注释。
  • 支持:

    • 变量的类型注解。
    • 函数(方法)形参列表和返回值的类型注解。
  • 为变量设置类型注解,基础语法: 变量: 类型。

  • 注意:

    • 元组类型设置类型详细注解,需要将每一个元素都标记出来。
    • 字典类型设置类型详细注解,需要2个类型,第一个是key第二个是value。
  • 除了使用 变量: 类型, 这种语法做注解外,也可以在注释中进行类型注解。语法:type: 类型。

  • 为变量设置注解,显示的变量定义,一般无需注解:

    • 如图,就算不写注解,也明确的知晓变量的类型。
  • 一般,无法直接看出变量类型之时,会添加变量的类型注解。

  • 类型注解主要功能在于:

    • 帮助第三方IDE工具(如PyCharm)对代码进行类型推断,协助做代码提示。
    • 帮助开发者自身对变量进行类型注释(备注)。
  • 并不会真正的对类型做验证和判断。也就是,类型注解仅仅是提示性的,不是决定性的。如图代码,是不会报错的。

9.8.2 函数(方法)的类型注解

  • 如图所示:

    • 在编写函数(方法),使用形参data的时候,工具没有任何提示。
    • 在调用函数(方法),传入参数的时候,工具无法提示参数类型。
    • 这些都是因为,我们在定义函数(方法)的时候,没有给形参进行注解。
  • 函数和方法的形参类型注解语法:

  • 同时,函数(方法)的返回值也是可以添加类型注解的。语法如下:

9.8.3 Union类型

  • Union联合类型注解,在变量注解、函数(方法)形参和返回值注解中,均可使用。需要先导包再使用:from typing import Union。

9.9 多态

  • 多态,指的是:多种状态,即完成某个行为时,使用不同的对象会得到不同的状态。同样的行为(函数),传入不同的对象,得到不同的状态

  • 细心的同学可能发现了,父类Animal的speak方法,是空实现:

  • 抽象类就好比定义一个标准,包含了一些抽象的方法,要求子类必须实现。