学习笔记|Python

优雅、明确、简单,人生苦短,我用 python。

零、背景

01编译器

1.1区分

编译器:将程序翻译成机器语言的工具。

解释器:当编译器以解释方式运行时,也称之为解释器

1.2翻译方式

编译器翻译的方式有两种:(区别:翻译的时间点不同)

  1. 编译:编译编译型语言

  2. 解释:解释解释型语n

1.3对比

  • 速度:编译型语言执行速度更快。
  • 跨平台型:解释型语言跨平台型更好。

02设计目标

1999年,吉多·范罗苏姆提出了Python的目标:

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

03设计哲学

  1. 优雅

  2. 明确

  3. 简单

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

  • 如果面临多种选择,Python开发者一般拒绝花哨的语法,而选择明确没有或者很少有歧义的语法。

在Python社区,吉多被称为“仁慈的独裁者”

04特点

  • 完全面向对象:在Python中一切皆对象。
  • 强大的标准库
  • 大量的第三方模块

一、执行程序

01执行方式

1.1 脚本式

python/python3

1
>>> python HelloPython.py

1.2 交互式

IDLE 中 shell

  • 优点:适合学习和验证语法或局部代码
  • exit()/ ctrl+D 退出解释器

1.3 集成开发环境

集成开发环境(Intergrated development environment) —— 集成了开发软件需要的所有工具

1)Pycharm技巧
  • if代码块可折叠
  • if代码块,光标所在位置有提示
  • ctrl + / 批量添加多行的单行注释
  • 在函数名处的提示中,添加函数文档注释(自动显示形参注释)
2)Pycharm命名规则
  • 文件名:小写字母、数字、下划线
  • 文件名不能以数字开头
3)单步越过和单步进入

Step Over(F8):单步越过,_跳过_函数内部

Step Into(F7):单步进入,_进入_函数内部

02常见错误

  • NameError:name 'pprint' is not defined:名称错误

  • SyntaxError:invalid syntax:语法错误

  • IndentationError:unexpected indent:缩进错误

03执行原理

3.1计算机中的三大件

  1. CPU
    • 中央处理器,超大规模的集成电路
    • 负责 处理数据/计算
  2. 内存
    • 临时储存数据(断电后,数据消失)
    • 速度快
    • 空间小(单位价格高)
  3. 硬盘
    • 永久储存数据
    • 速度慢
    • 空间大(单位价格低)

3.2程序执行原理

  1. 程序运行前,被保存在硬盘之中
  2. 当要运行一个程序时,
    • 操作系统先让CPU把程序复制到内存
    • CPU 执行内存中的代码

程序要执行,首先被加载到内存中

3.3python执行原理

  1. 操作系统让 CPUPython解释器 的程序复制到内存中
  2. Python解释器根据语法规则,从上向下翻译Python程序中的代码
  3. CPU负责执行翻译完成的代码
  • Python解释器有多大(3.4M)?
1
2
3
4
5
6
7
8
# 1.确认解释器位置
$ which python3

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

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

建立软连接的目的,是为了方便使用者不用记住解释器的版本号

04代码规范

  • python官方代码格式文档**:PEP8**
  • Google 开源项目风格指南

任何语言的程序员,编写出符合规范的代码,是开始程序生涯的第一步。

二、基本语法

01注释

1.1单行注释

  • 格式#后面建议先写一个空格,再写注释文字
1
2
# 注释写在代码上方
ptint("Hello,Python!")
  • 格式:代码和注释间至少两个空格
1
ptint("Hello,Python!")  # 代码和注释之间至少两个空格

1.2多行注释

  • 格式:三个引号(单引号或者双引号)
1
2
3
4
5
"""
连续敲三个引号,
在引号中间输入多行注释
"""
print"hello!"

1.3什么时候需要使用注释

  1. 对于复杂操作,应在操作开始前,写上注释。
  2. 对于不是一目了然的代码,应在其行尾添加注释。
  3. 不要描述代码,应注释为什么写这行代码。

02算术运算符

运算符描述样例
+
-
*
/
%取余9 % 2 (返回余数=1)
//取整除9 // 2 (返回商=4)
******2 ** 3(返回乘积=8)
  • 在python 中 * 运算符可用于字符串,计算结果是字符串重复制定次数的结果
1
print("-" * 3)  # 打印结果为  '---'

03变量

3.1变量原理

1)QQ运行过程
  1. QQ在运行前,被保存在硬盘中

  2. 运行之后,QQ程序就会被加载到内存

  3. 读取用户输入的账号密码

  4. 把读取的账号和密码存储到内存中

  5. 将账号和密码发送给腾讯服务器

2)储存账号密码
  1. 在内存中为账号密码各自分配一块空间
    • 在QQ程序结束前,这两块空间由QQ程序负责管理,其他程序不允许使用
    • 在QQ自己使用完成之前,这两块空间只保存账号密码
  2. 使用一个别名,标记账号密码在内存中的位置

  • 在程序内部,为账号和密码在内存中分配的空间就叫做变量
  • 程序就是用来处理数据的!变量就是用来储存数据的!

3.2变量定义

格式:变量名 = 值

1
name = "python"
  • python 中定义变量不需要制定类型

3.3变量类型

  • 数据类型分为数字型非数字型
1)数字型
int整型
float浮点型
bool布尔型True 非0数、Flase 0
complex复数型主要用于科学计算
2)非数字型
字符串
列表
元组
字典
  • type函数查看变量的类型
1
2
>>> type(a)
<class 'str'>

3.4不同类型变量运算

  • 数字变量之间可以进行算数运算

    • True = 1
    • Flase = 0
  • 字符串变量之间使用 + 连接

1
2
3
4
>>> a = "张"
>>> b = "三"
>>> a + b
'张三'
  • 字符串变量整型变量 使用 * 重复拼接相同的字符串
1
2
>>> "-" * 5
'-----'

3.5变量命名

1)标示符
  • **标识符 **就是 变量名、函数名
  • 标示符需要见名知意
2)规则
  1. 字母、下划线、数字组成
  2. 不能以数字开头
  3. 不能与关键字重名
3)关键字

用以下命令可以查看 Python 中的关键字:

1
2
import keyword
print(keyword.kwlist)

结果如下:

1
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
4)风格
  • 下划线法(Python推荐)
  • 大驼峰
  • 小驼峰

04函数

4.1 print函数

  • 打印
1
2
3
4
print("hello,python!")
print(name)

# 不换行打印
  • 格式化输出
格式化字符含义
%s字符串
%06d有符号十进制,06 表示输出整数显示位数,不足的地方用 0 补全
%0.2f浮点数,0.2表示小数点后只显示两位
%%输出%
1
2
3
# print(”格式化字符串“ % 变量名)
# 当输出多个变量时,% 后接一对括号,括号中用 逗号 分隔变量名
print("我的学号是 %06d ,我的名字时 %s" % (student_no, name))
  • 当变量发生运算时,
1
2
3
4
5
# 这是把字符串输出10遍
print("数据比例为:%.2f%%" % scale * 10)

# 这是把 scale的值乘10
print("数据比例为:%.2f%%" % (scale * 10))
  • 转义字符
\t垂直制表符垂直方向,保持对齐(4位)
\r回车将光标的位置,回退到本行的开头位置
\n换行符
\\反斜杠
\’单引号
\"双引号
  • \r 的使用
1
2
3
4
5
6
# 进度条功能
import time
for i in range(20):
print("\r" + "【" + "■" *i + " " *(19-i) + "】", sep="", end="")
time.sleep(0.2)
print("\n下载完成")
1
2
3
4
5
# 显示倒计时
import time
for i in range(10):
print("\r离程序退出还剩%s秒" % (9-i), end="")
time.sleep(1)
1
2
3
4
5
6
7
# 缓冲转圈
import time
lst = ["\\", "|", "/", "——"]
for i in range(20):
j = i % 4
print("\r" + lst[j], end="")
time.sleep(0.2)

4.2 input函数

  • input接收的任何内容 ,Python都认为是一个字符串
1
age = input("请输入信息:")

4.3类型转换函数

  • int(x)
  • float(x)

4.4定义函数 & 调用函数

封装 独立的功能

  • def 关键字(定义一个函数)
1
2
3
# 该文件命名为 new_function.py  (符合标识符命名规则)
def my_fun():
print("这个函数可以打印这句话")
  • import 关键字 (导入文件,调用函数)
1
2
import new_function
new_function.my_fun # 调用 new_function.py 中的 my_fun 函数
  • return 关键字
1
2
def sum(num1,num2):
return num1 + num2
  • 格式:
    1. ⚠️定义函数有 (): ,参数在 ()
    2. ⚠️调用函数用 .
    3. dfe 是**define** 的缩写
    4. 函数名 与 模块名(即函数的 python文件名)命名应该符合标识符的命名规则

4.5函数的文档注释

  1. 函数定义的上方 保留两个空行
  2. 注释写在函数名下面,用三个双引号 **""""" **

05语句

5.1 判断

  • if 后加一个空格,条件后加冒号 : ,**else **后直接加 :
  • 代码缩进四个空格 或者 一个 Tab建议使用空格】
  • 在Python 开发中,Tab 键和空格不混用
1
2
3
4
5
6
if age > 18:
print("已成年!")
elif age == 18:
print("刚成年!")
else:
print("未成年!")
  • if 嵌套格式
1
2
3
4
5
6
7
if sex == 1:
if age > 18:
print("允许参战")
else:
print("未成年不允许")
else:
print("以防受伤")

5.2逻辑运算

  • and : 与

  • or :或

  • not :非

  • not 使用场景

    1. 希望某个条件不满足时,执行一些代码
    2. 需要拼接复杂的逻辑计算条件
1
2
3
4
# not + 布尔型变量
is_dog = True
if not is_dog:
print("不是狗狗")

5.3循环语句

  • while
  • break
  • continue
1
2
3
4
5
6
7
8
9
i = 0;
while i < 10:
if i % 3 == 0:
print(i)
else:
i += 1;
continue
print("已打印")
i += 2
  • for
1
2
3
4
for 变量 in 集合:
循环体代码
else:
没有通过 break 退出循环,循环结束后,会执行代码

应用场景

  • 迭代遍历 嵌套的数据类型时,例如 一个列表包含多个字典

  • 需求:要判断 某一个字典中 是否存在指定的值

    • 如果 存在,提示并且退出循环 【break 来完成】
    • 如果 不存在,在 循环整体结束 后,得到一个统一的提示 【else 来完成】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
student = [
{"name" : "阿土"},
{"name" : "阿美"}
]

find_name = "阿美"

for stu_dict in student:
if stu_dict["name"] == find_name:
print(stu_dict)
print("已找到!")
break # 已经找到后,无需继续循环
else:
print("没找到!")

06工具包

先导入工具包,后使用包内函数

6.1 keyword

  • kwlist函数:查看关键字

6.2 random

  • randint()函数:随机生成数,类型int
1
2
# 随机生成数,包括1和10
random.randint("1,10")

07关键字、函数、方法

7.1关键字

  • 是 Python 内置的、具有特殊意义的标识符
  • 关键字后不需要使用括号

7.2函数

  • 函数 封装了独立功能,可以直接调用
1
2
# 函数后面要加括号
函数名(参数)

7.3方法

  • 方法函数 类似,同样是封装了独立功能
  • 方法 需要通过 对象 来调用,表示针对这个 对象 要做的操作
1
对象.方法名(参数)

三、高级变量

01列表(List)

1.1定义格式

  • 列表定义: 在其他语言中叫做数组
  • 列表用 [] 定义,数据之间用 , 分隔
1
2
3
4
>>> name_list01 = []           # 创建一个空列表
>>> name_list02 = ["hu", "wen", "tao"] # 创建一个有三个数据的列表
>>> print(name_list02[0])
hu

1.2常用操作

  • IDLE 定义一个列表name_list
  • 输入 name_list. ,按下 Tab 键,按 上下箭头 翻找方法
1
2
>>> name_list = []
>>> name_list.append
分类方法说明
增加列表.insert(索引,数据)在索引位置插入数据
列表.append(数据)在末尾追加数据
列表.extend(列表2)将列表2的数据追加在列表中
修改列表[索引] = 数据
删除del列表[索引]从内存中删除指定索引数据
列表.remove[数据]删除第一个出现的制定数据
列表.pop删除末尾数据
列表.pop(索引)删除指定索引数据
列表.clear清空列表
统计len(列表)列表长度
列表.count(数据)数据在列表中出现次数
排序列表.sort()升序排序
列表.sort(reverse=True)降序排序
列表.reverse()逆序、反转
1)del关键字

del 本质上是 用来将一个变量从内存中删除

1
2
3
4
5
name = “tom”
del name

name_list = ["张三","李四","王五"]
del name_list[1]

⚠️注意:

  • 使用del 关键字将变量从内存中删除后,后续代码就不能再使用这个变量

  • del name_list[1] 可以删除数据,但是推荐用列表方法

2)len函数
  • 统计列表中的元素总数
1
list_len = len(list_name)
3) count方法
  • 统计列表中某一个数据出现的次数
1
2
# 统计 Tom 在列表name_list中出现的次数
count = name_list.count("Tom")
4) remove方法
  • 删除第一次出现的数据
1
name_list.remove("张三")
5)sort方法
  • 默认升序排序 reverse = Flase
1
2
# 降序排序
name_list.sort(reverse=True)

1.3循环遍历

python 中为了提高列表遍历效率,专门提供了迭代遍历(iteration)

  • 顺序地从列表中获取数据,并放入一个变量中
1
2
3
name_list = ["zhangsan","lisi","wangwu"]
for my_name in name_list:
print("my name is %s" % my_name)

1.4列表应用场景

  • 储存同类型数据
  • 通过 迭代遍历,在循环体内部,针对每一项元素,执行相同的操作

02元组(Tuple)

  • 元组 Tuple 与 列表 List 类似,
  • 相似之处:
    • 用于储存一串数据,数据之间用 分隔
  • 不同之处:
    • 元组用 () 定义
    • 元组中的元素定义完成,便不能修改

2.1定义一个元素的元组

  • 正确样例:
1
2
single_tuple = (5,)
many_tuple = (5,6,7)
  • 错误样例:

    Python 解释器会将 5 当作 int 型变量

1
single_tuple = (5)

2.2常用操作

  • IDLE 定义一个列表info_tuple
  • 输入 info_tuple. ,按下 Tab 键,按 上下箭头 翻找方法
1
2
>>> info_tuple = []
>>> info_tuple.index
方法说明
元组.count(数据)统计该数据在元组中出现的次数
元组.index(数据)确定该数据在元组中的索引
  • count方法
1
2
3
info_tuple = ("zhangsan", 18, 1.75)
# 统计 “zhangsan” 出现的次数
print(info_tuple.count("zhangshan"))
  • index 方法
1
2
3
info_tuple = ("zhangsan", 18, 1.75)
# 统计 “zhangsan” 在元组的索引
print(info_tuple.index("zhangshan"))

2.3元组的应用场景

  • 函数的 参数返回值,一个函数可以接受任意多个参数,或者 一次返回多个数据

  • **格式化字符串,**格式化字符串后面的 () 本质上是个元组

  • **让列表不可以修改,**以保护数据安全

1
2
3
info_tuple = ("小明", 18)
print("%s 年龄是 %d" % ("小明", 18))
print("%s 年龄是 %d" % info_tuple)
  • ⚠️用格式化字符串,拼接生成新的字符串
1
info_str = "%s 年龄是 %d" % info_tuple

2.4元组和列表转换

  • list 函数:将元组转化为列表
1
list(元组)
  • tuple 函数:将列表转化为元组
1
tuple(列表)

03字典(dictionary)

3.1定义格式

  • dictionary (字典)是除列表以外,python 中 最灵活 的数据类型

  • 字典同样可以 储存多个数据

    • 用来 描述一个物体 的相关信息
  • 和列表的区别

    • 列表有序 的对象集合
    • 字典无序 的对象集合(不关心保存顺序
  • 字典用 {} 定义

  • 字典使用 键值对 储存数据,键值对 之间使用 , 分隔

    • key 是索引
    • value 是数据
    • 和 **值 ** 之间使用 : 分隔
    • 键必须是唯一的
    • 可以取任何数据类型,但 只能使用 字符串、数字、元组
1
2
3
4
5
# 前面是键,后面是值,中间用 冒号 分隔
xiaoming_dict = {"name":"小明",
"age":18,
"gender":True,
"height":175}

3.2常用操作

  • 取值
1
2
3
4
5
# 在取值的时候,如果指定的 key 不存在,程序会报错!
print(xiaoming_dict["name"])

# 字典.get(key),当key不存在时不报错!
xiaoming_dict.get("name")
  • 增加/修改
1
2
3
4
5
# 如果key不存在,会新增键值对
xiaoming_dict["farther"] = "大明"

# 如果key存在,会修改键值对
xiaoming_dict["age"] = 19
  • 删除
    • 使用 pop 函数删除数据
1
2
# 在删除的时候,如果指定的 key 不存在,程序会报错!
xiaoming_dict.pop("name")
1)len函数
  • 统计 键值对 数量
1
print(len(xiaoming_dict))
2)update方法
  • 合并字典
    • 如果被合并的字典中包含已存在的键值对,会覆盖原有的键值对
1
2
3
4
5
# 新建一个临时键值对
temp_dict = {"height":1.75
"age":20}
# 将临时键值对合并入 xiaoming_dict 字典中
xiaoming_dict.update(temp_dict)
3)clear方法
  • 清空字典
1
xiaoming_dict.clear

3.3循环遍历

  • 遍历字典中的所有 key ,再根据 key 获取 value
1
2
for key in xiaoming_dict:
print("%s : %s" %(key, xiaoming_dict[key]))

3.4应用场景

  • 使用 多个键值对,储存 描述一个物体 的相关信息 — 描述更复杂的数据信息
  • 多个字典 放在 一个列表 中,再进行遍历,在循环体内部,针对每个字典进行 相同的处理
    • card_list = [ { }, { }, { } ]
1
2
3
4
5
6
7
card_list = [{"name":"zhangsan",
"qq":1213123,
"phone":10086},
{"name":lisi,
"qq":980890,
"phone":10010}
]

04字符串

  • "" '' 定义字符串
    • 如果字符串内部需要使用 "" ,则使用 '' 来定义
    • 如果字符串内部需要使用 ‘’ ,则使用 “” 来定义

4.1循环遍历

  • 依次取出每个字符
1
2
3
hello_str = “hello,python”
for c in hello_str:
print(c)

4.2len函数

  • 统计字符串长度
1
print(len(hello_str))

4.3count方法

  • 统计某一子串出现的次数
1
print(hello_str.count("llo"))

4.4index方法

  • 统计某一个子串第一次出现的位置
1
2
# 如果使用index方法传递的子串不存在,程序会报错
print(hello_str.index("llo"))

4.5常用操作

string 内置的方法超级多

1)判断类型 -9
方法描述
string.isspace()判断 string 中只包含空白字符(空格和转义字符),则返回 True
string.isalnum()如果 string 至少有一个字符,且所有字符都是字母或者数字,则返回True
string.isalpha()如果 string 至少有一个字符,且所有字符都是字母,则返回True
string.isdecimal()如果 string 只包含数字,则返回 True,(不能识别小数)
string.isdigit()如果 string 只包含数字,则返回 True,(不能识别小数)、(1)、\u00b2 【unicode字符串】
string.isnumeric()如果 string 只包含数字,则返回 True,(不能识别小数)、(1)、\u00b2、汉字数字
2)查找和替换 -7
方法描述
string.startswith(str)检查字符串是否以 str 开头,是则返回 True
string.endswith(str)检查字符串是否以 str 结束,是则返回 True
string.find(str,start=0,end=len(string))检查 str 是否包含在指定范围 start - end 内,如果是则返回开始的索引值,否则返回-1
string.replace(old_str,new_str,num=string.count(old))把 string 中的 old_str 替换成 new_str,如果有num,则替换次数不超过num次【不改变原有的字符串,返回新的字符串】
3)大小写转换 -5
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.split(str=“”,num)以 str 为分隔符拆分 string,如果num存在,则仅分隔 num+1 个字符串,若 split 无参数,则 str 默认包含 ‘\r’、‘\n’、‘\t’ 和空格
string.join(seq)以 string 作为分隔符,将 seq 中所有元素合并成一个新的字符串【seq可以是列表】

05共同方法

5.1切片

  • 切片 方法适用于 字符串、列表、元组

  • 通过 索引值 来限定范围 ,包括前索引对应数值,不包括后索引对应数值。【左开右闭】

  • 正序索引(0,1,2) 倒序索引(-1,-2,-3)

  • 当结束索引为空,便可切到字符串末尾

1
2
3
# 字符串[开始索引:结束索引:步长]
# 该切片为str[0]、str[1]
str[0:2:2]

5.2 Python内置函数

函数描述备注
len(item)计算容器中的个数
del(item)删除变量del 有两种使用方式【关键字 和 函数】
max(item)返回容器中元素最大值如果是字典,只比较 key
min(item)返回容器中的元素最小值如果是字典,只比较 key

5.3运算符

运算符Python表达式结果描述支持的数据类型
+[1,2] + [3,4][1,2,3,4]合并字符串、列表、元组
*[“hi”] * 3[“hi”,“hi”,“hi”]重复字符串、列表、元组
in3 in (1,2,3)True元素是否存在字符串、列表、元组、字典【key】
not in4 not in (1,2,3,4)True元素是否不存在字符串、列表、元组、字典【key】
>
>=
==
<
<=
(1,2,3) < (2,2,3)True元素比较字符串、列表、元组

注意

  • innot in字典 进行操作时,判断的是 字典的键

  • innot in 被称为 成员运算符

四、项目训练:名片管理

01框架搭建

  • 搭建 名片管理系统 框架结构
    1. 准备文件,确定文件名,保证能 **在需要的位置 ** 编写代码
    2. 编写 主运行循环,实现基本的 用户输入和判断

1.1文件准备

  1. 新建 card_main.py 保存 主程序功能代码
    • 程序的入口
    • 每一次启动名片管理系统都通过 main 这个文件启动
  2. 新建 card_tools.py 保存 所有名片功能函数
    • 将对名片的 新增、查询、修改、删除 等功能封装在不同的函数中

1.2 pass关键字

  • 如果开发时,不希望立即编写分支内部的代码,

  • 可以使用 pass 关键字,表示已一个占位符,能够保证程序的代码结构正确

  • pass 不会执行任何操作

1.3 TODO注释

  • # 后跟上 TODO ,用于标记需要去做的工作
  • pycham 中左下角有 TODO 列表
1
2
# TODO(作者/邮箱) 注释内容
代码。。。

1.4 shebang符号(#!)

  • shebang 在 unix 系统脚本中的 第一行开头 使用
  • 指明 **执行这个文件 **的 解释程序

例如

  • 如果解释器 python3 在 /usr/bin/python3,
  • 使用shebang符号就可以在终端中使用 ./文件名.py 就可以执行
  • 前提修改 文件名.py 的读写权限 chomd +x 文件名.py
1
2
3
#! /usr/bin/python3

print("这样就可以直接在终端中执行命令")

五 、变量进阶

01变量的引用

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

在python中

  • 变量数据 都是分开存储的

  • 数据 保存在内存中的一个位置

  • 变量 中保存着数据在内存中的地址

  • 变量中记录着数据的地址,就叫做 引用

  • 使用 id() 函数来查看 变量中保存的内存地址

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

  • 变量 不再 对之前的数据引用
  • 变量 改为 对新赋值的数据引用
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = 1
>>> id(1)
4305020032

>>> id(a)
4305020032 # 原引用

>>> a = 2
>>> id(2)
4305020064

>>> id(a)
4305020064 # 新引用

02可变类型和不可变类型

  • 不可变类型,内存中的数据不允许被修改
    • 数字类型 int bool float complex long(2,x)
    • 字符串 str
    • 元组 tuple
  • 可变类型,内存中的数据可以被修改
    • 列表
    • 字典

注意

  1. 方法 修改 可变类型数据,地址不改变

  2. 使用 赋值语句 修改 可变类型数据,地址发生改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> a = [1,2,3]
>>> id(a)
4561371272 # 原地址
>>> a.append(99)
>>> a
[1, 2, 3, 99]
>>> id(a)
4561371272 # 地址不变
>>> a.clear()
>>> a
[]
>>> id(a)
4561371272 # 地址不变
>>> a = []
>>> id(a)
4561371656 # 使用赋值语句更改数据,地址改变

字典中的 key 只能用不可变类型数据

03 hash函数

  • python内置有一个hash(参数)
    • 传递一个 不可变类型参数
    • 返回一个 整数
1
2
>>> hash("hello")
3690660419994397917
  • python 中,设置字典的 键值对 时,会首先对 key 进行 hash 以确定内存中保存字典的数据,方便后续的增删改查

04 全局变量和局部变量

  • 注意python 中 函数内部不能直接修改全局变量的引用 — 使用赋值语句修改全局变量的值
1
2
3
num = 10
def:
num = 99 #只能新建一个局部变量 num=99,不能修改 全局变量 num=10
  • 只能使用 global 进行声明,才可以在函数内部修改全局变量
1
2
3
4
num = 10
def:
global num #global 关键字会告诉解释器后面的变量时一个全局变量
num = 99
  • 全局变量定义位置

    • 所有函数上方
  • 全局变量命名建议

    • 为避免局部变量和全局变量混淆,全局变量前应增加 g_ 或者 gl_ 的前缀

六、函数进阶

01返回多个数据

  • 使用元组让函数一次返回多个值

  • 如果函数返回类型时元组,小括号可省略

1
2
3
4
def math(a,b):
c = a + b
d = a * b
return c,d #也可以写成 return(c,d)

02接收多个数据

  • 返回i值为元组时,用多个变量一次接收函数的返回值
1
2
3
4
5
def math(a,b):
c = a + b
d = a * b
return c,d
gl_c,gl_d = math(a,b) # 可依次接收返回值的各个数据

03交换两个数字

Python 专有,利用元组

1
2
3
4
a = 6
b = 100
a, b = (b, a) # 用 a,b 来接收元组(a,b)
a, b = b, a # 括号可以省略

七、参数进阶

01用赋值语句修改参数

  • 无论传递的参数是可变的还是不可变的,使用赋值语句修改局部变量的引用,不会影响到外部实参
1
2
3
4
5
6
7
def demo(num):
num = 200
print(num) # 200

gl_num = 10
demo(gl_num)
print(gl_num) # 10 实参没有改变

02用方法修改参数

  • 传递参数是可变类型,使用方法修改数据的内容,不修改变量引用,同样会改变外部数据
1
2
3
4
5
6
7
def demo(num_list):
num_list.append(9)
print(num_list) # 1,2,3,9

gl_list = [1,2,3]
demo(gl_list)
print(gl_list) # 1,2,3,9 实参被改变

03 += 的本质

  • 列表变量调用 += 本质上是在执行列表变量的 extend 方法,不会修改变量的引用,改变数据内容

  • num += 1 是在做先相加再赋值的操作(针对数字和字符串)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def demo(num,num_list):
# num = num + num #修改引用,不影响外部实参
num += num
print(num)
# 本质上是在调用 extend 方法
# num_list.extend(num_lsit) #直接修改数据内容 影响外部实参
num_list += num_list
print(num_list)

gl_num = 9
gl_list = [1,2,3]
demo(gl_num,gl_list)
print(num)
print(num_list)

04缺省参数

  • 定义函数时,给某个参数指定一个默认值,具有默认值的参数就叫做 缺省参数

  • 调用函数时,若没有传入 缺省参数 的值,则在函数内部使用 默认参数值

  • 设置缺省参数时,应将最常见的值,设置为参数的缺省值,从而简化函数的调用

1.1 指定函数的缺省参数

  • 在参数后面使用赋值语句
1
2
3
4
5
def print_info(name,gender=True):
gender_text = "男生"
if not gender:
gender_text = "女生"
print("%s 是 %s" %(name,gender_text))

1.2缺省参数注意事项

1)缺省参数的定义位置
  • 必须保证 带有默认值的缺省参数 在参数列表末尾
1
def print_info(name, title, gender=True):
2)调用带有多个缺省参数的函数
  • 调用函数时,如果有多个缺省参数,传参需指定参数名
1
2
3
4
def print_info(name, title="007", gender=True):

# 调用时,传参需指定参数名
print_info("小明",gender=False)

05多值参数

5.1定义多值参数的函数

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

  • python 中有 两种 多值参数:

    • 形参前加 * 可以接收 元组
    • 形参前加 ** 可以接收 字典
  • 习惯命名:

    • *args ——存放 元组 参数
    • **kwargs ——存放 字典 参数
  • args 是 arguments 的缩写,kw 是 keyword 的缩写

1
2
3
4
5
6
7
8
9
10
11
def demo(num, *args, **kwargs):
print(num)
print(args)
print(kwargs)

demo(1,2,3,4,5,name="xiaoming") # 重点:传参方式,自动识别

#打印结果如下
1
2345
{‘name’:‘xiaoming’}

5.2元组和字典的拆包

  • 在调用多值参数的函数时,如果希望:

    • 将一个元组,直接传递给 args
    • 将一个字典,直接传递给 keargs
  • 就使用拆包,简化参数传递,拆包的方式是:

    • 在 元组变量前,增加*
    • 在 字典变量前,增加**
1
2
3
4
5
6
7
8
9
def demo(*args,**kwargs):
print(args)
print(kwargs)

gl_nums = (1,2,3)
gl_info = {'name':'小明','age':18}

# 需要将整个元组/字典 传递给函数对应的参数
demo(*gl_nums,**gl_info)

八、面向对象

面对对象编程 —— Object Orientd Programming 简写 OOP

###01基本概念

1.1面对过程–怎么做?

  1. 把完成某一个需求的 所有步骤 从头到尾 逐步实现
  2. 把某些 功能独立 的代码 封装 成一个又一个 函数
  3. 最后完成代码,顺序地 调用不同的函数

特点

  • 注重步骤和过程,不注重职责分工

  • 如果需求复杂,代码会很复杂

  • 开发复杂项目,没有固定套路,难度大

1.2面对对象–谁来做?

相比较函数,面对对象更大的封装 ,根据 职责一个对象中封装多个方法

  1. 在完成某一个需求时,首先确认 职责 —— 要做的事情(方法)
  2. 根据 职责 确定不同的 对象,在 对象 内部封装不同的 方法(多个)
  3. 最后完成代码,顺序地让 不同的对象 调用 不同的方法

特点

  1. 注重 对象和职责,不同的对象承担不同的职责
  2. 更加适合应对复杂的需求变化,是专业应对复杂项目开发,提供的固定套路
  3. 需要在面向过程的基础上,在学习一些面向对象的语法

02面对对象

2.1 类

  • 是对一群具有 相同 特征行为 的事物的一个统称,是抽象的,不能直接使用
    • 特征 被称为 属性
    • 行为 被称为 方法

2.2 对象

  • 对象 是 由类创建出来的一个具体存在,可以直接使用
    • 属性
    • 方法

2.3 类的设计

1)三要素
  • 在程序开发中,要设计一个类,满足三个要素:
    1. 类名 这类事物的名字,满足大驼峰命名法
    2. 属性 这类事物具有什么样的特征
    3. 方法 这类事物具有什么样的行为
2)类名的确定
  • 名次提炼法 分析整个业务流程,出现的 名词 ,通常就是找到的类
3)属性和方法的确定
  • 对象的特征描述,通常可以定义为 属性
  • 对象具有的行为(动词),通常可以定义为 方法

03基本语法

3.1dir内置函数

  • 查看对象的所有属性及方法
1
2
gl_list = [1,2]
dir(gl_list)

3.2定义类

  • 方法中,第一个参数必须是 self
  • 类名的命名规则 要符合 大驼峰命名法
1
2
3
4
5
class 类名:
def 方法(self,参数列表):
pass
def 方法(self,参数列表):
pass

3.3创建对象

1
对象变量 = 类名()

引用的概念强调

用 print 输出对象变量,能够输出 给对象是由哪一类创建的对象,以及在内存中的地址(十六进制)

1
2
3
4
5
6
7
8
class Cat:
def eat(self):
pass


tom = Cat()

print(tom)

输出的结果为: <main.Cat object at 0x1030a3cc0>

3.4给对象增加属性

  • 只需要在类的外部,直接通过 . 来设置属性【不推荐】
  • 不推荐的原因:如果在运行的时候,没有找到属性,程序就会报错
1
2
3
4
5
6
class Cat:
def eat(self):
pass

tom = Cat()
tom.name = "小猫"

3.5初始化方法

  • 当使用类名() 创建对象时,会自动执行以下操作:
    1. 为对象在内存中 分配空间 --创建对象
    2. 为对象的属性 设置初始值 --初始化方法
  • 这个 初始化方法 就是 __init__ 方法, __init__ 是对象的内置方法
  • __init__ 方法 是专门用来定义一个类 具有哪些属性 的方法
1
2
3
4
5
class Cat():
def __init__(self): # 创建类时,被自动调用
print("初始化方法执行完毕!")

tom = Cat()

3.6在初始化方法内部定义属性

  • __init__ 方法内部使用 self.属性名 = 属性的初始值 就可以定义属性
  • 定义属性后,使用类创建的对象,都有该属性
1
2
3
4
5
6
class Cat:
def __init__(self):
self.name = "小猫"

tom = Cat()
print(tom.name)

3.7初始化的同时定义初始值

  • 在创建对象的同时,根据传入的参数设置对象的属性,用 __init__ 方法 进行改造
    1. 设置形参接收参数
    2. 将参数传递给 self.属性名
1
2
3
4
5
6
7
class Cat:
def __init__(self,new_name):
self.name = new_name

tom = Cat("小猫")

print(tom.name)

3.8_del_方法

  • 在python中,

    • 使用 类名() 创建对象时,分配完空间后,自动调用__init__ 方法
    • 当对象被销毁时,自动调用 __del__ 方法
  • 应用场景

    • __init__ 方法 :改造初始化方法,让创建对象时,更加灵活
    • __del__ 方法:希望在销毁对象之前,可以再做一些事情
  • 生命周期

    • 对象的生命周期 是 调用__init__ 和 调用__del__ 之间的时间
1
2
3
4
5
6
7
8
9
calss Cat():
def __init__(self):
print("初始化方法执行了!")

def __del__(self):
print("该对象内存即将销毁!")

tom = Cat()
print("--" )
  • 运行结果为:
1
2
3
初始化方法执行了!
--
该对象内存即将销毁!

因为 tom 变量是全局变量,只有在整个程序结束的时候,才会销毁tom变量的内存空间,才会执行_del_ 方法

3.9_str_方法

  • 在python中,打印对象变量,默认会打印出 这个变量由哪一类创建的对象,以及在内存中的地址(十六进制)

  • 如果希望打印对象变量时,能够打印自定义的内容,就可以使用 _str_方法

1
2
3
class Cat:
def __str__(self):
return "这个内置方法返回一个字符串"
  • 还可以让返回值带有参数
1
2
3
4
5
class Cat:
def __init__(self,new_name):
self.name = new_name
def __str__(self):
return "我的名字叫 %s" % self.name

3.10封装案例一

需求

  1. 房子有户型、总面积、家具名列表
    • 新房子没有任何家具
  2. 家具有名字和占地面积
    • 席梦思(bed):4
    • 衣柜(chest):2
    • 餐桌(table):1.5
  3. 将家具添加到房子中
  4. 打印房子户型、总面积、剩余面积、家具名列表

分析

  1. 寻找类:房子、家具

  2. 类的属性(变量):

    • 房子:户型、总面积、家具列表
    • 家具:名称、面积
  3. 类的方法:

    • 添加家具:判断面积是否足够、重新计算剩余面积

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Item:

# 接收传入的参数:家具名字、面积
def __init__(self,name,area):
self.name = name
self.area = area


class House:

# 接收传入的参数:户型、总面积
def __init__(self,type,area):
self.type = type
self.area = area

# 将剩余面积初始化为总面积
self.free_area = area

# 将家具列表初始化为空
self.item_list = []

# 打印房子信息
def __str__(self):

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

# 添加家具的函数
def add(self,item):

# 添加家具前,先判断剩余面积是否够用
if item.area <= self.free_area:

# 满足条件,更改剩余面积
self.free_area -= item.area

# 将家具名添加到家具列表
self.item_list.append(item.name)
print("添加成功!")

# 如果不够,输出反馈信息
else:
print("%s 的面积太大了,无法添加" %item.name)

# 实例化房子和家具,并添加家具
house = House("01",180)
bed = Item("席梦思",40)
chest = Item("衣柜",2)
table = Item("餐桌",1.5)

house.add(bed)
house.add(bed)

print(house)

3.11封装案例二(对象作参数)

  1. 士兵 许三多 有一把 AK47
  2. 士兵 可以 开火
  3. 能够 发射 子弹
  4. 装填 装填子弹 —— 增加子弹数量

分析

  1. 寻找类:士兵、枪
  2. 类的属性:
    • 士兵:名字,枪的类型
    • 枪:子弹的数量
  3. 类的函数:
    • 士兵:射击
    • 枪:装填子弹、发射子弹

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 开发一个士兵类
class Soldier:

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

# 士兵最开始没有发枪
self.gun = None

def kaihuo(self):

# 判断士兵是否有枪
if self.gun is None:
print("%s 没枪!" % self.name)
return

# 让枪装填子弹
self.gun.addzidan(50)

# 让枪发射子弹
self.gun.shoot()



class Gun:

def __init__(self,mode):

# 枪的型号
self.mode = mode

# 枪的子弹数量
self.count = 0

def shoot(self):
if self.count>0:
print("发射子弹!")
self.count -=1

def addzidan(self,count):
self.count = count

xusanduo = Soldier("xusanduo") # 实例化一个 许三多 的士兵
ak47 = Gun("ak47") # 实例化一个 ak47 的枪
xusanduo.gun = ak47 # 把 ak47 给 许三多
xusanduo.kaihuo() # 许三多 开火,实则调用 枪 的方法
print(xusanduo.gun.count) # 打印 子弹剩余数量

小结

  • 一个类实例化的对象 当作参数传递给 另一个类(一个对象的 属性 可以是 另外一个类 创建的对象

    • None 关键字 表示 什么都没有
    • 表示一个 空对象没有方法和属性,是一个特殊的常量
    • 可以将 None 赋值给任何一个变量

3.12身份运算符

身份运算符用于比较两个对象的内存地址是否一致 — 是否对同一个对象的引用

  • 在对 None 进行比较时,建议使用 is 【PEP8官方文档】
运算符描述
is判断是不是引用相同的对象
not is判断是不是引用不同的对象

is 和 == 的区别

is 用于两个变量 引用的对象是否是同一个

== 用于判断引用变量的值 是否相等

1
2
3
4
5
6
>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True
>>> a is b
False

04私有属性和私有方法

应用场景

  • 对象的某些属性和方法,只希望在对象的内部(在类的内部)被使用,而不希望在外部(在类的外部)被直接访问到
  • 私有属性就是对象不希望公开的属性
  • 私有方法就是对象不希望公开的方法

定义方式

  • 在i定义属性和定义方法时,在属性名前或者方法名前,增加两个下划线,定义出来的就是私有的属性或方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class women:

def _init_(self,name):
self.name = name
self.__age = 18 # 定义 age 为私有属性

def secret(self):
print(self.__age)

xiaomei = women("xiaomei")

# 无法直接访问私有属性
print(xiaomei.age)

# 可以通过类中的方法 访问私有属性
xiaomei.secret

python处理方式

python中,没有真正意义的私有

  • python对私有属性、私有方法的处理方法就是,在名称前面加上 _类名__私有属性名或者私有方法名
1
2
3
# 所以直接用 这种方式 也可以访问到私有属性或者私有方法
# 对象名._类名__属性名
print(xiaomei._women__age)

九、继承

面对对象的三大特性

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

01单继承

1.1单继承的概念和语法

1) 概念子类(派生类) 拥有父类(基类)所有的方法和属性

2)语法

1
2
calss 类名(父类名):
pass

3)继承的传递性

  • 子类 拥有 父类父类的父类 中封装的所有属性和方法

1.2方法的重写

1)应用场景

  • 子类 继承 父类 所有属性和方法
  • 父类 的方法实现不能满足 子类 需求时,可以对方法进行 重写(override)

2)重写的情况有两种

  1. 覆盖 父类的方法
  2. 扩展 父类的方法

3)覆盖父类的方法

1
2
3
4
5
6
7
8
9
class Animal:
def eat(self):
pass
def bark(self):
pass

class Dog(Animal):
def bark(self):
print("汪!") # 此处子类Dog 覆盖重写了 父类Animal 中的bark()方法

4)扩展父类的方法

1
2
3
4
5
6
7
8
9
10
class Animal:
def eat(self):
pass
def bark(self):
pass

class Dog(Animal):
def bark(self):
print("汪!") # 此处子类Dog 覆盖重写了 父类Animal 中的bark()方法
super().bark(self) # 此处继续保留父类的方法
  • super(). 不是 super.

  • super 是一个特殊的类

  • super()是由super 类创建出来的对象

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

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

02多继承

2.1多继承的概念和语法

1)概念:子类拥有 多个 父类,并具有所有父类的属性和方法

2)语法

1
2
class 子类名(父类名1,父类名2...):
pass

2.2多继承的使用注意事项

  • 如果 不同的父类 中存在 同名的方法或者属性,应避免使用多继承

2.3Python中的MRO–方法搜索顺序

  • method resolution order

  • python 中针对类 提供了一个内置属性 __mro__ 可以查看 方法 搜索顺序

  • 主要用于在多继承时,判断方法、属性的调用路径

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
def test(self):
pass

class B:
def test(self):
pass

class D(A,B): # D继承A和B,A和B拥有重名方法
pass

d = D()
print(D.__mro__) # 查看D类对象调用方法的顺序
  • 输出结果显示,先查找D类,再查找A类,以及B类(即上面第九行的继承顺序)
  • 如果找到,直接执行不再搜素;如果没找到,程序报错。
1
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

2.4新式类和旧式类(经典类)

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

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

新式类经典类 在多继承时 —— 会影响到方法的搜索顺序

为了保证编写的代码能够同时在 Python 2.xPython 3.x 运行!
今后在定义类时,如果没有父类,建议统一继承自 object

1
2
class 类名(object):
pass

十、多态

  • 多态:不同的 子类对象 调用 相同的父类方法 ,产生 不同的执行结果
    • 多态可以增加代码灵活度
    • 继承重写父类方法 为前提
    • 多态调用方法的技巧,不会影响类的内部设计

01案例演练

需求

  1. Dog 类中封装方法 game
    • 普通狗只是简单的玩耍
  2. 定义 XiaoTianDog 继承自 Dog,并且重写 game 方法
    • 哮天犬需要在天上玩耍
  3. 定义 Person 类,并且封装一个 和狗玩 的方法
    • 在方法内部,直接让 狗对象 调用 game 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Dog(object):

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

def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)


class XiaoTianDog(Dog):

def game(self):
print("%s 飞到天上去玩耍..." % self.name)


class Person(object):

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

def game_with_dog(self, dog):

print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name))

# 让狗玩耍
dog.game()


# 1. 创建一个狗对象
wangcai = XiaoTianDog("飞天旺财")

# 2. 创建一个小明对象
xiaoming = Person("小明")

# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)

案例小结

  • Person 类中只需要让 狗对象 调用 game 方法,而不关心具体是 什么狗
    • game 方法是在 Dog 父类中定义的
  • 在程序执行时,传入不同的 狗对象 实参,就会产生不同的执行效果

十一、类属性和类方法

01.类的结构


学习笔记|Python
https://www.aimtao.net/python/
Posted on
2019-07-08
Licensed under