@IT·互联网python呆鸟的Python数据分析

看完这篇文章,你的Python基础就差不多了

2018-09-09  本文已影响66人  柳厌之

前言

本文是基于黑马程序员2018年的Python基础班的内容编写的,以2018年的资料为蓝本,2016年的资料为补充,还参考了一些网上的教程,旨在更好地总结Python基础知识,力求简明扼要,以供实战演练时能够快速查询遗忘的知识点。学一门语言贵在坚持用它,不用就淡忘了,而记录下一篇文章也有助于日后快速回忆。全文分为两大部分,分别是Python基础语法和面向对象。

第一部分 Python基础语法

第一部分 Python基础语法

1. 认识Python

1.1 Python 简介

Python 的创始人为吉多·范罗苏姆(Guido van Rossum)。

Python 的设计目标:

  • 一门简单直观的语言并与主要竞争者一样强大
  • 开源,以便任何人都可以为它做贡献
  • 代码像纯英语那样容易理解
  • 适用于短期开发的日常任务

Python 的设计哲学:

  • 优雅、明确、简单

Python 开发者的哲学是:用一种方法,最好是只有一种方法来做一件事

Python 是完全面向对象的语言,在 Python 中一切皆对象。

可扩展性:如果需要一段关键代码运行得更快或者希望某些算法不公开,可以把这部分程序用 CC++ 编写,然后在 Python 程序中使用它们。

1.2. 第一个Python程序

执行 Python 程序的三种方式:
解释器、交互式运行、IDE运行

Python 是一个格式非常严格的程序设计语言。
python 2.x 默认不支持中文

  • ASCII 字符只包含 256 个字符,不支持中文

为了照顾现有的程序,官方提供了一个过渡版本 —— Python 2.6

提示:如果开发时,无法立即使用 Python 3.0(还有极少的第三方库不支持 3.0 的语法),建议

  • 先使用 Python 3.0 版本进行开发
  • 然后使用 Python 2.6Python 2.7 来执行,并且做一些兼容性的处理

IPython 是一个 python 的 交互式 shell,比默认的 python shell 好用得多,它支持 bash shell 命令,适合于学习/验证 Python 语法或者局部代码。

集成开发环境(IDE,Integrated Development Environment)—— 集成了开发软件需要的所有工具,一般包括以下工具:

PyCharm 是 Python 的一款非常优秀的集成开发环境 PyCharm运行工具栏

1.3. PyCharm 的设置

PyCharm 的 配置信息 是保存在 用户家目录下.PyCharmxxxx.x 目录下的,xxxx.x 表示当前使用的 PyCharm 的版本号

1.3.1 恢复 PyCharm 的初始设置:
$ rm -r ~/.PyCharm2016.3
1.3.2 PyCharm 安装和启动步骤:
$ tar -zxvf pycharm-professional-2017.1.3.tar.gz

/opt 目录用户存放给主机额外安装的软件

$ sudo mv pycharm-2017.1.3/ /opt/
$ cd /opt/pycharm-2017.1.3/bin
$ ./pycharm.sh
1.3.3 设置启动图标

ubuntu 中,应用程序启动的快捷方式通常都保存在 /usr/share/applications 目录下

1.3.4 卸载之前版本的 PyCharm

要卸载 PyCharm 只需要做以下两步工作:

$ sudo rm -r /opt/pycharm-2016.3.1/
$ rm -r ~/.PyCharm2016.3/

如果不再使用 PyCharm 还需要将 /usr/share/applications/ 下的 jetbrains-pycharm.desktop 删掉

1.4. 多文件项目的演练

让选中的程序可以执行

2. 注释

2.1 单行注释(行注释)

print("hello python")  # 输出 `hello python`

为了保证代码的可读性,# 后面建议先添加一个空格,然后再编写相应的说明文字;为了保证代码的可读性,注释和代码之间 至少要有 两个空格。

2.2 多行注释(块注释)

"""
这是一个多行注释

在多行注释之间,可以写很多很多的内容……
""" 
print("hello python")

提示:

  1. 注释不是越多越好,对于一目了然的代码,不需要添加注释
  2. 对于 复杂的操作,应该在操作开始前写上若干行注释
  3. 对于 不是一目了然的代码,应在其行尾添加注释(为了提高可读性,注释应该至少离开代码 2 个空格)
  4. 绝不要描述代码,假设阅读代码的人比你更懂 Python,他只是不知道你的代码要做什么

2.3 代码规范:

3. 运算符

3.1 算数运算符

是完成基本的算术运算使用的符号,用来处理四则运算,而“+”和“*”还可以用来处理字符串。

运算符 描述 实例
+ 10 + 20 = 30
- 10 - 20 = -10
* 10 * 20 = 200
/ 10 / 20 = 0.5
// 取整除 返回除法的整数部分(商) 9 // 2 输出结果 4
% 取余数 返回除法的余数 9 % 2 = 1
** 又称次方、乘方,2 ** 3 = 8

3.2 比较(关系)运算符

运算符 描述
== 检查两个操作数的值是否 相等,如果是,则条件成立,返回 True
!= 检查两个操作数的值是否 不相等,如果是,则条件成立,返回 True
> 检查左操作数的值是否 大于 右操作数的值,如果是,则条件成立,返回 True
< 检查左操作数的值是否 小于 右操作数的值,如果是,则条件成立,返回 True
>= 检查左操作数的值是否 大于或等于 右操作数的值,如果是,则条件成立,返回 True
<= 检查左操作数的值是否 小于或等于 右操作数的值,如果是,则条件成立,返回 True
  • Python 2.x 中判断 不等于 还可以使用 <> 运算符
  • != 在 Python 2.x 中同样可以用来判断 不等于

3.3 赋值运算符

运算符 描述 实例
= 简单的赋值运算符 c = a + b 将 a + b 的运算结果赋值为 c
+= 加法赋值运算符 c += a 等效于 c = c + a
-= 减法赋值运算符 c -= a 等效于 c = c - a
*= 乘法赋值运算符 c *= a 等效于 c = c * a
/= 除法赋值运算符 c /= a 等效于 c = c / a
//= 取整除赋值运算符 c //= a 等效于 c = c // a
%= (余数)赋值运算符 c %= a 等效于 c = c % a
**= 幂赋值运算符 c **= a 等效于 c = c ** a

3.4 身份运算符

身份运算符比较两个对象的内存位置。常用的有两个身份运算符,如下所述:

运算符 描述 示例
is 判断两个标识符是不是引用同一个对象 x is y,类似 id(x) == id(y)
is not 判断两个标识符是不是引用不同对象 x is not y,类似 id(a) != id(b)

辨析

3.5 成员运算符

Python成员运算符测试给定值是否为序列中的成员。 有两个成员运算符,如下所述:

运算符 描述
in 如果在指定的序列中找到一个变量的值,则返回true,否则返回false。
not in 如果在指定序列中找不到变量的值,则返回true,否则返回false。

3.6 逻辑运算符

运算符 逻辑表达式 描述
and x and y 只有 x 和 y 的值都为 True,才会返回 True<br />否则只要 x 或者 y 有一个值为 False,就返回 False
or x or y 只要 x 或者 y 有一个值为 True,就返回 True<br />只有 x 和 y 的值都为 False,才会返回 False
not not x 如果 x 为 True,返回 False<br />如果 x 为 False,返回 True

3.7 运算符优先级

运算符 描述
** 幂 (最高优先级)
* / % // 乘、除、取余数、取整除
+ - 加法、减法
<= < > >= 比较运算符
== != 等于运算符
= %= /= //= -= += *= **= 赋值运算符
is is not 身份运算符
in not in 成员运算符
not or and 逻辑运算符

<补>程序执行原理

Python程序执行示意图
  1. 操作系统会首先让 CPUPython 解释器 的程序复制到 内存
  2. Python 解释器 根据语法规则,从上向下CPU 翻译 Python 程序中的代码
  3. CPU 负责执行翻译完成的代码

Python 的解释器有多大?

# 1. 确认解释器所在位置
$ which python

# 2. 查看 python 文件大小(只是一个软链接)
$ ls -lh /usr/bin/python

# 3. 查看具体文件大小
$ ls -lh /usr/bin/python2.7

4. 变量

4.1 变量定义

变量名 = 值

使用交互式方式,如果要查看变量内容,直接输入变量名即可,不需要使用 print 函数
使用解释器执行,如果要输出变量的内容,必须要要使用 print 函数

4.2 变量的类型

提示:在 Python 2.x 中,整数 根据保存数值的长度还分为:

  • int(整数)
  • long(长整数)
In [1]: type(name)
<补>不同类型变量之间的计算
  1. 数字型变量 之间可以直接计算
  1. 字符串变量 之间使用 + 拼接字符串
  2. 字符串变量 可以和 整数 使用 * 重复拼接相同的字符串
  3. 数字型变量字符串 之间 不能进行其他计算
<补>从键盘获取输入信息:input
字符串变量 = input("提示信息:")
<补>类型转换函数
函数 说明
int(x) 将 x 转换为一个整数
float(x) 将 x 转换到一个浮点数
str(x) 将对象x转换为字符串表示形式
tuple(s) 将s转换为元组
list(s) 将s转换为列表
price = float(input("请输入价格:"))
<补>格式化输出:print
格式化字符 含义
%s 字符串
%d 有符号十进制整数,%06d 表示输出的整数显示位数,不足的地方使用 0 补全
%f 浮点数,%.2f 表示小数点后只显示两位
%% 输出 %
print("格式化字符串" % 变量1)

print("格式化字符串" % (变量1, 变量2...))

4.3 变量的命名

4.3.1 标识符和关键字

标示符就是程序员定义的 变量名函数名

  • 标示符可以由 字母下划线数字 组成
  • 不能以数字开头
  • 不能与关键字重名

关键字 就是在 Python 内部已经使用的标识符

  • 关键字 具有特殊的功能和含义
  • 开发者 不允许定义和关键字相同的名字的标识符
  • 通过以下命令可以查看 Python 中的关键字
In [1]: import keyword
In [2]: print(keyword.kwlist)
4.3.2 变量的命名规则

命名规则 可以被视为一种 惯例,并无绝对与强制
目的是为了 增加代码的识别和可读性
注意 Python 中的 标识符区分大小写的

  1. 在定义变量时,为了保证代码格式,= 的左右应该各保留一个空格
  2. 在 Python 中,如果 变量名 需要由 二个 或 多个单词 组成时,可以按照以下方式命名:每个单词都使用小写字母,单词与单词之间使用 _下划线 连接,例如:first_namelast_nameqq_numberqq_password

当然,还有驼峰命名法:
小驼峰式命名法:第一个单词以小写字母开始,后续单词的首字母大写,例如:firstNamelastName
大驼峰式命名法,每一个单词的首字母都采用大写字母,例如:FirstNameLastNameCamelCase

4.4 高级变量类型

Python 中,所有 非数字型变量 都支持以下特点:
1. 都是一个 序列 sequence,也可以理解为 容器
2. 取值 []
3. 遍历 for in
4. 计算长度len最大/最小值max/min比较删除del
5. 链接 +重复 *
6. 切片

4.4.1 列表(list)

索引 就是数据在 列表 中的位置编号,索引 又可以被称为 下标
注意:从列表中取值时,如果 超出索引范围,程序会报错

name_list = ["zhangsan", "lisi", "wangwu"]
列表示意图
<补>del 关键字
In [1]: l = [1,2,3,4]
In [2]: del l[1]
In [3]: l[1]
Out[3]: 3

在日常开发中,要从列表删除数据,建议 使用列表提供的方法

<补>函数与方法
函数名(参数)

函数需要死记硬背

对象.方法名(参数)

在变量后面输入 .,然后选择针对这个变量要执行的操作,记忆起来比函数要简单很多

<补>循环遍历
# for 循环内部使用的变量 in 列表
for name in name_list:
    循环内部针对列表元素进行操作
    print(name)
for-in循环流程图

4.4.2 元组(tuple)

info_tuple = ("zhangsan", 18, 1.75)

创建空元组:

info_tuple = ()

元组中 只包含一个元素 时,需要 在元素后面添加逗号

info_tuple = (50, )
元组示意图
  • 在 Python 中,可以使用 for 循环遍历所有非数字型类型的变量:列表元组字典 以及 字符串
  • 提示:在实际开发中,除非 能够确认元组中的数据类型,否则针对元组的循环遍历需求并不是很多
  • 在开发中,更多的应用场景是:
    • 函数的 参数 和 返回值,一个函数可以接收 任意多个参数,或者 一次返回多个数据
    • 格式字符串,格式化字符串后面的 () 本质上就是一个元组
    • 让列表不可以被修改,以保护数据安全
<补>元组和列表之间的转换
list(元组) 
tuple(列表)

4.4.3 字典(dict)

dict(字典) 是 除列表以外 Python 之中 最灵活 的数据类型。
字典同样可以用来 存储多个数据,通常用于存储 描述一个 物体 的相关信息

xiaoming = {"name": "小明",
            "age": 18,
            "gender": True,
            "height": 1.75}
字典示意图
# for 循环内部使用的 `key 的变量` in 字典
for k in xiaoming:

    print("%s: %s" % (k, xiaoming[k]))

提示:在实际开发中,由于字典中每一个键值对保存数据的类型是不同的,所以针对字典的循环遍历需求并不是很多

card_list = [{"name": "张三",
              "qq": "12345",
              "phone": "110"},
             {"name": "李四",
              "qq": "54321",
              "phone": "10086"}
             ]

4.4.4 字符串(str)

大多数编程语言都是用 " 来定义字符串

string = "Hello Python"

for c in string:
    print(c)

字符串示意图

提示:在 python 中对字符串操作,内置提供的方法足够多,使得在开发时,能够针对字符串进行更加灵活的操作!应对更多的开发需求!

1) 判断类型 - 9
方法 说明
string.isspace() 如果 string 中只包含空格,则返回 True
string.isalnum() 如果 string 至少有一个字符并且所有字符都是字母或数字则返回 True
string.isalpha() 如果 string 至少有一个字符并且所有字符都是字母则返回 True
string.isdecimal() 如果 string 只包含数字则返回 True,全角数字
string.isdigit() 如果 string 只包含数字则返回 True,全角数字\u00b2
string.isnumeric() 如果 string 只包含数字则返回 True,全角数字汉字数字
string.istitle() 如果 string 是标题化的(每个单词的首字母大写)则返回 True
string.islower() 如果 string 中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True
string.isupper() 如果 string 中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回 True
2) 查找和替换 - 7
方法 说明
string.startswith(str) 检查字符串是否是以 str 开头,是则返回 True
string.endswith(str) 检查字符串是否是以 str 结束,是则返回 True
string.find(str, start=0, end=len(string)) 检测 str 是否包含在 string 中,如果 start 和 end 指定范围,则检查是否包含在指定范围内,如果是返回开始的索引值,否则返回 -1
string.rfind(str, start=0, end=len(string)) 类似于 find(),不过是从右边开始查找
string.index(str, start=0, end=len(string)) 跟 find() 方法类似,不过如果 str 不在 string 会报错
string.rindex(str, start=0, end=len(string)) 类似于 index(),不过是从右边开始
string.replace(old_str, new_str, num=string.count(old)) 把 string 中的 old_str 替换成 new_str,如果 num 指定,则替换不超过 num 次
3) 大小写转换 - 5
方法 说明
string.capitalize() 把字符串的第一个字符大写
string.title() 把字符串的每个单词首字母大写
string.lower() 转换 string 中所有大写字符为小写
string.upper() 转换 string 中的小写字母为大写
string.swapcase() 翻转 string 中的大小写
4) 文本对齐 - 3
方法 说明
string.ljust(width) 返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串
string.rjust(width) 返回一个原字符串右对齐,并使用空格填充至长度 width 的新字符串
string.center(width) 返回一个原字符串居中,并使用空格填充至长度 width 的新字符串
5) 去除空白字符 - 3
方法 说明
string.lstrip() 截掉 string 左边(开始)的空白字符
string.rstrip() 截掉 string 右边(末尾)的空白字符
string.strip() 截掉 string 左右两边的空白字符
6) 拆分和连接 - 5
方法 说明
string.partition(str) 把字符串 string 分成一个 3 元素的元组 (str前面, str, str后面)
string.rpartition(str) 类似于 partition() 方法,不过是从右边开始查找
string.split(str="", num) 以 str 为分隔符拆分 string,如果 num 有指定值,则仅分隔 num + 1 个子字符串,str 默认包含 '\r', '\t', '\n' 和空格
string.splitlines() 按照行('\r', '\n', '\r\n')分隔,返回一个包含各行作为元素的列表
string.join(seq) 以 string 作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串
<补>字符串的切片
字符串索引示意图
字符串[开始索引:结束索引:步长]

注意

  1. 指定的区间属于 左闭右开[开始索引, 结束索引) => 开始索引 <= 范围 < 结束索引
    • 起始 位开始,到 结束位的前一位 结束(不包含结束位本身)
  2. 从头开始,开始索引 数字可以省略,冒号不能省略
  3. 到末尾结束,结束索引 数字可以省略,冒号不能省略
  4. 步长默认为 1,如果连续切片,数字和冒号都可以省略
<补>索引的顺序和倒序
num_str = "0123456789"

# 1. 截取从 2 ~ 5 位置 的字符串
print(num_str[2:6])

# 2. 截取从 2 ~ `末尾` 的字符串
print(num_str[2:])

# 3. 截取从 `开始` ~ 5 位置 的字符串
print(num_str[:6])

# 4. 截取完整的字符串
print(num_str[:])

# 5. 从开始位置,每隔一个字符截取字符串
print(num_str[::2])

# 6. 从索引 1 开始,每隔一个取一个
print(num_str[1::2])

# 倒序切片
# -1 表示倒数第一个字符
print(num_str[-1])

# 7. 截取从 2 ~ `末尾 - 1` 的字符串
print(num_str[2:-1])

# 8. 截取字符串末尾两个字符
print(num_str[-2:])

# 9. 字符串的逆序(面试题)
print(num_str[::-1])

4.4.5 公共方法和变量的高级应用

4.4.5.1 内置函数

Python 包含了以下内置函数:

函数 描述 备注
len(item) 计算容器中元素个数
del(item) 删除变量 del 有两种方式
max(item) 返回容器中元素最大值 如果是字典,只针对 key 比较
min(item) 返回容器中元素最小值 如果是字典,只针对 key 比较
cmp(item1, item2) 比较两个值,-1 小于 / 0 相等 / 1 大于 Python 3.x 取消了 cmp 函数

注意字符串 比较符合以下规则: "0" < "A" < "a"。

4.4.5.2 切片
描述 Python 表达式 结果 支持的数据类型
切片 "0123456789"[::-2] "97531" 字符串、列表、元组
4.4.5.3 运算符
运算符 Python 表达式 结果 描述 支持的数据类型
+ [1, 2] + [3, 4] [1, 2, 3, 4] 合并 字符串、列表、元组
* ["Hi!"] * 4 ['Hi!', 'Hi!', 'Hi!', 'Hi!'] 重复 字符串、列表、元组
in 3 in (1, 2, 3) True 元素是否存在 字符串、列表、元组、字典
not in 4 not in (1, 2, 3) True 元素是否不存在 字符串、列表、元组、字典
> >= == < <= (1, 2, 3) < (2, 2, 3) True 元素比较 字符串、列表、元组

注意

4.4.5.4 内置函数完整的 for 循环语法
for 变量 in 集合:
    
    循环体代码
else:
    没有通过 break 退出循环,循环结束后,会执行的代码

应用场景:

4.4.5.5 变量的引用
  • 变量 和 数据 都是保存在 内存 中的
  • 在 Python 中 函数 的 参数传递 以及 返回值 都是靠 引用 传递的

在 Python 中:变量数据 是分开存储的,数据 保存在内存中的一个位置,变量 中保存着数据在内存中的地址,就叫做 引用,使用 id() 函数可以查看变量中保存数据所在的 内存地址

注意:如果变量已经被定义,当给一个变量赋值的时候,本质上是 修改了数据的引用

  • 变量 不再 对之前的数据引用
  • 变量 改为 对新赋值的数据引用

在 Python 中,变量的名字类似于 便签纸 贴在 数据 上:

代码 图示
a = 1
代码 图示
a = 2
代码 图示
b = a

Python 中,函数的 实参/返回值 都是是靠 引用 来传递来的

def test(num):

    print("-" * 50)
    print("%d 在函数内的内存地址是 %x" % (num, id(num)))

    result = 100

    print("返回值 %d 在内存中的地址是 %x" % (result, id(result)))
    print("-" * 50)

    return  result

a = 10
print("调用函数前 内存地址是 %x" % id(a))

r = test(a)

print("调用函数后 实参内存地址是 %x" % id(a))
print("调用函数后 返回值内存地址是 %x" % id(r))

结果
4.4.5.6 可变和不可变类型

注意:字典的 key 只能使用不可变类型的数据

注意

  1. 可变类型的数据变化,是通过 方法 来实现的
  2. 如果给一个可变类型的变量,赋值了一个新的数据,引用会修改
    • 变量 不再 对之前的数据引用
    • 变量 改为 对新赋值的数据引用
<补>哈希 (hash)
4.4.5.7 局部变量和全局变量

提示:在其他的开发语言中,大多 不推荐使用全局变量 —— 可变范围太大,导致程序不好维护!

注意:函数执行时,需要处理变量时 会:

  1. 首先 查找 函数内部 是否存在 指定名称 的局部变量,如果有,直接使用
  2. 如果没有,查找 函数外部 是否存在 指定名称 的全局变量,如果有,直接使用
  3. 如果还没有,程序报错!

注意:函数不能直接修改全局变量的引用,如果要修改,必须要用global声明该变量是全局变量。

num = 10


def demo1():

    print("demo1" + "-" * 50)

    # global 关键字,告诉 Python 解释器 num 是一个全局变量
    global num
    # 只是定义了一个局部变量,不会修改到全局变量,只是变量名相同而已
    num = 100
    print(num)


def demo2():

    print("demo2" + "-" * 50)
    print(num)

demo1()
demo2()

print("over")

注意:为了避免局部变量和全局变量出现混淆,在定义全局变量时,有些公司会有一些开发要求,例如:全局变量名前应该增加 g_ 或者 gl_ 的前缀。

5. 判断(分支)

5.1 if 语句语法

1、if 判断语句基本语法:

if 要判断的条件:
    条件成立时,要做的事情
    ……

注意:代码的缩进为一个 tab 键,或者 4 个空格 —— 建议使用空格

  • 在 Python 开发中,Tab 和空格不要混用!

2、如果需要在 不满足条件的时候,做某些事情,该如何做呢?

if 要判断的条件:
    条件成立时,要做的事情
    ……
else:
    条件不成立时,要做的事情
    ……
条件1 and 条件2 :两个条件同时满足,返回 True
条件1 or 条件2:两个条件只要有一个满足,返回 True
not 条件:非,不是

3、如果希望 再增加一些条件条件不同,需要执行的代码也不同 时,就可以使用 elif

if 条件1:
    条件1满足执行的代码
    ……
elif 条件2:
    条件2满足时,执行的代码
    ……
elif 条件3:
    条件3满足时,执行的代码
    ……
else:
    以上条件都不满足时,执行的代码
    ……

注意

4、在开发中,使用 if 进行条件判断,如果希望 在条件成立的执行语句中增加条件判断,就可以使用 if 的嵌套

if 条件 1:
    条件 1 满足执行的代码
    ……
    
    if 条件 1 基础上的条件 2:
        条件 2 满足时,执行的代码
        ……    
        
    # 条件 2 不满足的处理
    else:
        条件 2 不满足时,执行的代码
        
# 条件 1 不满足的处理
else:
    条件1 不满足时,执行的代码
    ……

5.2 if语句的应用

5.2.1 随机数的处理
import random
random.randint(12, 20)  # 生成的随机数n: 12 <= n <= 20   
random.randint(20, 20)  # 结果永远是 20   
random.randint(20, 10)  # 该语句是错误的,下限必须小于上限
5.2.2 石头剪刀布
# 导入随机工具包
# 注意:在导入工具包的时候,应该将导入的语句,放在文件的顶部
# 因为,这样可以方便下方的代码,在任何需要的时候,使用工具包中的工具
import random

# 从控制台输入要出的拳 —— 石头(1)/剪刀(2)/布(3)
player = int(input("请输入您要出的拳 石头(1)/剪刀(2)/布(3):"))

# 电脑 随机 出拳 —— 先假定电脑只会出石头,完成整体代码功能
computer = random.randint(1, 3)

print("玩家选择的拳头是 %d - 电脑出的拳是 %d" % (player, computer))

# 比较胜负
# 1 石头 胜 剪刀
# 2 剪刀 胜 布
# 3 布 胜 石头
# if (()
#        or ()
#        or ()):
if ((player == 1 and computer == 2)
        or (player == 2 and computer == 3)
        or (player == 3 and computer == 1)):

    print("欧耶,电脑弱爆了!")
# 平局
elif player == computer:
    print("真是心有灵犀啊,再来一盘")
# 其他的情况就是电脑获胜
else:
    print("不服气,我们决战到天明!")

6. 循环

6.1 程序执行的三大流程

在程序开发中,一共有三种流程方式:

程序执行的三大流程

6.2 while 循环基本使用

while 语句基本语法:

初始条件设置 —— 通常是重复执行的 计数器

while 条件(判断 计数器 是否达到 目标次数):
    条件满足时,做的事情1
    条件满足时,做的事情2
    条件满足时,做的事情3
    ...(省略)...
    
    处理条件(计数器 + 1)

由于程序员的原因,忘记 在循环内部 修改循环的判断条件,导致循环持续执行,程序将陷入死循环而无法终止!

计数器 +1 :可以通过赋值运算符简化代码的编写。
常见的计数方法有两种,可以分别称为:

因此,大家在编写程序时,应该尽量养成习惯:除非需求的特殊要求,否则 循环 的计数都从 0 开始

6.3 break 和 continue

breakcontinue专门在循环中使用的关键字

6.4 while 循环嵌套

while 条件 1:
    条件满足时,做的事情1
    条件满足时,做的事情2
    条件满足时,做的事情3
    ...(省略)...
    
    while 条件 2:
        条件满足时,做的事情1
        条件满足时,做的事情2
        条件满足时,做的事情3
        ...(省略)...
    
        处理条件 2
    
    处理条件 1

示例:

"""
打印 9 行小星星:
*
**
***
****
*****
******
*******
********
*********
"""

# 定义起始行
row = 1

# 最大打印 9 行
while row <= 9:
    # 定义起始列
    col = 1

    # 最大打印 row 列
    while col <= row:

        # end = "",表示输出结束后,不换行
        # "\t" 可以在控制台输出一个制表符,协助在输出文本时对齐
        print("%d * %d = %d" % (col, row, row * col), end="\t")

        # 列数 + 1
        col += 1

    # 一行打印完成的换行
    print("")

    # 行数 + 1
    row += 1

<补>字符串中的转义字符

制表符 的功能是在不使用表格的情况下在 垂直方向 按列对齐文本

转义字符 描述
\\ 反斜杠符号
\' 单引号
\" 双引号
\n 换行
\t 横向制表符
\r 回车

7. 函数

7.1 函数的基本使用

所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用。在开发程序时,使用函数可以提高编写的效率以及代码的 重用,函数的使用包含两个步骤:
1. 定义函数 —— 封装 独立的功能
2. 调用函数 —— 享受 封装 的成果

def 函数名():

    函数封装的代码
    ……

PyCharm 的调试工具:

  • F8 Step Over 可以单步执行代码,会把函数调用看作是一行代码直接执行
  • F7 Step Into 可以单步执行代码,如果是函数,会进入函数内部

注意:因为 函数体相对比较独立函数定义的上方,应该和其他代码(包括注释)保留 两个空行

7.2 函数的参数

7.2.1 形参和实参

def sum_2_num(num1, num2):

    result = num1 + num2
    
    print("%d + %d = %d" % (num1, num2, result))

sum_2_num(50, 20)

7.2.2 可变和不可变参数

问题 1:在函数内部,针对参数使用 赋值语句,会不会影响调用函数时传递的 实参变量? —— 不会!

问题 2:如果传递的参数是 可变类型,在函数内部,使用 方法 修改了数据的内容,同样会影响到外部的数据,例如列表变量调用 += 本质上是在执行列表变量的 extend 方法。

7.2.2 缺省参数

定义函数时,可以给 某个参数 指定一个默认值,具有默认值的参数就叫做 缺省参数,* 调用函数时,如果没有传入 缺省参数 的值,则在函数内部使用定义函数时指定的 参数默认值将常见的值设置为参数的缺省值,从而 简化函数的调用。例如:对列表排序的方法:

gl_num_list = [6, 3, 9]

# 默认就是升序排序,因为这种应用需求更多
gl_num_list.sort()
print(gl_num_list)

# 只有当需要降序排序时,才需要传递 `reverse` 参数
gl_num_list.sort(reverse=True)
print(gl_num_list)
def print_info(name, gender=True):

    gender_text = "男生"
    if not gender:
        gender_text = "女生"

    print("%s 是 %s" % (name, gender_text))

提示

  1. 缺省参数,需要使用 最常见的值 作为默认值!
  2. 如果一个参数的值 不能确定,则不应该设置默认值,具体的数值在调用函数时,由外界传递!

注意

  1. 必须保证 带有默认值的缺省参数 在参数列表末尾
  2. 调用函数时,如果有 多个参数需要指定参数名,这样解释器才能够知道参数的对应关系!

7.2.3 多值参数

有时可能需要 一个函数 能够处理的参数 个数 是不确定的,这个时候,就可以使用 多值参数

def demo(num, *args, **kwargs):

    print(num)
    print(args)
    print(kwargs)


demo(1, 2, 3, 4, 5, name="小明", age=18, gender=True)
print("-"*20)
demo(1,(2,3,4,5),{"name":"小明", "age":18, "gender":True})
print("-"*20)
demo(1,(2,3,4,5), name="小明", age=18, gender=True)

结果
<补> 元组和字典的拆包
def demo(*args, **kwargs):

    print(args)
    print(kwargs)

# 需要将一个元组变量/字典变量传递给函数对应的参数
gl_nums = (1, 2, 3)
gl_xiaoming = {"name": "小明", "age": 18}

# 会把 num_tuple 和 xiaoming 作为元组传递个 args
# demo(gl_nums, gl_xiaoming)
demo(*gl_nums, **gl_xiaoming)

7.3 函数的返回值

注意:return 表示返回,后续的代码都不会被执行

def sum_2_num(num1, num2):
    """对两个数字的求和"""

    return num1 + num2

# 调用函数,并使用 result 变量接收计算结果
result = sum_2_num(10, 20)

print("计算结果是 %d" % result)

函数参数和返回值

技巧

# Python 专有,利用元组交换两个变量的值
a, b = b, a

7.4 函数的嵌套调用

def test1():

    print("*" * 50)
    print("test 1")
    print("*" * 50)


def test2():

    print("-" * 50)
    print("test 2")
    
    test1()
    
    print("-" * 50)

test2()

提示:工作中针对需求的变化,应该冷静思考,不要轻易修改之前已经完成的,能够正常执行的函数

7.5 函数的递归

函数调用自身的 编程技巧 称为递归

特点一个函数 内部 调用自己

代码特点

  1. 函数内部的 代码 是相同的,只是针对 参数 不同,处理的结果不同
  2. 参数满足一个条件 时,函数不再执行,通常被称为递归的出口,否则 会出现死循环

案例 —— 计算数字累加

需求:

  1. 定义一个函数 sum_numbers
  2. 能够接收一个 num 的整数参数
  3. 计算 1 + 2 + ... num 的结果
def sum_numbers(num):

    if num == 1:
        return 1
    
    # 假设 sum_numbers 能够完成 num - 1 的累加
    temp = sum_numbers(num - 1)

    # 函数内部的核心算法就是 两个数字的相加
    return num + temp

print(sum_numbers(2))

7.6 在模块中定义函数

模块是 Python 程序架构的一个核心概念

模块 就好比是 工具包,要想使用这个工具包中的工具,就需要 导入 import 这个模块, 每一个以扩展名 py 结尾的 Python 源代码文件都是一个 模块,在模块中定义的 全局变量函数 都是模块能够提供给外界直接使用的工具。

模块可以让 曾经编写过的代码 方便的被 复用
模块名也是一个标识符,如果在给 Python 文件起名时,以数字开头 是无法在 PyCharm 中通过导入这个模块的。

<补> Pyc 文件

Ccompiled 编译过 的意思。

Python 这样保存 字节码 是作为一种启动 速度的优化

8. 文件、异常、模块和包

8.1 文件

8.1.1 文件的概念

文件的作用:将数据长期保存下来,在需要的时候使用
文件的存储方式:在计算机中,文件是以 二进制 的方式保存在磁盘上的
文本文件:可以使用 文本编辑软件 查看,本质上还是二进制文件
二进制文件:保存的内容 不是给人直接阅读的,而是 提供给其他软件使用的,例如:图片文件、音频文件、视频文件等等,二进制文件不能使用 文本编辑软件 查看

8.1.2 文件的基本操作

操作文件的套路
计算机 中要操作文件的套路非常固定,一共包含三个步骤

  1. 打开文件
  2. 读、写文件
    • 将文件内容读入内存
    • 将内存内容写入文件
  3. 关闭文件

操作文件的函数/方法
Python 中要操作文件需要记住 1 个函数和 3 个方法

序号 函数/方法 说明
01 open 打开文件,并且返回文件操作对象
02 read 将文件内容读取到内存
03 write 将指定内容写入文件
04 close 关闭文件

open 函数负责打开文件,并且返回文件对象
read/write/close 三个方法都需要通过 文件对象 来调用

读取文件示例

open 函数的第一个参数是要打开的文件名(文件名区分大小写)
如果文件 存在,返回 文件操作对象
如果文件 不存在,会 抛出异常

read 方法可以一次性 读入返回 文件的 所有内容

close 方法负责 关闭文件
如果 忘记关闭文件会造成系统资源消耗,而且会影响到后续对文件的访问

注意read 方法执行后,会把 文件指针 移动到 文件的末尾

# 1\. 打开 - 文件名需要注意大小写
file = open("README")

# 2\. 读取
text = file.read()
print(text)

# 3\. 关闭
file.close()

提示

<补> 文件指针

思考:如果执行了一次 read 方法,读取了所有内容,那么再次调用 read 方法,还能够获得到内容吗?
答案:不能!第一次读取之后,文件指针移动到了文件末尾,再次调用不会读取到任何的内容。

<补> 打开文件的方式
f = open("文件名", "访问方式")
访问方式 说明
r 只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常
w 只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件
a 追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入
r+ 读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常
w+ 读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件
a+ 读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入

提示:频繁的移动文件指针,会影响文件的读写效率,开发中更多的时候会以 只读只写 的方式来操作文件

写入文件示例

# 打开文件
f = open("README", "w")

f.write("hello python!\n")
f.write("今天天气真好")

# 关闭文件
f.close()

<补> 按行读取文件内容:readline()

读取大文件的正确姿势

# 打开文件
file = open("README")

while True:
    # 读取一行内容
    text = file.readline()

    # 判断是否读到内容
    if not text:
        break

    # 每读取一行的末尾已经有了一个 `\n`
    print(text, end="")

# 关闭文件
file.close()

复制大文件

# 1\. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")

# 2\. 读取并写入文件
while True:
    # 每次读取一行
    text = file_read.readline()

    # 判断是否读取到内容
    if not text:
        break

    file_write.write(text)

# 3\. 关闭文件
file_read.close()
file_write.close()

8.1.3 文件/目录的常用管理操作

文件管理操作

序号 方法名 说明 示例
01 rename 重命名文件 os.rename(源文件名, 目标文件名)
02 remove 删除文件 os.remove(文件名)

目录管理操作

序号 方法名 说明 示例
01 listdir 目录列表 os.listdir(目录名)
02 mkdir 创建目录 os.mkdir(目录名)
03 rmdir 删除目录 os.rmdir(目录名)
04 getcwd 获取当前目录 os.getcwd()
05 chdir 修改工作目录 os.chdir(目标目录)
06 path.isdir 判断是否是文件 os.path.isdir(文件路径)

提示:文件或者目录操作都支持 相对路径绝对路径

8.1.4 文本文件的编码格式

Python 2.x 默认使用 ASCII 编码格式
Python 3.x 默认使用 UTF-8 编码格式

ASCII 编码

UTF-8 编码格式

Ptyhon 2.x 中如何使用中文?

Python 2.x 中如何正确遍历 unicode 字符串?

# *-* coding:utf8 *-*

# 在字符串前,增加一个 `u` 表示这个字符串是一个 utf8 字符串
hello_str = u"你好世界"

print(hello_str)

for c in hello_str:
    print(c)

8.2 异常

8.2.1 异常的概念

程序开发时,很难将 所有的特殊情况 都处理的面面俱到,通过 异常捕获 可以针对突发事件做集中的处理,从而保证程序的 稳定性和健壮性

8.2.2 捕获异常:try except else finally

简单的捕获异常语法

try:
    尝试执行的代码
except:
    出现错误的处理

简单异常捕获1 —— 要求用户输入整数

try:
    # 提示用户输入一个数字
    num = int(input("请输入数字:"))
except:
    print("请输入正确的数字")

错误类型捕获

try:
    # 尝试执行的代码
    pass
except 错误类型1:
    # 针对错误类型1,对应的代码处理
    pass
except (错误类型2, 错误类型3):
    # 针对错误类型2 和 3,对应的代码处理
    pass
except Exception as result:
    print("未知错误 %s" % result)

异常类型捕获2 —— 要求用户输入整数

try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除 0 错误")

捕获未知错误

语法如下:

except Exception as result:
    print("未知错误 %s" % result)

异常捕获完整语法

try:
    # 尝试执行的代码
    pass
except 错误类型1:
    # 针对错误类型1,对应的代码处理
    pass
except 错误类型2:
    # 针对错误类型2,对应的代码处理
    pass
except (错误类型3, 错误类型4):
    # 针对错误类型3 和 4,对应的代码处理
    pass
except Exception as result:
    # 打印错误信息
    print(result)
else:
    # 没有异常才会执行的代码
    pass
finally:
    # 无论是否有异常,都会执行的代码
    print("无论是否有异常,都会执行的代码")

try:
    num = int(input("请输入整数:"))
    result = 8 / num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除 0 错误")
except Exception as result:
    print("未知错误 %s" % result)
else:
    print("正常执行")
finally:
    print("执行完成,但是不保证正确")

8.2.3 异常的传递

提示:

  • 在开发中,可以在主函数中增加 异常捕获,而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中
  • 这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁
'''
需求:
1.  定义函数 `demo1()` **提示用户输入一个整数并且返回**
2.  定义函数 `demo2()` 调用 `demo1()`
3.  在主程序中调用 `demo2()`
'''
def demo1():
    return int(input("请输入一个整数:"))

def demo2():
    return demo1()

try:
    print(demo2())
except ValueError:
    print("请输入正确的整数")
except Exception as result:
    print("未知错误 %s" % result)

8.2.4 抛出异常: raise

应用场景

示例

抛出异常

'''
**需求**

*   定义 `input_password` 函数,提示用户输入密码
*   如果用户输入长度 < 8,抛出异常
*   如果用户输入长度 >=8,返回输入的密码
'''
def input_password():

    # 1\. 提示用户输入密码
    pwd = input("请输入密码:")

    # 2\. 判断密码长度,如果长度 >= 8,返回用户输入的密码
    if len(pwd) >= 8:
        return pwd

    # 3\. 密码长度不够,需要抛出异常
    # 1> 创建异常对象 - 使用异常的错误信息字符串作为参数
    ex = Exception("密码长度不够")

    # 2> 抛出异常对象
    raise ex

try:
    user_pwd = input_password()
    print(user_pwd)
except Exception as result:
    print("发现错误:%s" % result)

8.3 模块和包

8.3.1 模块

模块是 Python 程序架构的一个核心概念

模块的两种导入方式

1)import 导入

import 模块名1, 模块名2 

提示:在导入模块时,每个导入应该独占一行

import 模块名1
import 模块名2 

如果模块的名字太长,可以使用 as 指定模块的名称,以方便在代码中的使用

import 模块名1 as 模块别名

注意:模块别名 应该符合 大驼峰命名法

2)from...import 导入

# 从 模块 导入 某一个工具
from 模块名1 import 工具名

注意

如果 两个模块,存在 同名的函数,那么 后导入模块的函数,会 覆盖掉先导入的函数

# 从 模块 导入 所有工具
from 模块名1 import *

注意

这种方式不推荐使用,因为函数重名并没有任何的提示,出现问题不好排查

<补> 模块的搜索顺序

Python 的解释器在 导入模块 时,会:

  1. 搜索 当前目录 指定模块名的文件,如果有就直接导入
  2. 如果没有,再搜索 系统目录

在开发时,给文件起名,不要和 系统的模块文件 重名

Python 中每一个模块都有一个内置属性 __file__ 可以 查看模块完整路径

原则 —— 每一个文件都应该是可以被导入的

实际开发场景

__name__ 属性

在很多 Python 文件中都会看到以下格式的代码

# 导入模块
# 定义全局变量
# 定义类
# 定义函数

# 在代码的最下方
def main():
    # ...
    pass

# 根据 __name__ 判断是否执行下方代码
if __name__ == "__main__":
    main()

8.3.2 包(Package)

好处:使用 import 包名 可以一次性导入 所有的模块

案例

  1. 新建一个 hm_message
  2. 在目录下,新建两个文件 send_messagereceive_message
  3. send_message 文件中定义一个 send 函数
  4. receive_message 文件中定义一个 receive 函数
  5. 在外部直接导入 hm_message 的包

__init__.py

# 从 当前目录 导入 模块列表
from . import send_message
from . import receive_message

8.3.3 发布模块

  1. 创建 setup.py
from distutils.core import setup

setup(name="hm_message",  # 包名
      version="1.0",  # 版本
      description="itheima's 发送和接收消息模块",  # 描述信息
      long_description="完整的发送和接收消息模块",  # 完整描述信息
      author="itheima",  # 作者
      author_email="itheima@itheima.com",  # 作者邮箱
      url="www.itheima.com",  # 主页
      py_modules=["hm_message.send_message",
                  "hm_message.receive_message"])

有关字典参数的详细信息,可以参阅官方网站:https://docs.python.org/2/distutils/apiref.html

  1. 构建模块
$ python3 setup.py build
  1. 生成发布压缩包
$ python3 setup.py sdist

注意:要制作哪个版本的模块,就使用哪个版本的解释器执行!

4)安装模块

$ tar -zxvf hm_message-1.0.tar.gz 
$ sudo python3 setup.py install

5)卸载模块

直接从安装目录下,把安装模块的 目录 删除就可以

$ cd /usr/local/lib/python3.5/dist-packages/
$ sudo rm -r hm_message*

pip 安装第三方模块

安装和卸载命令如下:

# 将模块安装到 Python 2.x 环境
$ sudo pip install pygame
$ sudo pip uninstall pygame

# 将模块安装到 Python 3.x 环境
$ sudo pip3 install pygame
$ sudo pip3 uninstall pygame

第二部分 面向对象

第二部分 面向对象

1. 面向对象(OOP)基本概念

面向对象编程 —— Object Oriented Programming 简写 OOP

2. 面向对象基础语法

2.1 dir 内置函数和内置方法

在 Python 中 对象几乎是无所不在的,我们之前学习的 变量、数据、函数 都是对象。
在 Python 中可以使用以下两个方法验证:

序号 方法名 类型 作用
01 __new__ 方法 创建对象时,会被 自动 调用
02 __init__ 方法 对象被初始化时,会被 自动 调用
03 __del__ 方法 对象被从内存中销毁前,会被 自动 调用
04 __str__ 方法 返回对象的描述信息,print 函数输出使用

提示 利用好 dir() 函数,在学习时很多内容就不需要死记硬背了。

2.2 定义简单的类(只包含方法)

面向对象是更大的封装,在 一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!

定义一个只包含方法的类:

class 类名:

    def 方法1(self, 参数列表):
        pass
    
    def 方法2(self, 参数列表):
        pass

方法 的定义格式和之前学习过的函数几乎一样,区别在于第一个参数必须是 self
注意类名 的 命名规则 要符合 大驼峰命名法
当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:

对象变量 = 类名()

在面向对象开发中,引用的概念是同样适用的!

使用 print输出 对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)。

提示:在计算机中,通常使用 十六进制 表示 内存地址。

如果在开发中,希望使用 print输出 对象变量 时,能够打印 自定义的内容,就可以利用 __str__这个内置方法了:

class Cat:

    def __init__(self, new_name):

        self.name = new_name

        print("%s 来了" % self.name)

    def __del__(self):

        print("%s 去了" % self.name)

    def __str__(self):
        return "我是小猫:%s" % self.name

tom = Cat("Tom")
print(tom)

注意:__str__方法必须返回一个字符串。

2.3 方法中的 self 参数

在 Python 中,要 给对象设置属性,非常的容易,只需要在 类的外部的代码 中直接通过 对象.设置一个属性即可,但是不推荐使用:

class Cat:
    """这是一个猫类"""

    def eat(self):
        print("小猫爱吃鱼")

    def drink(self):
        print("小猫在喝水")

tom = Cat()
# 给对象设置属性
tom.name = "Tom"

因为:对象属性的封装应该封装在类的内部

由哪一个对象调用的方法,方法内的 self就是哪一个对象的引用

2.4 初始化方法:__init__

__init__ 方法是 专门 用来定义一个类具有哪些属性的方法!

class Cat:

    def __init__(self, name):
        print("初始化方法 %s" % name)
        self.name = name
    ...
    
tom = Cat("Tom")
...

lazy_cat = Cat("大懒猫")
...

2.5 私有属性和私有方法

应用场景

定义方式

私有属性和私有方法

伪私有属性和私有方法
Python 中,并没有 真正意义 的 私有
在给 属性、方法 命名时,实际是对名称做了一些特殊处理,使得外界无法访问到
处理方式:在 名称 前面加上_类名 => _类名__名称

# 私有属性,外部不能直接访问到
print(xiaofang._Women__age)

# 私有方法,外部不能直接调用
xiaofang._Women__secret()

提示:在日常开发中,不要使用这种方式,访问对象的 私有属性 或 私有方法。

3. 封装、继承和多态

面向对象三大特性:

  1. 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
  2. 继承 实现代码的重用,相同的代码不需要重复的编写
  3. 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度

3.1 继承

3.1.1 单继承

继承的概念:子类 拥有 父类 以及 父类的父类 中封装的所有 属性 和 方法。

class 类名(父类名):

    pass

当 父类 的方法实现不能满足子类需求时,可以对方法进行重写(override)
重写 父类方法有两种情况:

  1. 覆盖 父类的方法:父类的方法实现 和 子类的方法实现完全不同
    具体的实现方式,就相当于在 子类中 定义了一个 和父类同名的方法并且实现。
  2. 对父类方法进行 扩展:子类的方法实现 中 包含 父类的方法实现
    在子类中 重写 父类的方法;在需要的位置使用 super().父类方法 来调用父类方法的执行代码;其他的位置针对子类的需求,编写 子类特有的代码实现。
关于 super

调用父类方法的另外一种方式:在 Python 2.x 时,如果需要调用父类的方法,还可以使用以下方式:父类名.方法(self)。目前在 Python 3.x 还支持这种方式,但不推荐使用,因为一旦 父类发生变化,方法调用位置的 类名 同样需要修改。

父类的 私有属性 和 私有方法

子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法
子类对象 可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法

  • 私有属性、方法 是对象的隐私,不对外公开,外界 以及 子类 都不能直接访问
  • 私有属性、方法 通常用于做一些内部的事情

3.1.2 多继承

子类 可以拥有 多个父类,并且具有 所有父类 的 属性 和 方法,例如:孩子 会继承自己 父亲 和 母亲 的 特性。

class 子类名(父类名1, 父类名2...):
    pass
Python 中的 MRO算法(Method Resolution Order)

MRO 是 method resolution order —— 方法搜索顺序,主要用于 在多继承时判断 方法、属性 的调用 路径

新式类与旧式(经典)类

在 Python 3.x 中定义类时,如果没有指定父类,会 默认使用 object作为该类的 基类 —— Python 3.x 中定义的类都是 新式类,在 Python 2.x 中定义类时,如果没有指定父类,则不会以 object 作为 基类。

class 类名(object):
    pass

object 是 Python 为所有对象提供的 基类,提供有一些内置的属性和方法,可以使用 dir(object) 函数查看。

3.2 多态

面向对象三大特性:

  1. 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
    • 定义类的准则
  2. 继承 实现代码的重用,相同的代码不需要重复的编写
    • 设计类的技巧
    • 子类针对自己特有的需求,编写特定的代码
  3. 多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
    • 增加代码的灵活度
    • 以 继承 和 重写父类方法 为前提
    • 调用方法的技巧,不会影响到类的内部设计

多态 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!

案例
在 Dog 类中封装方法 game:普通狗只是简单的玩耍
定义 XiaoTianDog 继承自 Dog,并且重写 game 方法:哮天犬需要在天上玩耍
定义 Person 类,并且封装一个 和狗玩 的方法:在方法内部,直接让 狗对象 调用 game 方法

多态示例
Person 类中只需要让 狗对象 调用 game 方法,而不关心具体是 什么狗。

4. 类属性和类方法

4.1 类的结构

通常会把:
创建出来的 对象 叫做 类的实例
创建对象的 动作 叫做 实例化
对象的属性 叫做 实例属性
对象调用的方法 叫做 实例方法
每一个对象 都有自己独立的内存空间,保存各自不同的属性
多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部

各个不同的属性,独一份的方法

在 Python 中,类是一个特殊的对象。

Python 中 一切皆对象:

  • class AAA: 定义的类属于 类对象
  • obj1 = AAA() 属于 实例对象

在程序运行时,类同样会被加载到内存
在程序运行时,类对象在内存中只有一份,使用 一个类可以创建出很多个对象实例
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法——类属性、类方法,通过 类名. 的方式可以 访问类的属性 或者 调用类的方法

类的结构

4.2 类属性和实例属性

类属性 就是 类对象中定义的属性
通常用来记录与这个类相关的特征
类属性不会用于记录具体对象的特征
示例
定义一个 工具类,每件工具都有自己的 name
需求 —— 知道使用这个类,创建了多少个工具对象

class Tool(object):

    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

    def __init__(self, name):
        self.name = name

        # 针对类属性做一个计数+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 知道使用 Tool 类到底创建了多少个对象?
print("现在创建了 %d 个工具" % Tool.count)
属性的获取机制

在 Python 中 属性的获取 存在一个 向上查找机制

因此,要访问类属性有两种方式:

4.3 类方法和静态方法

4.3.1 类方法

语法如下

@classmethod
def 类方法名(cls):
    pass

示例

@classmethod
def show_tool_count(cls):
    """显示工具对象的总数"""
    print("工具对象的总数 %d" % cls.count)

4.3.2 静态方法

语法如下

@staticmethod
def 静态方法名():
    pass

示例

class Game(object):

    # 游戏最高分,类属性
    top_score = 0

    @staticmethod
    def show_help():
        print("帮助信息:让僵尸走进房间")
        
    @classmethod
    def show_top_score(cls):
        print("游戏最高分是 %d" % cls.top_score)

    def __init__(self, player_name):
        self.player_name = player_name

    def start_game(self):
        print("[%s] 开始游戏..." % self.player_name)
        
        # 使用类名.修改历史最高分
        Game.top_score = 999

# 1. 查看游戏帮助
Game.show_help()

# 2. 查看游戏最高分
Game.show_top_score()

# 3. 创建游戏对象,开始游戏
game = Game("小明")

game.start_game()

# 4. 游戏结束,查看游戏最高分
Game.show_top_score()

探索

5. 单例

5.1 单例设计模式

5.2 静态方法: __new__

重写 __new__ 方法 的代码非常固定!

class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        # 如果不返回任何结果,就不会调用对象的初始化方法
        return super().__new__(cls)

    def __init__(self):
        print("初始化音乐播放对象")

player = MusicPlayer()

print(player)

5.3 Python 中的单例

class MusicPlayer(object):

    # 定义类属性记录单例对象引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1\. 判断类属性是否已经被赋值
        if cls.instance is None:
            cls.instance = super().__new__(cls)

        # 2\. 返回类属性的单例引用
        return cls.instance

只执行一次初始化工作

需求

解决办法

  1. 定义一个类属性 init_flag 标记是否 执行过初始化动作,初始值为 False
  2. __init__ 方法中,判断 init_flag,如果为 False 就执行初始化动作
  3. 然后将 init_flag 设置为 True
  4. 这样,再次 自动 调用 __init__ 方法时,初始化动作就不会被再次执行
class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    instance = None
    # 记录是否执行过初始化动作
    init_flag = False

    def __new__(cls, *args, **kwargs):

        # 1\. 判断类属性是否是空对象
        if cls.instance is None:
            # 2\. 调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)

        # 3\. 返回类属性保存的对象引用
        return cls.instance

    def __init__(self):

        if not MusicPlayer.init_flag:
            print("初始化音乐播放器")

            MusicPlayer.init_flag = True

# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

Tips

1、Python 能够自动的将一对括号内部的代码连接在一起:

        return ("户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
                % (self.house_type, self.area,
                   self.free_area, self.item_list))

2、一个对象的 属性 可以是 另外一个类创建的对象。
3、在__init__方法中定义类的属性时,如果 不知道设置什么初始值,可以设置为 None):None 关键字 表示 什么都没有,表示一个 空对象,没有方法和属性,是一个特殊的常量。可以将 None 赋值给任何一个变量

在 Python 中针对 None 比较时,建议使用is 判断

4、eval() 函数十分强大 —— 将字符串 当成 有效的表达式 来求值 并 返回计算结果

# 基本的数学计算
In [1]: eval("1 + 1")
Out[1]: 2

# 字符串重复
In [2]: eval("'*' * 10")
Out[2]: '**********'

# 将字符串转换成列表
In [3]: type(eval("[1, 2, 3, 4, 5]"))
Out[3]: list

# 将字符串转换成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict

在开发时千万不要使用 eval 直接转换 input 的结果,举个例子:

__import__('os').system('ls')

# 等价代码

import os

os.system("终端命令")



参考教程:
1.【易百python教程】https://www.yiibai.com/python

2018.9.9 完

上一篇 下一篇

猜你喜欢

热点阅读