Python基础 面向ML&DL

绪论

个人学习笔记,可能会有增删改,并且主要针对机器学习深度学习,不涉及其他python方向,仅供参考。
官方文档最靠谱

第二章 基本语法元素

第一部分 数据类型

1. 基本类型:数字、字符串、布尔

1.1 数字类型
  • int 整型   整数
2
2
  • float 浮点型   带小数的数
2.0
  • complex 复数   a+bj
3+4j
1.2 字符串类型
  • str 字符串   视作文本
  • 组成: 由数字、字母、空格、其他字符等组合而成
  • 表达: 用" " 或’ ’
"python 123 @#$^&(())"
'python 123 @#$^&(())'
1.3 布尔类型
  • bool 布尔类型
  • 主要用于逻辑运算
y = 2 < 1
y
False

上述类型均可定义单个数据,如果我们有一组数据,该如何表示?

2. 组合类型:列表、元组、字典、集合

2.1 列表
  • list 列表    序列类型:  数据有位置顺序
  • 表示方式:  [data1,data2…]
a = [1, 2, 3, 4, 5]
a[0]
1
2.2 元组
  • tuple 元组    序列类型
  • 表示方式: (data1,data2…)
  • 元素不支持修改——“不可变的列表”
b = (1, 2, 3, 4, 5)
b[0]
1
2.3 字典
  • dict 字典  映射类型: 通过“键”-“值”的映射实现数据存储和查找
  • 表示方式: {key1:value1 , key2:value2 , …}
student = {201901: "小明", 201902: "小红", 201903: "小强"}
student[201901]
'小明'
2.4 集合
  • set 集合  一系列互不相等元素的集合,无序的
  • 表示方式: {data1,data2…}
s = {"小明", "小红", "小强", "小明"}
s
{'小强', '小明', '小红'}

在程序中,我们如何来引用这些数据?

  • 非常通俗的处理办法:赋值给一个变量

第二部分 变量

1. 变量的概念

  • “量”   实实在在的对象:如数据、抽象
  • “变”   可变性:增、删、查、改等
  • 变量定义二要素:  变量名、赋值
x = 1

2. 变量的命名

2.1 哪些可以用来做变量名?
  • 大写字母、小写字母、数字、下划线、汉字及其组合。
  • 严格区分大小写
Python_is_第1= True
python_is_第1= False
2.2 哪些情况不被允许?
  • 首字符不允许为数字
1_fruit = "apple"
  File "<ipython-input-59-e3b1d93d01a0>", line 1
    1_fruit = "apple"
     ^
SyntaxError: invalid token
  • 变量名中间不能有空格
my fruit = "apple"
  File "<ipython-input-60-36327c3a601f>", line 1
    my fruit = "apple"
           ^
SyntaxError: invalid syntax
  • 不能与33个Pyhton保留字相同
if = True
  File "<ipython-input-62-4c75bcfb9bb9>", line 1
    if = True
       ^
SyntaxError: invalid syntax
2.3 变量名定义技巧
  • 变量名尽可能有实际意义,表征数据的某种特性
a = [17, 18, 19]
age_of_students = [17, 18, 19]
  • 下划线(推荐:变量和函数名)
    变量名由多个单词组成:用_连接多个单词

  • 驼峰体(推荐:类名) 变量名由多个单词组成:单词首字母大写
AgeOfStudents
  • 尽量避免用中文和拼音做变量名

  • 特殊的变量:常量(不变的量,如 π \pi π、e)   变量名所有字母均为大写
MAX_ITERATION = 1000

3. 变量的赋值

3.1 一般赋值
  • 通过等号自右向左进行赋值
x = 1+2
x
3
3.2 增量赋值
x = 10
x = x+10
x
x += 10
20
3.3 打包赋值
x, y = 1, 2
print(x, y)
x, y = y, x
print(x, y)
1 2
2 1

第三部分 控制流程

1. 顺序流程

  • 自上向下依次执行

【小例子】实现1到5的整数求和

# res = 1+2+3+4+5 
res = 0    # 赋初值
res += 1
res += 2
res += 3
res += 4
res += 5
res     # 显示结果
15

2. 循环流程——遍历循环(for)

主要形式:
  • for 元素 in 可迭代对象:
      执行语句
执行过程:
  • 从可迭代对象中,依次取出每一个元素,并进行相应的操作

【小例子】实现1到5的整数求和

res = 0
for i in [1,2,3,4,5]:    # 每次迭代,取出一个i
    res += i               # 对每次迭代取出的i 进行相应操作   
res                      # 遍历结束后,执行后续语句
15

3. 循环流程——无限循环(while)

主要形式:
  • while 判断条件:
  •   条件为真,执行语句
  • 条件为假,while 循环结束

【小例子】实现1到5的整数求和

i = 1
res = 0
while i <= 5:   # 若i不大于5,则循环继续
    res += i
    i += 1
res           # 若循环条件不成立,循环停止,执行后续语句
15

4. 分支流程(if)

最简单的形式:
  • if 判断条件:
  •   条件为真,执行语句
  • else:
  •   条件为假,执行语句
age = 18
if age > 22:
    print("可以结婚啦")
else:
    print("em,着急了点,再等等。。。")
em,着急了点,再等等。。。
  • 有了数据和变量,以及控制流程这些个中间过程后
  • 我们回过头来考虑下程序的输入和输出

第四部分 输入输出

1. 数据从哪里来?

1. 外部文件导入

  • 从本地硬盘、网络端读入等
  • 该部分内容放在 第八章《文件、异常和模块》进行讲解

2. 程序中定义

age = 18
name = "Tom"

3. 动态交互输入 input

  • 在程序运行的过程中进行输入
x = input("请输入一个数字:")
x
请输入一个数字:4





'4'
y = input("请输入一个数字:")
y
请输入一个数字:3.5





'3.5'
x + y 
'43.5'
type(x)
str
  • eval() 去掉引号
x = eval(input("请输入一个数字:"))
x
请输入一个数字:4





4
y = eval(input("请输入一个数字:"))
y
请输入一个数字:3.5





3.5
x + y
7.5

2. 数据到哪里去?

1. 存储到本地硬盘或网络端

  • 该部分内容放在 第八章《文件、异常和模块》进行讲解

2. 打印输出 print

  • 直接打印数据
print("我是一颗小星星")
我是一颗小星星
print(1234)
1234
  • 打印变量
x = 1024
print(x)
1024
  • print 默认换行
print(1)
print(2)
1
2
  • 如果不想换行怎么办?
  • 换行控制 end=
print(123,end=" ")
print(456)
123 456
  • 有时候,我们需要一些复杂的输出:比如几个变量一起组合输出
PI = 3.1415926
E = 2.71828
print("PI = ", PI, "E = ", E)
PI =  3.1415926 E =  2.71828

3. 格式化输出方法 format

  • 基本格式:“字符{ 0 }字符{ 1 }字符”.format(v0,v1)
print("PI = {0},E = {1}".format(PI, E))
PI = 3.1415926,E = 2.71828
print("PI = {1},E = {0}".format(PI, E))
PI = 2.71828,E = 3.1415926
print("PI = {},E = {}".format(PI, E))
PI = 3.1415926,E = 2.71828
print("PI = {0},E = {0}".format(PI, E))
PI = 3.1415926,E = 3.1415926
  • 再进一步 修饰性输出
  1. 填充输出
# ____3.1415926_____  进行填充
print("{0:_^20}".format(PI))
# 0代表PI   冒号代表修饰   _代表填充图案   ^ 表示居中   整体输出宽度为20
# <左对齐   >右对齐
_____3.1415926______
print("{0:*<30}".format(PI))
3.1415926*********************
  1. 数字千分位分隔符
  • 显示1,000,000
print("{0:,}".format(10000000))
10,000,000
print("{0:&>20,}".format(10000000))
# 填充符号写在前&  分割符号写在后,
&&&&&&&&&&10,000,000
print("{0:,&>20}".format(10000000))
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-35-9f30412a92d9> in <module>
----> 1 print("{0:,&>20}".format(10000000))


ValueError: Invalid format specifier
  1. 浮点数简化输出
  • 留2位小数
print("{0:.2f}".format(PI))
3.14
  • 按百分数输出
print("{0:.1%}".format(0.818727))
81.9%
  • 科学计数法输出
print("{0:.2e}".format(0.818727))
8.19e-01
  1. 整数的进制转换输出
  • 十进制整数转二进制、unicode码、十进制、八进制、十六进制输出
"二进制{0:b},Unicode码{0:c},十进制{0:d},八进制{0:o},十六进制{0:x}".format(450)
'二进制111000010,Unicode码ǂ,十进制450,八进制702,十六进制1c2'

第五部分 程序格式

1. 行最大长度

所有行限制的最大字符数为79

2. 缩进

  • 用缩进来表示语句间的逻辑
  • 在 for while if def class等 :之后下一行开始进行缩进,表明后续代码与前句之间的从属关系
  • 缩进量:4字符
for i in [1, 2, 3]:
    print(i)
print("打印结束")

3. 使用空格

  • 二元运算符两边加一个空格
x = 2       # 赋值
x + = 4     # 增量
6 > 2       # 比较
  • 使用不同优先级的运算符,考虑在最低优先级的运算符周围添加空格
x = x*2 - 1
z = x*x + y*y
c = (a+b) * (a-b)
  • 在逗号后使用空格
x, y = 1, 2
ls = [1, 2, 3]
  • 不要使用一个以上的空格
x     = 2        # 空格一个就够了,不应过多

4. 避免使用空格

  • 在制定关键字参数或者默认参数值的时候,不要在=附近加上空格
def fun(n=1, m=2):
    print(n, m)

小结

1、以上属于PEP8格式指南的部分内容,养成良好的编码规范利人利己
2、格式约定的目的:
  • 使大量Python代码风格一致
  • 提升代码可读性
3、尽信书不如无书,不应死板教条的执行格式规范
  • 项目规范优先

5. 注释

  • 单行注释
  • 格式:# 注释内容
a=1  # 我是单行注释
  • 多行注释
  • 格式:“”“注释内容,可分行”“”
"""
妈妈
我
好像
发现了
写诗
的
诀窍
"""

第三章 基本数据类型

第一部分 数字类型

1.1 数字类型的组成

1.1.1 整数——不同进制的转换
  • 默认输入十进制
  • 二进制0b、八进制0o、十六进制0x
16 == 0b10000 == 0o20 == 0x10
True
  • 十进制与其他进制的转换
a = bin(16)   # 转二进制
b = oct(16)   # 转八进制
c = hex(16)   # 转十六进制
print(a, b, c)
0b10000 0o20 0x10

注意:上述转换后结果为字符串类型

a == b == c
False
type(a)
str
  • 其他进制转十进制
d = int(a, 2)      # 二进制转十进制
e = int(b, 8)      # 八进制转十进制
f = int(c, 16)     # 十六进制转十进制
print(d, e, f)
16 16 16
1.1.2 浮点数——不确定性
  • 不确定小数问题
(0.1+0.2) == 0.3
False
0.1+0.2
0.30000000000000004

计算机采用二进制小数来表示浮点数的小数部分

  • 部分小数不能用二进制小数完全表示

     二进制                  十进制
    

0.00011001100110011001 0.09999942779541016
0.0011001100110011 0.1999969482421875
0.01001100110011001 0.29999542236328125
0.01100110011001101 0.40000152587890625
0.1 0.5

  • 通常情况下不会影响计算精度
0.1 + 0.7
0.7999999999999999
  • 四舍五入获得精确解
a = 3*0.1
print(a)
0.30000000000000004
b = round(a, 1)
print(b)
b == 0.3
0.3





True
1.1.3 复数——a+bj
  • 大写J或小写j均可
3+4j
2+5J
(2+5j)
  • 虚部系数为1时,需要显式写出
2+1j

1.2 数字运算操作符(a 操作符 b)

  • 加减乘除运算   +  -  /  *
(1+3-4*2)/5
-0.8
  • 取反  -
x = 1
-x
-1
  • 乘方运算  **
2**3
8
  • 整数商//  和  模运算%
13//5    # 整数商    x/y 向下取整数
2
13 % 5   # 模运算    余数 13=2*5+3
3

几点说明

  • 整数与浮点数运算结果是浮点数
  • 除法运算的结果是浮点数
1+1.5
2.5
2/5
0.4
8/4
2.0

1.3 数字运算操作函数  function(x, …)

  • 求绝对值 abs()
abs(-5)
5
abs(3+4j)  # 对复数a+bj 执行的是求模运算(a^2+b^2)^0.5
5.0
  • 幂次方 pow(x,n)
pow(2, 5)  # pow(x,n) x的n次方  等价于x**n
32
pow(2, 5, 3) # 2^5 % 3  更快速
2
  • 四舍五入 round(x,n)
a = 1.618
print(round(a))      # 默认四舍五入为整数
2
print(round(a, 2))   # 参数2表示四舍五入后保留2位小数
1.62
print(round(a, 5))   # 位数不足,无需补齐
1.618
  • 整数商和模运算 divmod(x,y)
  • 等价于返回二元元组(x//y,x % y)
divmod(13, 5)   # 较(x//y,x % y)更快,只执行了一次x/y
(2, 3)
  • 序列最大/最小值 max( )  min( )
max(3, 2, 3, 6, 9, 4, 5)
9
a = [3, 2, 3, 6, 9, 4, 5]
print("max:", max(a))
print("min:", min(a))
max: 9
min: 2
  • 求和sum(x)
sum((1, 2, 3, 4, 5))
15
  • 借助科学计算库 math\scipy\numpy
import math   # 导入库
print(math.exp(1))   # 指数运算 e^x
print(math.log2(2))  # 对数运算
print(math.sqrt(4))  # 开平方运算  等价于4^0.5
2.718281828459045
1.0
2.0
import numpy as np
a = [1, 2, 3, 4, 5]
print(np.mean(a))    # 求均值
print(np.median(a))  # 求中位数
print(np.std(a))     # 求标准差
3.0
3.0
1.4142135623730951

第二部分 字符串类型

2.1 字符串的表达

  • 用""或’'括起来的任意字符
print("Python")
print('Python')
Python
Python
  • 字符串中有双引号或单引号的情况

双中有单

print("I'm 18 years old")
I'm 18 years old

单中有双

print('"Python" is good')
"Python" is good

双中有双,单中有单——转义符  \

# print(""Python" is good")
print("\"Python\" is good")  # \ 我是个字符呀
"Python" is good

转义符可以用来换行继续输入

# 等等,我还没完事!
s = "py\
thon"                    
print(s)
python

2.2 字符串的性质

2.2.1 字符串的索引
s = "My name is Peppa Pig"

变量名[位置编号]

  • 正向索引——从零开始递增
  • 位置编号不能超过字符串的长度
print(s[0])   
print(s[2])
print(s[5])
M
 
m
s = "My name is Peppa Pig"
  • 反向索引——从-1开始递减
print(s[-1])
print(s[-3])
print(s[-5])
g
P
a

索引只能获得一个字符,如何获得多个字符?

2.2.2 字符串的切片

变量名[开始位置:结束位置:切片间隔]

  • 切片间隔如不设置默认为1,可省略
  • 切片范围不包含结束位置
s = "Python"
print(s[0:3:1])  #间隔为1
Pyt
print(s[0:3])
Pyt
print(s[0:3:2])  #间隔为2
Pt
  • 起始位置是0 可以省略
  • 结束位置省略,代表可以取到最后一个字符
  • 可以使用反向索引
s = "Python"
print(s[0:6])
Python
print(s[:6])
Python
print(s[:])
Python
print(s[-6:])
Python

反向切片

  • 起始位置是-1也可以省略
  • 结束位置省略,代表可以取到第一个字符
s = "123456789"
print(s[-1:-10:-1])
987654321
print(s[:-10:-1])
987654321
print(s[::-1])
987654321

2.3 字符串操作符

2.3.1 字符串的拼接
  • 字符串1+字符串2
a = "I love "
b = "my wife "
a+b        
'I love my wife '
2.3.2 字符串的成倍复制
  • 字符串 * n   n * 字符串
c = a+b
print(c*3)
print(3*c)
I love my wife I love my wife I love my wife 
I love my wife I love my wife I love my wife 
2.2.3 成员运算
  • 子集in全集   任何一个连续的切片都是原字符串的子集
folk_singers = "Peter, Paul and Mary"    
"Peter" in folk_singers
True
"PPM" in folk_singers
False
  • 遍历字符串字符   for 字符 in 字符串
for s in "Python":
    print(s)
P
y
t
h
o
n

2.4 字符串处理函数

2.4.1 字符串的长度
  • 所含字符的个数
s = "python"
len(s)
6
2.4.2 字符编码

将中文字库,英文字母、数字、特殊字符等转化成计算机可识别的二进制数

  • 每个单一字符对应一个唯一的互不重复的二进制编码
  • Python 中使用的是Unicode编码

将字符转化为Unicode码——ord(字符)

print(ord("1"))
print(ord("a"))
print(ord("*"))
print(ord("中"))
print(ord("国"))
49
97
42
20013
22269

将Unicode码转化为字符——chr(Unicode码)

print(chr(1010))
print(chr(10000))
print(chr(12345))
print(chr(23456))
ϲ
✐
〹
宠

2.5 字符串的处理方法

2.5.1 字符串的分割——字符串.split(分割字符)
  • 返回一个列表
  • 原字符串不变

上述特性适合以下所有字符串处理方法

languages = "Python C C++ Java PHP R"
languages_list = languages.split(" ")
print(languages_list)
print(languages)
['Python', 'C', 'C++', 'Java', 'PHP', 'R']
Python C C++ Java PHP R
2.5.2 字符串的聚合——“聚合字符”.join(可迭代数据类型)
  • 可迭代类型 如:字符串、列表
s = "12345"
s_join = ",".join(s)
s_join
'1,2,3,4,5'
  • 序列类型的元素必须是字符类型
# s = [1, 2, 3, 4, 5]
s = ["1", "2", "3", "4", "5"]
"*".join(s) 
'1*2*3*4*5'
3.5.3 删除两端特定字符——字符串.strip(删除字符)
  • strip从两侧开始搜索,遇到指定字符执行删除,遇到非指定字符,搜索停止
  • 类似的还有左删除lstrip和右删除rstrip
s = "      I have many blanks     "
print(s.strip(" "))                        # 还有吗?hahaha
print(s.lstrip(" "))
print(s.rstrip(" "))
print(s)
I have many blanks
I have many blanks     
      I have many blanks
      I have many blanks     
3.5.4 字符串的替换——字符串.replace(“被替换”,“替换成”)
s = "Python is coming"
s1 = s.replace("Python","Py")
print(s1)
Py is coming
3.5.5 字符串统计——字符串.count(“待统计字符串”)
s = "Python is an excellent language"
print("an:", s.count("an"))
print("e:", s.count("e"))
an: 2
e: 4
3.3.6 字符串字母大小写
  • 字符串.upper() 字母全部大写
s = "Python"
s.upper()
'PYTHON'
  • 字符串.lower() 字母全部小写
print(s.lower())
print(s)
python
Python
  • 字符串.title()首字母大写
s.title()
'Python'

第三部分 布尔类型 TRUE or False

3.1 逻辑运算的结果

a = 10
print(a > 8)
print(a == 12)
print(a < 5)
True
False
False
  • any() all()
print(any([False,1,0,None]))   # 0 False None 都是无
print(all([False,1,0,None]))
True
False

3.2 指示条件

n = 2800
while True:
    m = eval(input("请输入一个正整数:"))
    if m == n:
        print("你猜对啦")
        break
    elif m > n:
        print("太大了")
    else:
        print("太小了")  
请输入一个正整数:28
太小了
请输入一个正整数:2800
你猜对啦

3.3 作为掩码

import numpy as np
x = np.array([[1, 3, 2, 5, 7]])   # 定义 numpy数组
print(x > 3)
x[x > 3]
[[False False False  True  True]]





array([5, 7])

第四部分 类型判别及类型转换

4.1 类型判别

  • type(变量)
age = 20
name = "Ada"
print(type(age))
print(type(name))
<class 'int'>
<class 'str'>
  • isinstance(变量,预判类型) 承认继承
  • 变量类型是预判类型的子类型,则为真,否则为假
print(isinstance(age, int))        # 承认继承
True
print(isinstance(age, object))
print(isinstance(name, object))    # object 是老祖宗
True
True
  • 字符串检查方法

字符串.isdigit()字符是否只有数字组成

age = "20"
name = "Ada"
age.isdigit()
True
name.isdigit()
False

字符串.isalpha()字符是否只有字母组成

name.isalpha()
True
age.isalpha()
False

字符串.isalnum()字符是否只有数字和字母组成

"Ada20".isalnum()    # 比如可用于判断用户名是否合法
True

4.2 类型转换

  • 数字类型转字符串  str(数字类型)
age = 20
print("My age is "+str(age))
My age is 20
  • 仅有数字组成的字符串转数字  int()  float()  eval()
s1 = "20"
s2 = "10.1"
int(s1)     # 仅整型
# int(s2)
20
float(s1)
20.0
float(s2)
10.1
eval(s1)  #只去掉引号
20
eval(s2)
10.1

第四章 组合数据类型

4.1 列表

4.1.1 列表的表达

  • 序列类型:内部元素有位置关系,能通过位置序号访问其中元素
  • 列表是一个可以使用多种类型元素,支持元素的增、删、查、改操作的序列类型
ls = ["Python", 1989, True, {"version": 3.7}]
ls
['Python', 1989, True, {'version': 3.7}]
  • 另一种产生方式:list(可迭代对象)
  • 可迭代对象包括:字符串、元组、集合、range()等

字符串转列表

list("人工智能是未来的趋势")
['人', '工', '智', '能', '是', '未', '来', '的', '趋', '势']

元组转列表

list(("我", "们", "很", "像"))
['我', '们', '很', '像']

集合转列表

list({"李雷", "韩梅梅", "Jim", "Green"})
['Green', 'Jim', '李雷', '韩梅梅']

特殊的range()

for i in [0, 1, 2, 3, 4, 5]:
    print(i)
0
1
2
3
4
5
for i in range(6):
    print(i)
0
1
2
3
4
5
  • range(起始数字,中止数字,数字间隔)

如果起始数字缺省,默认为0

必须包含中止数字

数字间隔缺省,默认为1

for i in range(1, 11, 2):
    print(i)
1
3
5
7
9
  • range()转列表
list(range(1, 11, 2))
[1, 3, 5, 7, 9]

4.1.2 列表的性质

  • 列表的长度——len(列表)
ls = [1, 2, 3, 4, 5]
len(ls)
5
  • 列表的索引——与同为序列类型的字符串完全相同

变量名[位置编号]

正向索引从0开始
反向索引从-1开始

cars = ["BYD", "BMW", "AUDI", "TOYOTA"]
print(cars[0])
print(cars[-1])
BYD
TOYOTA
  • 列表的切片

变量名[开始位置:结束位置:切片间隔]

cars = ["BYD", "BMW", "AUDI", "TOYOTA"]
  • 正向切片
print(cars[:3])     # 前三个元素,开始位置缺省,默认为0;切片间隔缺省,默认为1
['BYD', 'BMW', 'AUDI']
print(cars[1:4:2])  # 第二个到第四个元素 前后索引差为2
['BMW', 'TOYOTA']
print(cars[:])      # 获取整个列表,结束位置缺省,默认取值到最后
['BYD', 'BMW', 'AUDI', 'TOYOTA']
print(cars[-4:-2])  # 获取前两个元素
['BYD', 'BMW']
  • 反向切片
cars = ["BYD", "BMW", "AUDI", "TOYOTA"]
print(cars[:-4:-1])      # 开始位置缺省,默认为-1
print(cars[::-1])        # 获得反向列表
['TOYOTA', 'AUDI', 'BMW']
['TOYOTA', 'AUDI', 'BMW', 'BYD']

4.1.3 列表的操作符

  • 用** list1+lis2 **的形式实现列表的拼接
a = [1, 2]
b = [3, 4]
a+b            # 该用法用的不多
[1, 2, 3, 4]
  • 用 n*list 或 list*n 实现列表的成倍复制

初始化列表的一种方式

[0]*10
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

4.1.4 列表的操作方法

1、增加元素

  • 在末尾增加元素——列表.append(待增元素)
languages = ["Python", "C++", "R"]
languages.append("Java")
languages
['Python', 'C++', 'R', 'Java']
  • 在任意位置插入元素——列表.insert(位置编号,待增元素)
    在位置编号相应元素前插入待增元素
languages.insert(1, "C")
languages
['Python', 'C', 'C++', 'R', 'Java']
  • 在末尾整体并入另一列表——列表1.extend(列表2)

append 将列表2整体作为一个元素添加到列表1中

languages.append(["Ruby", "PHP"])
languages
['Python', 'C', 'C++', 'R', 'Java', ['Ruby', 'PHP']]

extend 将待列表2内的元素逐个添加到列表1中

languages = ['Python', 'C', 'C++', 'R', 'Java']
languages.extend(["Ruby", "PHP"])
languages
['Python', 'C', 'C++', 'R', 'Java', 'Ruby', 'PHP']

2、删除元素

  • 删除列表i位置的元素  列表.pop(位置)
languages = ['Python', 'C', 'C++', 'R', 'Java']
languages.pop(1)
languages
['Python', 'C++', 'R', 'Java']
  • 不写位置信息,默认删除最后一个元素
languages.pop()
languages
['Python', 'C++', 'R']
  • 删除列表中的第一次出现的待删元素 列表.remove(待删元素)
languages = ['Python', 'C', 'R', 'C', 'Java']
languages.remove("C")    
languages
['Python', 'R', 'C', 'Java']
languages = ['Python', 'C', 'R', 'C', 'Java']
while "C" in languages:
    languages.remove("C")    
languages   
['Python', 'R', 'Java']

3、查找元素

  • 列表中第一次出现待查元素的位置 列表.index(待查元素)
languages = ['Python', 'C', 'R','Java']
idx = languages.index("R") 
idx
2

4、修改元素

  • 通过"先索引后赋值"的方式,对元素进行修改 列表名[位置]=新值
languages = ['Python', 'C', 'R','Java']
languages[1] = "C++"
languages
['Python', 'C++', 'R', 'Java']

5、列表的复制

  • 错误的方式
languages = ['Python', 'C', 'R','Java']
languages_2 = languages
print(languages_2)
['Python', 'C', 'R', 'Java']
languages.pop()
print(languages)
print(languages_2)
['Python', 'C', 'R']
['Python', 'C', 'R']
  • 正确的方式——浅拷贝

  • 方法1:列表.copy()

languages = ['Python', 'C', 'R','Java']
languages_2 = languages.copy()
languages.pop()
print(languages)
print(languages_2)
['Python', 'C', 'R']
['Python', 'C', 'R', 'Java']
  • 方法2:列表[ : ]
languages = ['Python', 'C', 'R','Java']
languages_3 = languages[:]
languages.pop()
print(languages)
print(languages_3)
['Python', 'C', 'R']
['Python', 'C', 'R', 'Java']

6、列表的排序

  • 使用列表.sort()对列表进行永久排序
  • 直接在列表上进行操作,无返回值
ls = [2, 5, 2, 8, 19, 3, 7]
ls.sort()
ls
[2, 2, 3, 5, 7, 8, 19]
  • 递减排列
ls.sort(reverse = True)
ls
[19, 8, 7, 5, 3, 2, 2]
  • 使用sorted(列表)对列表进行临时排序
  • 原列表保持不变,返回排序后的列表
ls = [2, 5, 2, 8, 19, 3, 7]
ls_2 = sorted(ls)
print(ls)
print(ls_2)
[2, 5, 2, 8, 19, 3, 7]
[19, 8, 7, 5, 3, 2, 2]
sorted(ls, reverse = True)
[19, 8, 7, 5, 3, 2, 2]

7、列表的翻转

  • 使用列表.reverse()对列表进行永久翻转
  • 直接在列表上进行操作,无返回值
ls = [1, 2, 3, 4, 5]
print(ls[::-1])
ls
[5, 4, 3, 2, 1]





[1, 2, 3, 4, 5]
ls.reverse()
ls
[5, 4, 3, 2, 1]

8、使用for循环对列表进行遍历

ls = [1, 2, 3, 4, 5]
for i in ls:
    print(i)
1
2
3
4
5

4.2 元组

4.2.1 元组的表达

  • 元组是一个可以使用多种类型元素,一旦定义,内部元素不支持增、删和修改操作的序列类型

通俗的讲,可以将元组视作“不可变的列表”

names = ("Peter", "Pual", "Mary")

4.2.2 元组的操作

  • 不支持元素增加、元素删除、元素修改操作
  • 其他操作与列表的操作完全一致

4.2.3 元组的常见用处

打包与解包

  • 例1
def f1(x):              # 返回x的平方和立方
    return x**2, x**3   # 实现打包返回

print(f1(3))
print(type(f1(3)))      # 元组类型
(9, 27)
<class 'tuple'>
a, b = f1(3)            # 实现解包赋值 
print(a)
print(b)
9
27
  • 例2
numbers = [201901, 201902, 201903]
name = ["小明", "小红", "小强"]
list(zip(numbers,name))
[(201901, '小明'), (201902, '小红'), (201903, '小强')]
for number,name in zip(numbers,name):   # 每次取到一个元组,立刻进行解包赋值
    print(number, name)
201901 小明
201902 小红
201903 小强

4.3 字典

4.3.1 字典的表达

  • 映射类型: 通过“键”-“值”的映射实现数据存储和查找
  • 常规的字典是无序的
students = {201901: '小明', 201902: '小红', 201903: '小强'}
students

字典键的要求

  • 1、字典的键不能重复
students = {201901: '小明', 201901: '小红', 201903: '小强'}
students
{201901: '小红', 201903: '小强'}
  • 2、字典的键必须是不可变类型,如果键可变,就找不到对应存储的值了

  • 不可变类型:数字、字符串、元组。  一旦确定,它自己就是它自己,变了就不是它了。

  • 可变类型:列表、字典、集合。  一旦确定,还可以随意增删改。

d1 = {1: 3}
d2 = {"s": 3}
d3 = {(1,2,3): 3}
d = {[1, 2]: 3}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-68-bf7f06622b3f> in <module>
----> 1 d = {[1, 2]: 3}


TypeError: unhashable type: 'list'
d = {{1:2}: 3}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-69-188e5512b5fe> in <module>
----> 1 d = {{1:2}: 3}


TypeError: unhashable type: 'dict'
d = {{1, 2}: 3}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-70-c2dfafc1018a> in <module>
----> 1 d = {{1, 2}: 3}


TypeError: unhashable type: 'set'

4.3.2 字典的性质

  • 字典的长度——键值对的个数
students = {201901: '小明', 201902: '小红', 201903: '小强'}
len(students)
3
  • 字典的索引

通过 字典[键] 的形式来获取对应的值

students = {201901: '小明', 201902: '小红', 201903: '小强'}
students[201902]
'小红'

4.3.3 字典的操作方法

1、增加键值对

  • 变量名[新键] = 新值
students = {201901: '小明', 201902: '小红', 201903: '小强'}
students[201904] = "小雪"
students
{201901: '小明', 201902: '小红', 201903: '小强', 201904: '小雪'}

2、删除键值对

  • 通过del 变量名[待删除键]
students = {201901: '小明', 201902: '小红', 201903: '小强'}
del students[201903]
students
{201901: '小明', 201902: '小红'}
  • 通过变量名.pop(待删除键)
students = {201901: '小明', 201902: '小红', 201903: '小强'}
value = students.pop(201903)   # 删除键值对,同时获得删除键值对的值
print(value)
print(students)
小强
{201901: '小明', 201902: '小红'}
  • 变量名.popitem() 随机删除一个键值对,并以元组返回删除键值对
students = {201901: '小明', 201902: '小红', 201903: '小强'}
key, value = students.popitem()
print(key, value)
print(students)
201903 小强
{201901: '小明', 201902: '小红'}

3、修改值

  • 通过先索引后赋值的方式对相应的值进行修改
students = {201901: '小明', 201902: '小红', 201903: '小强'}
students[201902] = "小雪"
students
{201901: '小明', 201902: '小雪', 201903: '小强'}

4、d.get( )方法

d.get(key,default) 从字典d中获取键key对应的值,如果没有这个键,则返回default

  • 小例子:统计"牛奶奶找刘奶奶买牛奶"中字符的出现频率
s = "牛奶奶找刘奶奶买牛奶"
d = {}
print(d)
for i in s:
    d[i] = d.get(i, 0)+1
    print(d)
# print(d)
{}
{'牛': 1}
{'牛': 1, '奶': 1}
{'牛': 1, '奶': 2}
{'牛': 1, '奶': 2, '找': 1}
{'牛': 1, '奶': 2, '找': 1, '刘': 1}
{'牛': 1, '奶': 3, '找': 1, '刘': 1}
{'牛': 1, '奶': 4, '找': 1, '刘': 1}
{'牛': 1, '奶': 4, '找': 1, '刘': 1, '买': 1}
{'牛': 2, '奶': 4, '找': 1, '刘': 1, '买': 1}
{'牛': 2, '奶': 5, '找': 1, '刘': 1, '买': 1}

5、d.keys( ) d.values( )方法

students = {201901: '小明', 201902: '小红', 201903: '小强'}
print(list(students.keys()))
print(list(students.values()))
[201901, 201902, 201903]
['小明', '小红', '小强']

6、d.items( )方法及字典的遍历

print(list(students.items()))
for k, v in students.items():
    print(k, v)
[(201901, '小明'), (201902, '小红'), (201903, '小强')]
201901 小明
201902 小红
201903 小强

4.4 集合

4.4.1 集合的表达

  • 一系列互不相等元素的无序集合
  • 元素必须是不可变类型:数字,字符串或元组,可视作字典的键
  • 可以看做是没有值,或者值为None的字典
students = {"小明", "小红", "小强", "小明"}   #可用于去重
students
{'小强', '小明', '小红'}

4.4.2 集合的运算

  • 小例子 通过集合进行交集并集的运算
Chinese_A = {"刘德华", "张学友", "张曼玉", "钟楚红", "古天乐", "林青霞"}
Chinese_A
{'刘德华', '古天乐', '张学友', '张曼玉', '林青霞', '钟楚红'}
Math_A = {"林青霞", "郭富城", "王祖贤", "刘德华", "张曼玉", "黎明"}
Math_A
{'刘德华', '张曼玉', '林青霞', '王祖贤', '郭富城', '黎明'}
  • 语文和数学两门均为A的学员
  • S & T 返回一个新集合,包括同时在集合S和T中的元素
Chinese_A & Math_A
{'刘德华', '张曼玉', '林青霞'}
  • 语文或数学至少一门为A的学员
  • S | T 返回一个新集合,包括集合S和T中的所有元素
Chinese_A | Math_A
{'刘德华', '古天乐', '张学友', '张曼玉', '林青霞', '王祖贤', '郭富城', '钟楚红', '黎明'}
  • 语文数学只有一门为A的学员
  • S ^ T 返回一个新集合,包括集合S和T中的非共同元素
Chinese_A ^ Math_A
{'古天乐', '张学友', '王祖贤', '郭富城', '钟楚红', '黎明'}
  • 语文为A,数学不为A的学员
  • S - T 返回一个新集合,包括在集合S但不在集合T中的元素
Chinese_A - Math_A
{'古天乐', '张学友', '钟楚红'}
  • 数学为A,语文不为A的学员
Math_A - Chinese_A
{'王祖贤', '郭富城', '黎明'}

4.4.3 集合的操作方法

  • 增加元素——S.add(x)
stars = {"刘德华", "张学友", "张曼玉"}
stars.add("王祖贤")
stars
{'刘德华', '张学友', '张曼玉', '王祖贤'}
  • 移除元素——S.remove(x)
stars.remove("王祖贤")
stars
{'刘德华', '张学友', '张曼玉'}
  • 集合的长度——len(S)
len(stars)
3
  • 集合的遍历——借助for循环
for star in stars:
    print(star)
张学友
张曼玉
刘德华

第五章 程序控制结构

非顺序式的程序控制,往往需要根据一定的条件,决定程序运行的路线。因此,我们首先来认识一下什么叫条件测试。

第一部分 条件测试

1、比较运算

a = 10
b = 8
print(a > b)     # 大于
print(a < b)     # 小于
print(a >= b)    # 大于等于
print(a <= b)    # 小于等于
print(a == b)    # 等于
print(a != b)    # 不等于
True
False
True
False
False
True
  • 非空
ls = [1]
if ls:            # 数据结构不为空、变量不为0、None、False 则条件成立
    print("非空")
else:
    print("空的")
非空

2、逻辑运算

  • 与、或、非
a = 10
b = 8
c = 12
print((a > b) and (b > c))    # 与
print((a > b) or (b > c))     # 或
print(not(a > b))             # 非
False
True
False
  • 复合逻辑运算的优先级
    非 > 与 > 或
print(True or True and False)
True
print((True or True) and False)
False

3、存在运算

元素 in 列表/字符串

cars = ["BYD", "BMW", "AUDI", "TOYOTA"]
print("BMW" in cars)
print("BENZ" in cars)
True
False

元素 not in 列表/字符串

print("BMW" not in cars)
print("BENZ" not in cars)
False
True

第二部分 分支结构——if语句

1、单分支

模板

if 条件:
  缩进的代码块

age = 8
if age > 7:
    print("孩子,你该上学啦!")
孩子,你该上学啦!

2、二分支

模板

if 条件:
  缩进的代码块
else:
  缩进的代码块

age = 6
if age > 7:
    print("孩子,你该上学啦!")
else:
    print("再玩两年泥巴!")
再玩两年泥巴!

3、 多分支

模板

if 条件:
  缩进的代码块
elif 条件:
  缩进的代码块
elif 条件:
  缩进的代码块

else:
  缩进的代码块

age = 38
if age < 7:
    print("再玩两年泥巴")
elif age < 13:
    print("孩子,你该上小学啦")
elif age < 16:
    print("孩子,你该上初中了")
elif age < 19:
    print("孩子,你该上高中了")
elif age < 23:
    print("大学生活快乐")
elif age < 60:
    print("辛苦了,各行各业的工作者们")
else:     # 有时为了清楚,也可以写成elif age >= 60:
    print("享受退休生活吧") 
辛苦了,各行各业的工作者们

不管多少分支,最后只执行一个分支

4、嵌套语句

题目:年满18周岁,在非公共场合方可抽烟,判断某种情形下是否可以抽烟

age = eval(input("请输入年龄"))
if age > 18:
    is_public_place = bool(eval(input("公共场合请输入1,非公共场合请输入0")))
    print(is_public_place)
    if not is_public_place:
        print("可以抽烟")
    else:
        print("禁止抽烟")        
else:
    print("禁止抽烟")            
请输入年龄16
禁止抽烟

第三部分 遍历循环——for 循环

主要形式:

  • for 元素 in 可迭代对象:
      执行语句

执行过程:

  • 从可迭代对象中,依次取出每一个元素,并进行相应的操作

1、直接迭代——列表[ ]、元组( )、集合{ }、字符串" "

graduates = ("李雷", "韩梅梅", "Jim")
for graduate in graduates:
    print("Congratulations, "+graduate)
Congratulations, 李雷
Congratulations, 韩梅梅
Congratulations, Jim

2、变换迭代——字典

students = {201901: '小明', 201902: '小红', 201903: '小强'}
for k, v in students.items():
    print(k, v)
for student in students.keys():
    print(student)  
201901 小明
201902 小红
201903 小强
201901
201902
201903

3、range()对象

res=[]
for i in range(10000):
    res.append(i**2)
print(res[:5])
print(res[-1])
[0, 1, 4, 9, 16]
99980001
res=[]
for i in range(1,10,2):
    res.append(i**2)
print(res)
[1, 9, 25, 49, 81]

循环控制:break 和 continue

  • break 结束整个循环
product_scores = [89, 90, 99, 70, 67, 78, 85, 92, 77, 82] # 1组10个产品的性能评分
 # 如果低于75分的超过1个,则该组产品不合格
i = 0
for score in product_scores:
    if score < 75:
        i += 1 
    if i == 2:
        print("产品抽检不合格")
        break
产品抽检不合格
  • continue 结束本次循环
product_scores = [89, 90, 99, 70, 67, 78, 85, 92, 77, 82] # 1组10个产品的性能评分
 # 如果低于75分,输出警示
print(len(product_scores))
for i in range(len(product_scores)):
    if product_scores[i] >= 75:
        continue 
    print("第{0}个产品,分数为{1},不合格".format(i, product_scores[i]))
10
第3个产品,分数为70,不合格
第4个产品,分数为67,不合格

for 与 else的配合

如果for 循环全部执行完毕,没有被break中止,则运行else块

product_scores = [89, 90, 99, 70, 67, 78, 85, 92, 77, 82] # 1组10个产品的性能评分
 # 如果低于75分的超过1个,则该组产品不合格
i = 0
for score in product_scores:
    if score < 75:
        i+=1 
    if i == 2:
        print("产品抽检不合格")
        break
else:
    print("产品抽检合格")
产品抽检不合格

第四部分 无限循环——while 循环

4.1 为什么要用while 循环

  • 经典题目:猜数字
albert_age = 18
#第1次
guess = int(input(">>:"))
if guess > albert_age :
    print("猜的太大了,往小里试试...")
elif guess < albert_age :
    print("猜的太小了,往大里试试...")
else:
    print("恭喜你,猜对了...")
#第2次
guess = int(input(">>:"))
if guess > albert_age :
    print("猜的太大了,往小里试试...")
elif guess < albert_age :
    print("猜的太小了,往大里试试...")
else:
    print("恭喜你,猜对了...")

代码可能需要重复执行,可是又不知道具体要执行多少次

4.2 while循环的一般形式

主要形式:

  • while 判断条件:
      执行语句
    条件为真,执行语句
    条件为假,while 循环结束
albert_age = 18 
guess = int(input(">>:"))
while guess != albert_age: 
    if guess > albert_age :
        print("猜的太大了,往小里试试...")
    elif guess < albert_age :
        print("猜的太小了,往大里试试...")
    guess = int(input(">>:"))
print("恭喜你,猜对了...")

4.3 while与风向标

albert_age = 18 
flag = True     # 布尔类型
while flag:
    guess = int(input(">>:"))
    if guess > albert_age :
        print("猜的太大了,往小里试试...")
    elif guess < albert_age :
        print("猜的太小了,往大里试试...")
    else:
        print("恭喜你,猜对了...")
        flag = False    # 当诉求得到满足,就让风向变一下
flag=True  
while flag:  
    pass  
    while flag:  
        pass  
        while flag:     
            flag=False     # 循环逐层判断,当flag为false时,循环会逐层退出

4.4 while 与循环控制 break、continue

albert_age = 18 
while True:
    guess = int(input(">>:"))
    if guess > albert_age :
        print("猜的太大了,往小里试试...")
    elif guess < albert_age :
        print("猜的太小了,往大里试试...")
    else:
        print("恭喜你,猜对了...")
        break    # 当诉求得到满足,就跳出循环

输出10以内的奇数

i = 0
while i < 10:
    i += 1
    if i % 2 == 0:
        continue       # 跳出本次循环,进入下一次循环
    print(i)
1
3
5
7
9

4.5 while与else

如果while 循环全部执行完毕,没有被break中止,则运行else块

count = 0
while count <= 5 :
    count += 1
    print("Loop",count)
else:
    print("循环正常执行完啦")
Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
Loop 6
循环正常执行完啦

4.6 再看两个例子

【小例子】删除列表中的特定值

pets = ["dog", "cat", "dog", "pig", "goldfish", "rabbit", "cat"]
while "cat" in pets:
    pets.remove("cat")
pets
['dog', 'dog', 'pig', 'goldfish', 'rabbit']

【小例子】将未读书籍列表中书名分别输出后,存入已读书籍列表

not_read = ["红楼梦", "水浒传", "三国演义", "西游记"]
have_read = []
while not_read:     # not_read非空,循环继续,否则中止
    book = not_read.pop()
    have_read.append(book)
    print("我已经读过《{}》了".format(book))
print(not_read)
print(have_read)
我已经读过《西游记》了
我已经读过《三国演义》了
我已经读过《水浒传》了
我已经读过《红楼梦》了
[]
['西游记', '三国演义', '水浒传', '红楼梦']

第五部分 控制语句注意问题

5.1 尽可能减少多层嵌套

  • 可读性差,容易把人搞疯掉
if 条件:
    执行语句
    if 条件:
        执行语句
        if...

5.2 避免死循环

条件一直成立,循环永无止境

# while True:
    # print("天地之渺渺,时间之无限")

5.3 封装过于复杂的判断条件

如果条件判断里的表达式过于复杂

出现了太多的 not/and/or等

导致可读性大打折扣

考虑将条件封装为函数

a, b, c, d, e = 10, 8, 6, 2, 0
if (a > b) and (c >d) and (not e):
    print("我已经晕鸟")
numbers = (10, 8, 6, 2, 0)


def judge(num):
    a, b, c, d, e = num
    x = a > b
    y = c > d
    z = not e
    return x and y and z


if judge(numbers):
    print("就是这个feel,biu倍儿爽")
就是这个feel,biu倍儿爽

第六章 函数

6.1 函数的定义及调用

6.1.1 为什么要用函数

1、提高代码复用性——抽象出来,封装为函数

2、将复杂的大问题分解成一系列小问题,分而治之——模块化设计的思想

3、利于代码的维护和管理

顺序式

# 5的阶乘
n = 5
res = 1
for i in range(1, n+1):
    res *= i
print(res)

# 20的阶乘
n = 20
res = 1
for i in range(1, n+1):
    res *= i
print(res)
120
2432902008176640000

抽象成函数

def factoria(n):
    res = 1
    for i in range(1,n+1):
        res *= i
    return res


print(factoria(5))
print(factoria(20))
120
2432902008176640000

6.1.2 函数的定义及调用

白箱子:输入——处理——输出

三要素:参数、函数体、返回值

1、定义

def  函数名(参数):

  函数体

  return 返回值

# 求正方形的面积
def area_of_square(length_of_side):
    square_area = pow(length_of_side, 2)
    return square_area    

2、调用

函数名(参数)

area = area_of_square(5)
area
25

6.1.3 参数传递

0、形参与实参

  • 形参(形式参数):函数定义时的参数,实际上就是变量名

  • 实参(实际参数):函数调用时的参数,实际上就是变量的值

1、位置参数

  • 严格按照位置顺序,用实参对形参进行赋值(关联)

  • 一般用在参数比较少的时候

def function(x, y, z):
    print(x, y, z)
    
    
function(1, 2, 3)    # x = 1; y = 2; z = 3
1 2 3
  • 实参与形参个数必须一一对应,一个不能多,一个不能少
function(1, 2)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-6-2a7da6ff9675> in <module>
----> 1 function(1, 2)


TypeError: function() missing 1 required positional argument: 'z'
function(1, 2, 3, 4)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-8-748d3d0335e6> in <module>
----> 1 function(1, 2, 3, 4)


TypeError: function() takes 3 positional arguments but 4 were given

2、关键字参数

  • 打破位置限制,直呼其名的进行值的传递(形参=实参)

  • 必须遵守实参与形参数量上一一对应

  • 多用在参数比较多的场合

def function(x, y, z):
    print(x, y, z)
    
    
function(y=1, z=2, x=3)    # x = 1; y = 2; z = 3
3 1 2
  • 位置参数可以与关键字参数混合使用

  • 但是,位置参数必须放在关键字参数前面

function(1, z=2, y=3)
1 3 2
function(1, 2, z=3)
1 2 3
  • 不能为同一个形参重复传值
def function(x, y, z):
    print(x, y, z)


function(1, z=2, x=3)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-12-f385272db011> in <module>
      3 
      4 
----> 5 function(1, z=2, x=3)


TypeError: function() got multiple values for argument 'x'

3、默认参数

  • 在定义阶段就给形参赋值——该形参的常用值

  • 在定义阶段就给形参赋值——该形参的常用值

  • 默认参数必须放在非默认参数后面

  • 调用函数时,可以不对该形参传值

  • 机器学习库中类的方法里非常常见

def register(name, age, sex="male"):
    print(name, age, sex)


register("大杰仔", 18)
大杰仔 18 male
  • 也可以按正常的形参进行传值
register("林志玲", 38, "female")
林志玲 38 female
  • 默认参数应该设置为不可变类型(数字、字符串、元组)
def function(ls=[]):
    print(id(ls))
    ls.append(1)
    print(id(ls))
    print(ls)


function()
1759752744328
1759752744328
[1]
function()
1759752744328
1759752744328
[1, 1]
function()
1759752744328
1759752744328
[1, 1, 1]
def function(ls="Python"):
    print(id(ls))
    ls += "3.7"
    print(id(ls))
    print(ls)
    
    
function()
1759701700656
1759754352240
Python3.7
function()
1759701700656
1759754353328
Python3.7
function()
1759701700656
1759754354352
Python3.7
  • 让参数变成可选的
def name(first_name, last_name, middle_name=None):
    if middle_name:
        return first_name+middle_name+last_name
    else:
        return first_name+last_name
    
    
print(name("大","仔"))
print(name("大", "仔", "杰"))
大仔
大杰仔

*4、可变长参数 args

  • 不知道会传过来多少参数 *args

  • 该形参必须放在参数列表的最后

def foo(x, y, z, *args):
    print(x, y ,z)
    print(args)
    
    
foo(1, 2, 3, 4, 5, 6)    # 多余的参数,打包传递给args
1 2 3
(4, 5, 6)
  • 实参打散
def foo(x, y, z, *args):
    print(x, y ,z)
    print(args)

    
foo(1, 2, 3, [4, 5, 6])    
1 2 3
([4, 5, 6],)
foo(1, 2, 3, *[4, 5, 6])   # 打散的是列表、字符串、元组或集合
1 2 3
(4, 5, 6)

**5、可变长参数 ****kwargs

def foo(x, y, z, **kwargs):
    print(x, y ,z)
    print(kwargs)
    
    
foo(1, 2, 3, a=4, b=5, c=6)    #  多余的参数,以字典的形式打包传递给kwargs
1 2 3
{'a': 4, 'b': 5, 'c': 6}
  • 字典实参打散
def foo(x, y, z, **kwargs):
    print(x, y ,z)
    print(kwargs)

    
foo(1, 2, 3, **{"a": 4, "b": 5, "c":6})
1 2 3
{'a': 4, 'b': 5, 'c': 6}
  • 可变长参数的组合使用
def foo(*args, **kwargs):
    print(args)
    print(kwargs)
    
    
foo(1, 2, 3, a=4, b=5, c=6) 
(1, 2, 3)
{'a': 4, 'b': 5, 'c': 6}

6.1.4 函数体与变量作用域

  • 函数体就是一段只在函数被调用时,才会执行的代码,代码构成与其他代码并无不同

  • 局部变量——仅在函数体内定义和发挥作用

def multipy(x, y):
    z = x*y
    return z   


multipy(2, 9)
print(z)            # 函数执行完毕,局部变量z已经被释放掉了
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-29-9a7fd4c4c0a9> in <module>
      5 
      6 multipy(2, 9)
----> 7 print(z)            # 函数执行完毕,局部变量z已经被释放掉了


NameError: name 'z' is not defined
  • 全局变量——外部定义的都是全局变量

  • 全局变量可以在函数体内直接被使用

n = 3
ls = [0]
def multipy(x, y):
    z = n*x*y
    ls.append(z)
    return z   


print(multipy(2, 9))
ls
54





[0, 54]
  • 通过global 在函数体内定义全局变量
def multipy(x, y):
    global z
    z = x*y
    return z 


print(multipy(2, 9))
print(z)
18
18

6.1.5 返回值

1、单个返回值

def foo(x):
    return x**2


res = foo(10)
res
100

2、多个返回值——以元组的形式

def foo(x):
    return 1, x, x**2, x**3    # 逗号分开,打包返回


print(foo(3))
(1, 3, 9, 27)
a, b , c, d = foo(3)       # 解包赋值
print(a)
print(b)
print(c)
print(d)
1
3
9
27

3、可以有多个return 语句,一旦其中一个执行,代表了函数运行的结束

def is_holiday(day):
    if day in ["Sunday", "Saturday"]:
        return "Is holiday"
    else:
        return "Not holiday"
    print("啦啦啦德玛西亚,啦啦啦啦")       # 你丫根本没机会运行。。。
    
    
print(is_holiday("Sunday"))
print(is_holiday("Monday"))
Is holiday
Not holiday

4、没有return语句,返回值为None

def foo():
    print("我是孙悟空")

res = foo()
print(res)
我是孙悟空
None

6.1.6 几点建议

1、函数及其参数的命名参照变量的命名

  • 字母小写及下划线组合

  • 有实际意义

2、应包含简要阐述函数功能的注释,注释紧跟函数定义后面

def foo():
    # 这个函数的作用是为了给大家瞅一瞅,你瞅啥,瞅你咋地。。。。
    pass

3、函数定义前后各空两行

def f1():
    pass

                 # 空出两行,以示清白
def f2():
    pass


def f3(x=3):    # 默认参数赋值等号两侧不需加空格
    pass


# ...

4、默认参数赋值等号两侧不需加空格

5.2 函数式编程实例

模块化编程思想

  • 自顶向下,分而治之

【问题描述】

  • 小丹和小伟羽毛球打的都不错,水平也在伯仲之间,小丹略胜一筹,基本上,打100个球,小丹能赢55次,小伟能赢45次。

  • 但是每次大型比赛(1局定胜负,谁先赢到21分,谁就获胜),小丹赢的概率远远大于小伟,小伟很是不服气。

  • 亲爱的小伙伴,你能通过模拟实验,来揭示其中的奥妙吗?

【问题抽象】

1、在小丹Vs小伟的二元比赛系统中,小丹每球获胜概率55%,小伟每球获胜概率45%;

2、每局比赛,先赢21球(21分)者获胜;

3、假设进行n = 10000局独立的比赛,小丹会获胜多少局?(n 较大的时候,实验结果≈真实期望)

【问题分解】

def main():
    # 主要逻辑
    prob_A, prob_B, number_of_games = get_inputs()                        # 获取原始数据
    win_A, win_B = sim_n_games(prob_A, prob_B, number_of_games)           # 获取模拟结果
    print_summary(win_A, win_B, number_of_games)                          # 结果汇总输出

1、输入原始数据

def get_inputs():  
    # 输入原始数据
    prob_A = eval(input("请输入运动员A的每球获胜概率(0~1):"))
    prob_B = round(1-prob_A, 2)
    number_of_games = eval(input("请输入模拟的场次(正整数):"))
    print("模拟比赛总次数:", number_of_games)
    print("A 选手每球获胜概率:", prob_A)
    print("B 选手每球获胜概率:", prob_B)
    return prob_A, prob_B, number_of_games

  单元测试

prob_A, prob_B, number_of_games = get_inputs()
print(prob_A, prob_B, number_of_games)
请输入运动员A的每球获胜概率(0~1):0.55
请输入模拟的场次(正整数):10000
模拟比赛总次数: 10000
A 选手每球获胜概率: 0.55
B 选手每球获胜概率: 0.45
0.55 0.45 10000

2、多场比赛模拟

def sim_n_games(prob_A, prob_B, number_of_games):
    # 模拟多场比赛的结果
    win_A, win_B = 0, 0                # 初始化A、B获胜的场次
    for i in range(number_of_games):   # 迭代number_of_games次
        score_A, score_B = sim_one_game(prob_A, prob_B)  # 获得模拟依次比赛的比分
        if score_A > score_B:
            win_A += 1
        else:
            win_B += 1
    return win_A, win_B
import random
def sim_one_game(prob_A, prob_B):
    # 模拟一场比赛的结果
    score_A, score_B = 0, 0
    while not game_over(score_A, score_B):
        if random.random() < prob_A:                # random.random() 生产[0,1)之间的随机小数,均匀分布
            score_A += 1                 
        else:
            score_B += 1
    return score_A, score_B
def game_over(score_A, score_B):
    # 单场模拟结束条件,一方先达到21分,比赛结束
    return score_A == 21 or score_B == 21

  单元测试 用assert——断言

  • assert expression

  • 表达式结果为 false 的时候触发异常

assert game_over(21, 8) == True   
assert game_over(9, 21) == True
assert game_over(11, 8) == False
assert game_over(21, 8) == False
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-42-88b651626036> in <module>
      2 assert game_over(9, 21) == True
      3 assert game_over(11, 8) == False
----> 4 assert game_over(21, 8) == False


AssertionError: 
print(sim_one_game(0.55, 0.45))
print(sim_one_game(0.7, 0.3))
print(sim_one_game(0.2, 0.8))
(21, 7)
(21, 14)
(10, 21)
print(sim_n_games(0.55, 0.45, 1000))
(731, 269)

3、结果汇总输出

def print_summary(win_A, win_B, number_of_games):
    # 结果汇总输出
    print("共模拟了{}场比赛".format(number_of_games))
    print("选手A获胜{0}场,占比{1:.1%}".format(win_A, win_A/number_of_games))
    print("选手B获胜{0}场,占比{1:.1%}".format(win_B, win_B/number_of_games))
print_summary(729, 271, 1000)
共模拟了1000场比赛
选手A获胜729场,占比72.9%
选手B获胜271场,占比27.1%
import random


def get_inputs():  
    # 输入原始数据
    prob_A = eval(input("请输入运动员A的每球获胜概率(0~1):"))
    prob_B = round(1-prob_A, 2)
    number_of_games = eval(input("请输入模拟的场次(正整数):"))
    print("模拟比赛总次数:", number_of_games)
    print("A 选手每球获胜概率:", prob_A)
    print("B 选手每球获胜概率:", prob_B)
    return prob_A, prob_B, number_of_games


def game_over(score_A, score_B):
    # 单场模拟结束条件,一方先达到21分,比赛结束    
    return score_A == 21 or score_B == 21


def sim_one_game(prob_A, prob_B):
    # 模拟一场比赛的结果
    score_A, score_B = 0, 0
    while not game_over(score_A, score_B):
        if random.random() < prob_A:                # random.random() 生产[0,1)之间的随机小数,均匀分布
            score_A += 1                 
        else:
            score_B += 1
    return score_A, score_B


def sim_n_games(prob_A, prob_B, number_of_games):
    # 模拟多场比赛的结果
    win_A, win_B = 0, 0                # 初始化A、B获胜的场次
    for i in range(number_of_games):   # 迭代number_of_games次
        score_A, score_B = sim_one_game(prob_A, prob_B)  # 获得模拟依次比赛的比分
        if score_A > score_B:
            win_A += 1
        else:
            win_B += 1
    return win_A, win_B


def print_summary(win_A, win_B, number_of_games):
    # 结果汇总输出
    print("共模拟了{}场比赛".format(number_of_games))
    print("\033[31m选手A获胜{0}场,占比{1:.1%}".format(win_A, win_A/number_of_games))
    print("选手B获胜{0}场,占比{1:.1%}".format(win_B, win_B/number_of_games))
    

def main():
    # 主要逻辑
    prob_A, prob_B, number_of_games = get_inputs()                        # 获取原始数据
    win_A, win_B = sim_n_games(prob_A, prob_B, number_of_games)           # 获取模拟结果
    print_summary(win_A, win_B, number_of_games)                          # 结果汇总输出


if __name__ == "__main__":
    main()
请输入运动员A的每球获胜概率(0~1):0.52
请输入模拟的场次(正整数):10000
模拟比赛总次数: 10000
A 选手每球获胜概率: 0.52
B 选手每球获胜概率: 0.48
共模拟了10000场比赛
[31m选手A获胜6033场,占比60.3%
选手B获胜3967场,占比39.7%

经统计,小丹跟小伟14年职业生涯,共交手40次,小丹以28:12遥遥领先。

其中,两人共交战整整100局:

小丹获胜61局,占比61%;

小伟获胜39局,占比39%。

你以为你跟别人的差距只是一点点,实际上,差距老大了

5.3 匿名函数

1、基本形式

lambda 变量: 函数体

2、常用用法

在参数列表中最适合使用匿名函数,尤其是与key = 搭配

  • 排序sort() sorted()
ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
ls.sort()
ls
[(76, 94), (79, 100), (85, 85), (86, 71), (93, 88)]
ls.sort(key = lambda x: x[1])
ls
[(86, 71), (85, 85), (93, 88), (76, 94), (79, 100)]
ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
temp = sorted(ls, key = lambda x: x[0]+x[1], reverse=True)
temp
[(93, 88), (79, 100), (85, 85), (76, 94), (86, 71)]
  • max() min()
ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
n = max(ls, key = lambda x: x[1])
n
(79, 100)
n = min(ls, key = lambda x: x[1])
n
(86, 71)

5.4 面向过程和面向对象

面向过程——以过程为中心的编程思想,以“什么正在发生”为主要目标进行编程。 冰冷的,程序化的

面向对象——将现实世界的事物抽象成对象,更关注“谁在受影响”,更加贴近现实。  有血有肉,拟人(物)化的

  • 以公共汽车为例

“面向过程”:汽车启动是一个事件,汽车到站是另一个事件。。。。

在编程序的时候我们关心的是某一个事件,而不是汽车本身。

我们分别对启动和到站编写程序。

“面向对象”:构造“汽车”这个对象。

对象包含动力、服役时间、生产厂家等等一系列的“属性”;

也包含加油、启动、加速、刹车、拐弯、鸣喇叭、到站、维修等一系列的“方法”。

通过对象的行为表达相应的事件

第七章 类——面向对象的编程

引子

Why:面向对象更符合人类对客观世界的抽象和理解

  • 一切皆对象
    一只小狗,一把椅子,一张信用卡,一条巧克力。。。

  • 一切对象,都有自己内在的属性
    狗狗的品种、椅子的质地、信用卡的额度、巧克力的口味。。。

  • 一切行为,皆是对象的行为
    狗狗蹲下、椅子移动位置、刷信用卡、巧克力融化了。。。

How:类是对象的载体

不同年龄、肤色、品质的猫,每一只都是一个对象

他们有一个共同的特征:都是猫

我们可以把一类对象的公共特征抽象出来,创建通用的类

# 创建类
class Cat():
    """模拟猫"""
    
    def __init__(self, name):
        """初始化属性"""
        self.name = name
    
    def jump(self):
        """模拟猫跳跃"""
        print(self.name + " is jumping") 
# 用类创建实例
my_cat = Cat("Loser")
your_cat = Cat("Lucky")
# 调用属性
print(my_cat.name)
print(your_cat.name)
Loser
Lucky
# 调用方法
my_cat.jump()
your_cat.jump()
Loser is jumping
Lucky is jumping

7.1 类的定义

三要素:类名、属性、方法

7.1.1 类的命名

  • 要有实际意义

  • 驼峰命名法——组成的单词首字母大写
    Dog、 CreditCard、 ElectricCar

# class 类名:
"""类前空两行"""


class Car():
    """对该类的简单介绍"""
    pass

"""类后空两行"""

7.1.2 类的属性

# def __init__(self,要传递的参数)  初始化类的属性
class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0        

7.1.3 类的方法

# 相对于类内部定义的函数
class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程"""
        return "行车总里程:{}公里".format(self.mileage)

7.2 创建实例

7.2.1 实例的创建

将实例赋值给对象,实例化过程中,传入相应的参数
v = 类名(必要的初始化参数)

my_new_car = Car("Audi", "A6", 2018)

7.2.2 访问属性

实例名.属性名

print(my_new_car.brand)
print(my_new_car.model)
print(my_new_car.year)
Audi
A6
2018

7.2.3 调用方法

class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        return "行车总里程:{}公里".format(self.mileage)

实例名.方法名(必要的参数)

my_new_car = Car("Audi", "A6", 2018)
my_new_car.get_main_information()
品牌:Audi   型号:A6   出厂年份:2018
mileage = my_new_car.get_mileage()
print(mileage)
行车总里程:0公里

7.2.4 修改属性

1、直接修改

my_old_car = Car("BYD", "宋", 2016)

先访问,后修改

print(my_old_car.mileage)
my_old_car.mileage = 12000
print(my_old_car.mileage)
0
12000
print(my_old_car.get_mileage())
行车总里程:12000公里

2、通过方法修改属性

class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        return "行车总里程:{}公里".format(self.mileage)
    
    def set_mileage(self, distance):
        """设置总里程数"""
        self.mileage = distance
my_old_car = Car("BYD", "宋", 2016)
print(my_old_car.get_mileage())
my_old_car.set_mileage(8000)
print(my_old_car.get_mileage())
行车总里程:0公里
行车总里程:8000公里

3、继续拓展

  • 禁止设置负里程
class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        print("行车总里程:{}公里".format(self.mileage)) 
    
    def set_mileage(self, distance):
        """设置总里程数"""
        if distance >= 0:
            self.mileage = distance
        else:
            print("里程数不能为负!")
    
    def increment_mileage(self, distance):
        """总里程数累计"""
        if distance >= 0:
            self.mileage += distance
        else:
            print("新增里程数不能为负!") 
            
my_old_car = Car("BYD", "宋", 2016)
my_old_car.get_mileage()
my_old_car.set_mileage(-8000)
my_old_car.get_mileage()
行车总里程:0公里
里程数不能为负!
行车总里程:0公里
  • 将每次的里程数累加
my_old_car.get_mileage()
my_old_car.set_mileage(8000)
my_old_car.get_mileage()
my_old_car.increment_mileage(500)
my_old_car.get_mileage()
行车总里程:0公里
行车总里程:8000公里
行车总里程:8500公里

小结

my_new_car = Car("Audi", "A6", 2018)
my_cars = [my_new_car, my_old_car]
  • 包含的信息量可以是极大的,可以创建无穷多的实例
  • 高度的拟人(物)化,符合人类对客观世界的抽象和理解

7.3 类的继承

引子

看一下人在生物界的分支链

生物——动物界——脊索动物门——哺乳动物纲——灵长目——人科——人属——智人种

公共特征逐渐增加的过程

【问题】

假设二元系统: 人属 = {A人种, B人种, C人种。。。。}
为每一个人种构造一个类

方案一:
各自独立,分别构造各自人种的类

方案二:
1、将各人种公共特征提取出来,建立人属的类;
2、各人种继承上一级(人属)的公共特征,然后添加自身特殊特征,构建各自人种的类。

通常,我们选择方案二,因为他避免了过多的重复劳动

所谓继承,就是低层抽象继承高层抽象的过程

7.3.1 简单的继承

父类

class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0
        
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        print("行车总里程:{}公里".format(self.mileage)) 
    
    def set_mileage(self, distance):
        """设置总里程数"""
        if distance >= 0:
            self.mileage = distance
        else:
            print("里程数不能为负!")
    
    def increment_mileage(self, distance):
        """总里程数累计"""
        if distance >= 0:
            self.mileage += distance
        else:
            print("新增里程数不能为负!")

子类

class 子类名(父类名):

  • 新建一个电动汽车的类
class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)  # 声明继承父类的属性
  • 自动继承父类的所有方法
my_electric_car = ElectricCar("NextWeek", "FF91", 2046)
my_electric_car.get_main_information()
品牌:NextWeek   型号:FF91   出厂年份:2046

7.3.2 给子类添加属性和方法

class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year, bettery_size):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)    # 声明继承父类的属性
        self.bettery_size = bettery_size        # 电池容量
        self.electric_quantity = bettery_size   # 电池剩余电量
        self.electric2distance_ratio = 5        # 电量距离换算系数 5公里/kW.h
        self.remainder_range = self.electric_quantity*self.electric2distance_ratio # 剩余可行驶里程
    
    def get_electric_quantit(self):
        """查看当前电池电量"""
        print("当前电池剩余电量:{} kW.h".format(self.electric_quantity))
        
    def set_electric_quantity(self, electric_quantity):
        """设置电池剩余电量,重新计算电量可支撑行驶里程"""
        if electric_quantity >= 0 and electric_quantity <= self.bettery_size:
            self.electric_quantity = electric_quantity
            self.remainder_range = self.electric_quantity*self.electric2distance_ratio
        else:
            print("电量未设置在合理范围!")
    
    def get_remainder_range(self):
        """查看剩余可行驶里程"""
        print("当前电量还可以继续驾驶 {} 公里".format(self.remainder_range))              
my_electric_car = ElectricCar("NextWeek", "FF91", 2046, 70)
my_electric_car.get_electric_quantit()            # 获取当前电池电量
my_electric_car.get_remainder_range()             # 获取当前剩余可行驶里程
当前电池剩余电量:70 kW.h
当前电量还可以继续驾驶 350 公里
my_electric_car.set_electric_quantity(50)         # 重设电池电量
my_electric_car.get_electric_quantit()            # 获取当前电池电量
my_electric_car.get_remainder_range()             # 获取当前剩余可行驶里程
当前电池剩余电量:50 kW.h
当前电量还可以继续驾驶 250 公里

7.3.3 重写父类的方法——多态

class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year, bettery_size):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)    # 声明继承父类的属性
        self.bettery_size = bettery_size        # 电池容量
        self.electric_quantity = bettery_size   # 电池剩余电量
        self.electric2distance_ratio = 5        # 电量距离换算系数 5公里/kW.h
        self.remainder_range = self.electric_quantity*self.electric2distance_ratio # 剩余可行驶里程
    
    def get_main_information(self):        # 重写父类方法
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}   续航里程:{} 公里"
              .format(self.brand, self.model, self.year, self.bettery_size*self.electric2distance_ratio))
    
    def get_electric_quantit(self):
        """查看当前电池电量,重新计算电量可支撑行驶里程"""
        print("当前电池剩余电量:{} kW.h".format(self.electric_quantity))
        
    def set_electric_quantity(self, electric_quantity):
        """设置电池剩余电量"""
        if electric_quantity >= 0 and electric_quantity <= self.bettery_size:
            self.electric_quantity = electric_quantity
            self.remainder_range = self.electric_quantity*self.electric2distance_ratio
        else:
            print("电量未设置在合理范围!")
    
    def get_remainder_range(self):
        """查看剩余可行驶里程"""
        print("当前电量还可以继续驾驶 {} 公里".format(self.remainder_range))
my_electric_car = ElectricCar("NextWeek", "FF91", 2046, 70)
my_electric_car.get_main_information()
品牌:NextWeek   型号:FF91   出厂年份:2046   续航里程:350 公里

7.3.4 用在类中的实例

把电池抽象成一个对象
逻辑更加清晰

class Bettery():
    """模拟电动汽车的电池"""
    
    def __init__(self, bettery_size = 70):
        self.bettery_size = bettery_size        # 电池容量
        self.electric_quantity = bettery_size   # 电池剩余电量
        self.electric2distance_ratio = 5        # 电量距离换算系数 5公里/kW.h
        self.remainder_range = self.electric_quantity*self.electric2distance_ratio # 剩余可行驶里程

    def get_electric_quantit(self):
        """查看当前电池电量"""
        print("当前电池剩余电量:{} kW.h".format(self.electric_quantity))
        
    def set_electric_quantity(self, electric_quantity):
        """设置电池剩余电量,计重新算电量可支撑行驶里程"""
        if electric_quantity >= 0 and electric_quantity <= self.bettery_size:
            self.electric_quantity = electric_quantity
            self.remainder_range = self.electric_quantity*self.electric2distance_ratio
        else:
            print("电量未设置在合理范围!")
    
    def get_remainder_range(self):
        """查看剩余可行驶里程"""
        print("当前电量还可以继续驾驶 {} 公里".format(self.remainder_range))
class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year, bettery_size):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)    # 声明继承父类的属性
        self.bettery = Bettery(bettery_size)    # 电池
    
    def get_main_information(self):        # 重写父类方法
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}   续航里程:{} 公里"
              .format(self.brand, self.model, self.year, 
              self.bettery.bettery_size*self.bettery.electric2distance_ratio))
my_electric_car = ElectricCar("NextWeek", "FF91", 2046, 70)
my_electric_car.get_main_information()                  # 获取车辆主要信息
品牌:NextWeek   型号:FF91   出厂年份:2046   续航里程:350 公里
my_electric_car.bettery.get_electric_quantit()          # 获取当前电池电量
当前电池剩余电量:70 kW.h
my_electric_car.bettery.set_electric_quantity(50)       # 重设电池电量
my_electric_car.bettery.get_electric_quantit()          # 获取当前电池电量    
当前电池剩余电量:50 kW.h
my_electric_car.bettery.get_remainder_range()           # 获取当前剩余可行驶里程
当前电量还可以继续驾驶 250 公里

第八章 文件、异常和模块

实际应用中,我们绝大多数的数据都是通过文件的交互完成的

8.1 文件的读写

8.1.1 文件的打开

  • 文件的打开通用格式
with open("文件路径", "打开模式", encoding = "操作文件的字符编码") as f:
    "对文件进行相应的读写操作"
使用with 块的好处:执行完毕后,自动对文件进行close操作。

【例1】一个简单的文件读取

with open("E:\ipython\测试文件.txt", "r", encoding = "gbk") as f:     # 第一步:打开文件
    text = f.read()                                                   # 第二步:读取文件
    print(text)
我是一个测试文件

1、文件路径

  • 完整路径,如上例所示

  • 程序与文件在同一文件夹,可简化成文件名

with open("测试文件.txt", "r", encoding = "gbk") as f:     # 第一步:打开文件
    text = f.read()                                        # 第二步:读取文件
    print(text)
我是一个测试文件

2、打开模式

  • “r”  只读模式,如文件不存在,报错

  • “w” 覆盖写模式,如文件不存在,则创建;如文件存在,则完全覆盖原文件

  • “x” 创建写模式,如文件不存在,则创建;如文件存在,报错

  • “a”  追加写模式,如文件不存在,则创建;如文件存在,则在原文件后追加内容

  • “b” 二进制文件模式,不能单独使用,需要配合使用如"rb",“wb”,“ab”,该模式不需指定encoding

  • “t” 文本文件模式,默认值,需配合使用 如"rt",“wt”,“at”,一般省略,简写成如"r",“w”,“a”

  • “+”,与"r",“w”,“x”,"a"配合使用,在原功能基础上,增加读写功能

  • 打开模式缺省,默认为只读模式

3、字符编码

  • 万国码 utf-8

包含全世界所有国家需要用到的字符

  • 中文编码 gbk

专门解决中文编码问题

  • windows系统下,如果缺省,则默认为gbk(所在区域的编码)

  • 为清楚起见,除了处理二进制文件,建议不要缺省encoding

8.1.2 文件的读取

1、读取整个内容——f.read()

with open("三国演义片头曲_utf.txt", "r", encoding="utf-8") as f:       # 第一步:打开文件
    text = f.read()                                                    # 第二步:读取文件
    print(text)
临江仙·滚滚长江东逝水
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。
青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。
古今多少事,都付笑谈中。
with open("三国演义片头曲_utf.txt", encoding="utf-8") as f:     # "r",可缺省,为清晰起见,最好写上
    text = f.read()                                                   
    print(text)
临江仙·滚滚长江东逝水
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。
青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。
古今多少事,都付笑谈中。
  • 解码模式不匹配
with open("三国演义片头曲_utf.txt", "r", encoding="gbk") as f:     
    text = f.read()                                                    
    print(text)
---------------------------------------------------------------------------

UnicodeDecodeError                        Traceback (most recent call last)

<ipython-input-6-8e9ea685585d> in <module>
      1 with open("三国演义片头曲_utf.txt", "r", encoding="gbk") as f:
----> 2     text = f.read()
      3     print(text)


UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 50: illegal multibyte sequence
with open("三国演义片头曲_utf.txt", "r") as f:     # encoding缺省,windows系统默认为"gbk"
    text = f.read()                                                    
    print(text)
---------------------------------------------------------------------------

UnicodeDecodeError                        Traceback (most recent call last)

<ipython-input-7-480622bc01aa> in <module>
      1 with open("三国演义片头曲_utf.txt", "r") as f:     # encoding缺省,windows系统默认为"gbk"
----> 2     text = f.read()
      3     print(text)


UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 50: illegal multibyte sequence

2、逐行进行读取——f.readline()

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    for i in range(3):
        text = f.readline()                                                   # 每次只读取一行
        print(text)
临江仙·滚滚长江东逝水

滚滚长江东逝水,浪花淘尽英雄。

是非成败转头空。
with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    while True:
        text = f.readline()
        if not text:
            # print(text is "")
            break
        else:
            # print(text == "\n")
            print(text, end="")      # 保留原文的换行,使print()的换行不起作用
临江仙·滚滚长江东逝水
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。

青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。
古今多少事,都付笑谈中。

3、读入所有行,以每行为元素形成一个列表——f.readlines()

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:
    text = f.readlines()              # 注意每行末尾有换行符
    print(text)               
['临江仙·滚滚长江东逝水\n', '滚滚长江东逝水,浪花淘尽英雄。\n', '是非成败转头空。\n', '\n', '青山依旧在,几度夕阳红。\n', '白发渔樵江渚上,惯看秋月春风。\n', '一壶浊酒喜相逢。\n', '古今多少事,都付笑谈中。\n']
with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    for text in f.readlines():                                                   
        print(text)                # 不想换行则用print(text, end="")         
临江仙·滚滚长江东逝水

滚滚长江东逝水,浪花淘尽英雄。

是非成败转头空。



青山依旧在,几度夕阳红。

白发渔樵江渚上,惯看秋月春风。

一壶浊酒喜相逢。

古今多少事,都付笑谈中。

4、文本文件读取小结

文件比较大时,read()和readlines()占用内存过大,不建议使用

readline用起来又不太方便

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    for text in f:         # f本身就是一个可迭代对象,每次迭代读取一行内容 
        print(text)  
临江仙·滚滚长江东逝水

滚滚长江东逝水,浪花淘尽英雄。

是非成败转头空。



青山依旧在,几度夕阳红。

白发渔樵江渚上,惯看秋月春风。

一壶浊酒喜相逢。

古今多少事,都付笑谈中。

5、二进制文件

图片:二进制文件

with open("test.jpg", "rb") as f:     
        print(len(f.readlines()))
69

8.1.3 文件的写入

1、向文件写入一个字符串或字节流(二进制)——f.write()

with open("恋曲1980.txt", "w", encoding="utf-8") as f:                      
        f.write("你曾经对我说\n")        # 文件不存在则立刻创建一个
        f.write("你永远爱着我\n")        # 如需换行,末尾加换行符\n
        f.write("爱情这东西我明白\n")
        f.write("但永远是什么\n")
with open("恋曲1980.txt", "w", encoding="utf-8") as f:                      
        f.write("姑娘你别哭泣\n")        # 如果文件存在,新写入内容会覆盖掉原内容,一定要注意!!!
        f.write("我俩还在一起\n")        
        f.write("今天的欢乐\n")
        f.write("将是明天创痛的回忆\n")

2、追加模式——“a”

with open("恋曲1980.txt", "a", encoding="utf-8") as f:                      
        f.write("姑娘你别哭泣\n")        # 如果文件存在,新写入内容会覆盖掉原内容,一定要注意!!!
        f.write("我俩还在一起\n")        
        f.write("今天的欢乐\n")
        f.write("将是明天创痛的回忆\n")

3、将一个元素为字符串的列表整体写入文件——f.writelines()

ls = ["春天刮着风", "秋天下着雨", "春风秋雨多少海誓山盟随风远去"]
with open("恋曲1980.txt", "w", encoding="utf-8") as f:
    f.writelines(ls)
ls = ["春天刮着风\n", "秋天下着雨\n", "春风秋雨多少海誓山盟随风远去\n"]
with open("恋曲1980.txt", "w", encoding="utf-8") as f:
    f.writelines(ls)

8.1.4 既读又写

1、“r+”

  • 如果文件名不存在,则报错
  • 指针在开始
  • 要把指针移到末尾才能开始写,否则会覆盖前面内容
with open("浪淘沙_北戴河.txt", "r+", encoding="gbk") as f:
#     for line in f:
#         print(line)   # 全部读一遍后,指针到达结尾
    f.seek(0,2)         # 或者可以将指针移到末尾f.seek(偏移字节数,位置(0:开始;1:当前位置;2:结尾))
    text = ["萧瑟秋风今又是,\n", "换了人间。\n"]
    f.writelines(text)

2、“w+”

  • 若文件不存在,则创建
  • 若文件存在,会立刻清空原内容!!!
with open("浪淘沙_北戴河.txt", "w+", encoding="gbk") as f:
    pass
with open("浪淘沙_北戴河.txt", "w+", encoding="gbk") as f:
    text = ["萧瑟秋风今又是,\n", "换了人间。\n"]  # 清空原内容
    f.writelines(text)                             # 写入新内容,指针在最后
    f.seek(0,0)            # 指针移到开始
    print(f.read())        # 读取内容

3、“a+”

  • 若文件不存在,则创建
  • 指针在末尾,添加新内容,不会清空原内容
with open("浪淘沙_北戴河.txt", "a+", encoding="gbk") as f:
    f.seek(0,0)            # 指针移到开始
    print(f.read())        # 读取内容
大雨落幽燕,
白浪滔天。
秦皇岛外打鱼船。
一片汪洋都不见,
知向谁边?
往事越千年,
魏武挥鞭,
东临碣石有遗篇。
萧瑟秋风今又是,
换了人间。
with open("浪淘沙_北戴河.txt", "a+", encoding="gbk") as f:
    text = ["萧瑟秋风今又是,\n", "换了人间。\n"]  
    f.writelines(text)                             # 指针在最后,追加新内容, 
    f.seek(0,0)            # 指针移到开始
    print(f.read())        # 读取内容
大雨落幽燕,
白浪滔天。
秦皇岛外打鱼船。
一片汪洋都不见,
知向谁边?
往事越千年,
魏武挥鞭,
东临碣石有遗篇。
萧瑟秋风今又是,
换了人间。
萧瑟秋风今又是,
换了人间。

8.1.5 数据的存储与读取

通用的数据格式,可以在不同语言中加载和存储

本节简单了解两种数据存储结构csv和json

1、csv格式

由逗号将数据分开的字符序列,可以由excel打开

  • 读取
with open("成绩.csv", "r", encoding="gbk") as f:
    ls = [] 
    for line in f:                              # 逐行读取
        ls.append(line.strip("\n").split(","))  # 去掉每行的换行符,然后用“,”进行分割
for res in ls:
    print(res)
['编号', '数学成绩', '语文成绩']
['1', '100', '98']
['2', '96', '99']
['3', '97', '95']
  • 写入
ls = [['编号', '数学成绩', '语文成绩'], ['1', '100', '98'], ['2', '96', '99'], ['3', '97', '95']]
with open("score.csv", "w", encoding="gbk") as f:   # encoding="utf-8"中文出现乱码
    for row in ls:                                  # 逐行写入
        f.write(",".join(row)+"\n")                 # 用逗号组合成字符串形式,末尾加换行符

也可以借助csv模块完成上述操作

2、json格式

常被用来存储字典类型

  • 写入——dump()
import json

scores = {"Petter":{"math":96 , "physics": 98},
        "Paul":{"math":92 , "physics": 99},
        "Mary":{"math":98 , "physics": 97}}
with open("score.json", "w", encoding="utf-8") as f:             # 写入整个对象 
        # indent 表示字符串换行+缩进 ensure_ascii=False 显示中文
        json.dump(scores, f, indent=4, ensure_ascii=False)       
        
  • 读取——load()
with open("score.json", "r", encoding="utf-8") as f:                                         
        scores = json.load(f)           # 加载整个对象
        for k,v in scores.items():
            print(k,v)
Petter {'math': 96, 'physics': 98}
Paul {'math': 92, 'physics': 99}
Mary {'math': 98, 'physics': 97}

8.2 异常处理

8.2.1 常见异常的产生

1、除0运算——ZeroDivisionError

1/0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-1-9e1622b385b6> in <module>
----> 1 1/0


ZeroDivisionError: division by zero

2、找不到可读文件——FileNotFoundError

with open("nobody.csv") as f:
    pass
---------------------------------------------------------------------------

FileNotFoundError                         Traceback (most recent call last)

<ipython-input-2-f2e8c7d0ac60> in <module>
----> 1 with open("nobody.csv") as f:
      2     pass


FileNotFoundError: [Errno 2] No such file or directory: 'nobody.csv'

3、值错误——ValueError

传入一个调用者不期望的值,即使这个值的类型是正确的

s = "1.3"
n = int(s)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-8-69942d9db3c0> in <module>
      1 s = "1.3"
----> 2 n = int(s)


ValueError: invalid literal for int() with base 10: '1.3'

4、索引错误——IndexError

下标超出序列边界

ls = [1, 2, 3]
ls[5]
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-9-acf459124b52> in <module>
      1 ls = [1, 2, 3]
----> 2 ls[5]


IndexError: list index out of range

5、类型错误——TypeError

传入对象类型与要求不符

1 + "3"
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-10-ee505dc42f75> in <module>
----> 1 1 + "3"


TypeError: unsupported operand type(s) for +: 'int' and 'str'

6、其他常见的异常类型

NameError 使用一个未被定义的变量
KeyError 试图访问字典里不存在的键
。。。

print(a)
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-11-bca0e2660b9f> in <module>
----> 1 print(a)


NameError: name 'a' is not defined
d = {}
d["1"]
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-12-e629d551aca0> in <module>
      1 d = {}
----> 2 d["1"]


KeyError: '1'

当异常发生的时候,如果不预先设定处理方法,程序就会中断

8.2.2 异常的处理

提高程序的稳定性和可靠性

1、try_except

  • 如果try内代码块顺利执行,except不被触发

  • 如果try内代码块发生错误,触发except,执行except内代码块

  • 单分支

x = 10
y = 0
try:
    z = x/y
except ZeroDivisionError:                  # 一般来说会预判到出现什么错误
    # z = x/(y+1e-7)
    # print(z)
    print("0不可以被除!")    
0不可以被除!
x = 10
y = 0
try:
    z = x/y
except NameError:                  # 一般来说会预判到出现什么错误
    # z = x/(y+1e-7)
    # print(z)
    print("0不可以被除!")   
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-14-aea58863ad72> in <module>
      2 y = 0
      3 try:
----> 4     z = x/y
      5 except NameError:                  # 一般来说会预判到出现什么错误
      6     # z = x/(y+1e-7)


ZeroDivisionError: division by zero
  • 多分支
ls = []
d = {"name": "大杰仔"}
try:
    y = m
    # ls[3]
    # d["age"]
except NameError:
    print("变量名不存在")
except IndexError:
    print("索引超出界限")
except KeyError:
    print("键不存在")
变量名不存在
  • 万能异常 Exception (所有错误的老祖宗)
ls = []
d = {"name": "大杰仔"}
try:
    # y = m
    ls[3]
    # d["age"]
except Exception:
    print("出错啦")
出错啦
  • 捕获异常的值 as
ls = []
d = {"name": "大杰仔"}
# y = x
try:
    y = m
    # ls[3]
    # d["age"]
except Exception as e:    # 虽不能获得错误具体类型,但可以获得错误的值
    print(e)
name 'm' is not defined

2、try_except_else

  • 如果try 模块执行,则else模块也执行

可以将else 看做try成功的额外奖赏

try:
    with open("浪淘沙_北戴河.txt") as f:
        text = f.read()
except FileNotFoundError:
    print("找不到该文件,ta是不是用了美颜?")
else:
    for s in ["\n", ",", "。", "?"]:         # 去掉换行符和标点符号
        text = text.replace(s, "")
    print("毛主席的名作《浪淘沙_北戴河》共由{}个字组成。".format(len(text)))
毛主席的名作《浪淘沙_北戴河》共由65个字组成。

3、try_except_finally

  • 不论try模块是否执行,finally最后都执行
ls = []
d = {"name": "大杰仔"}
# y = x
try:
    y = m
    # ls[3]
    # d["age"]
except Exception as e:    # 虽不能获得错误具体类型,但可以获得错误的值
    print(e)
finally:
    print("不论触不触发异常,都将执行")
name 'm' is not defined
不论触不触发异常,都将执行

8.3 模块简介

已经被封装好

无需自己再“造轮子”

声明导入后,拿来即用

8.3.1 广义模块分类

1、Python 内置

 时间库time\  随机库random\  容器数据类型collection\  迭代器函数itertools

2、第三方库

 数据分析numpy、pandas\  数据可视化matplotlib\  机器学习scikit-learn\  深度学习Tensorflow

3、自定义文件

  • 单独py文件

  • 包——多个py文件

# 文件夹内多个py文件,再加一个__init__.py文件(内容可为空)

8.3.2 模块的导入

1、导入整个模块——import 模块名

  • **调用方式:**模块名.函数名或类名
import time

start = time.time()      # 调用time模块中的time()
time.sleep(3)            # 调用time模块中的sleep()  休息3秒钟
end = time.time()       
print("程序运行用时:{:.2f}秒".format(end-start))
程序运行用时:3.00秒
import fun1

fun1.f1()
导入fun1成功

2、从模块中导入类或函数——from 模块 import 类名或函数名

  • **调用方式:**函数名或类名
from itertools import product   

ls = list(product("AB", "123"))
print(ls)
[('A', '1'), ('A', '2'), ('A', '3'), ('B', '1'), ('B', '2'), ('B', '3')]
from function.fun1 import f1         # 注意这种用法

f1()
导入fun1成功

一次导入多个

from function import fun1, fun2

fun1.f1()
fun2.f2()
导入fun1成功
导入fun2成功

3、导入模块中所有的类和函数——from 模块 import *

  • **调用方式:**函数名或类名
from random import * 

print(randint(1,100))       # 产生一个[1,100]之间的随机整数
print(random())             # 产生一个[0,1)之间的随机小数
36
0.6582485822110181

8.3.3 模块的查找路径

模块搜索查找顺序:

  • 1、内存中已经加载的模块
import fun1

fun1.f1()
导入fun1成功
# 删除硬盘上的fun1 文件
import fun1

fun1.f1()
导入fun1成功
# 修改硬盘上的fun1 文件
import fun1

fun1.f1()
# 居然没变,说明是优先从内存中读取的
导入fun1成功
  • 2、内置模块
# Python 启动时,解释器会默认加载一些 modules 存放在sys.modules中
# sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值
import sys

print(len(sys.modules))
print("math" in sys.modules)
print("numpy" in sys.modules)
for k,v in list(sys.modules.items())[:20]:
    print(k, ":", v)
738
True
False
sys : <module 'sys' (built-in)>
builtins : <module 'builtins' (built-in)>
_frozen_importlib : <module 'importlib._bootstrap' (frozen)>
_imp : <module '_imp' (built-in)>
_thread : <module '_thread' (built-in)>
_warnings : <module '_warnings' (built-in)>
_weakref : <module '_weakref' (built-in)>
zipimport : <module 'zipimport' (built-in)>
_frozen_importlib_external : <module 'importlib._bootstrap_external' (frozen)>
_io : <module 'io' (built-in)>
marshal : <module 'marshal' (built-in)>
nt : <module 'nt' (built-in)>
winreg : <module 'winreg' (built-in)>
encodings : <module 'encodings' from 'C:\\Users\\ibm\\Anaconda3\\lib\\encodings\\__init__.py'>
codecs : <module 'codecs' from 'C:\\Users\\ibm\\Anaconda3\\lib\\codecs.py'>
_codecs : <module '_codecs' (built-in)>
encodings.aliases : <module 'encodings.aliases' from 'C:\\Users\\ibm\\Anaconda3\\lib\\encodings\\aliases.py'>
encodings.utf_8 : <module 'encodings.utf_8' from 'C:\\Users\\ibm\\Anaconda3\\lib\\encodings\\utf_8.py'>
_signal : <module '_signal' (built-in)>
__main__ : <module '__main__'>
  • 3、sys.path路径中包含的模块
import sys

sys.path
['E:\\ipython',
 'C:\\Users\\ibm\\Anaconda3\\python37.zip',
 'C:\\Users\\ibm\\Anaconda3\\DLLs',
 'C:\\Users\\ibm\\Anaconda3\\lib',
 'C:\\Users\\ibm\\Anaconda3',
 '',
 'C:\\Users\\ibm\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages',
 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\win32',
 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\win32\\lib',
 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\Pythonwin',
 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\IPython\\extensions',
 'C:\\Users\\ibm\\.ipython']
  • sys.path的第一个路径是当前执行文件所在的文件夹
  • 若需将不在该文件夹内的模块导入,需要将模块的路径添加到sys.path
# import fun3
import sys

sys.path.append("C:\\Users\\ibm\\Desktop")    # 注意是双斜杠

import fun3

fun3.f3()
导入fun3成功

有益的探索

尝试着潜入水中,往冰山的深处扎一个小小的猛子

9.1 数据类型的底层实现

9.1.1 从奇怪的列表说起

1、错综复杂的复制

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
  • 浅拷贝
# list_3 = list_1          # 错误!!!
list_2 = list_1.copy()     # 或者list_1[:] \ list(list_1) 均可实习浅拷贝
  • 对浅拷贝前后两列表分别进行操作
list_2[1].append(55)

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
list_2:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

2、列表的底层实现

引用数组的概念

列表内的元素可以分散的存储在内存中

列表存储的,实际上是这些元素的地址!!!——地址的存储在内存中是连续的

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = list(list_1)   # 浅拷贝   与list_1.copy()功能一样

(1)新增元素

list_1.append(100)
list_2.append("n")

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']

(2)修改元素

list_1[0] = 10
list_2[0] = 20

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [20, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']

(3)对列表型元素进行操作

list_1[1].remove(44)
list_2[1] += [55, 66]

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [20, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 'n']

(4)对元组型元素进行操作

list_2[2] += (8,9)

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah'}, 'n']

元组是不可变的!!!

(5)对字典型元素进行操作

list_1[-2]["age"] = 18

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah', 'age': 18}, 100]
list_2:   [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah', 'age': 18}, 'n']

3、引入深拷贝

浅拷贝之后

  • 针对不可变元素(数字、字符串、元组)的操作,都各自生效了

  • 针对不可变元素(列表、集合)的操作,发生了一些混淆

引入深拷贝

  • 深拷贝将所有层级的相关元素全部复制,完全分开,泾渭分明,避免了上述问题
import copy

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = copy.deepcopy(list_1)
list_1[-1]["age"] = 18
list_2[1].append(55)

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}]
list_2:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

9.1.2 神秘的字典

1、快速的查找

import time

ls_1 = list(range(1000000))
ls_2 = list(range(500))+[-10]*500

start = time.time()
count = 0
for n in ls_2:
    if n in ls_1:
        count += 1
end = time.time()
print("查找{}个元素,在ls_1列表中的有{}个,共用时{}秒".format(len(ls_2), count,round((end-start),2)))
查找1000个元素,在ls_1列表中的有500个,共用时6.19秒
import time

d = {i:i for i in range(100000)}
ls_2 = list(range(500))+[-10]*500

start = time.time()
count = 0
for n in ls_2:
    try:
        d[n]
    except:
        pass
    else:
        count += 1
end = time.time()
print("查找{}个元素,在ls_1列表中的有{}个,共用时{}秒".format(len(ls_2), count,round(end-start)))
查找1000个元素,在ls_1列表中的有500个,共用时0秒

2、字典的底层实现

通过稀疏数组来实现值的存储与访问

字典的创建过程

  • 第一步:创建一个散列表(稀疏数组 N >> n)
d = {}
  • 第一步:通过hash()计算键的散列值
print(hash("python"))
print(hash(1024))
print(hash((1,2)))
-4771046564460599764
1024
3713081631934410656
d["age"] = 18    # 增加键值对的操作,首先会计算键的散列值hash("age")
print(hash("age")) 
  • 第二步:根据计算的散列值确定其在散列表中的位置

极个别时候,散列值会发生冲突,则内部有相应的解决冲突的办法

  • 第三步:在该位置上存入值
for i in range(2, 2):
    print(i)

键值对的访问过程

d["age"]
  • 第一步:计算要访问的键的散列值

  • 第二步:根据计算的散列值,通过一定的规则,确定其在散列表中的位置

  • 第三步:读取该位置上存储的值

     如果存在,则返回该值  
     如果不存在,则报错KeyError
    

3、小结

(1)字典数据类型,通过空间换时间,实现了快速的数据查找

  • 也就注定了字典的空间利用效率低下

(2)因为散列值对应位置的顺序与键在字典中显示的顺序可能不同,因此表现出来字典是无序的

  • 回顾一下 N >> n
    如果N = n,会产生很多位置冲突

  • 思考一下开头的小例子,为什么字典实现了比列表更快速的查找

9.1.3 紧凑的字符串

通过紧凑数组实现字符串的存储

  • 数据在内存中是连续存放的,效率更高,节省空间

  • 思考一下,同为序列类型,为什么列表采用引用数组,而字符串采用紧凑数组

    ** 因为字符串每个元素大小固定,就是一个字符,列表每个元素大小不固定

9.1.4 是否可变

1、不可变类型:数字、字符串、元组

在生命周期中保持内容不变

  • 换句话说,改变了就不是它自己了(id变了)

  • 不可变对象的 += 操作 实际上创建了一个新的对象

x = 1
y = "Python"

print("x id:", id(x))
print("y id:", id(y))
x id: 140718440616768
y id: 2040939892664
x += 2
y += "3.7"

print("x id:", id(x))
print("y id:", id(y))
x id: 140718440616832
y id: 2040992707056

元组并不是总是不可变的

t = (1,[2])
t[1].append(3)

print(t)
(1, [2, 3])

2、可变类型:列表、字典、集合

  • id 保持不变,但是里面的内容可以变

  • 可变对象的 += 操作 实际在原对象的基础上就地修改

ls = [1, 2, 3]
d = {"Name": "Sarah", "Age": 18}

print("ls id:", id(ls))
print("d id:", id(d))
ls id: 2040991750856
d id: 2040992761608
ls += [4, 5]
d_2 = {"Sex": "female"}
d.update(d_2)            # 把d_2 中的元素更新到d中

print("ls id:", id(ls))
print("d id:", id(d))
ls id: 2040991750856
d id: 2040992761608

9.1.5 列表操作的几个小例子

【例1】 删除列表内的特定元素

  • 方法1 存在运算删除法

缺点:每次存在运算,都要从头对列表进行遍历、查找、效率低

alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
while True:
    if s in alist:
        alist.remove(s)
    else:
        break
print(alist)
['2', '2', '4']
  • 方法2 一次性遍历元素执行删除
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
for s in alist:
    if s == "d":
        alist.remove(s)      # remove(s) 删除列表中第一次出现的该元素
print(alist)
['2', '2', 'd', 'd', '4']

解决方法:使用负向索引

alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
for i in range(-len(alist), 0):
    if alist[i] == "d":
        alist.remove(alist[i])      # remove(s) 删除列表中第一次出现的该元素
print(alist)
['2', '2', '4']

【例2】 多维列表的创建

ls = [[0]*10]*5  #将同一个地址复制了五份
ls
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls[0][0] = 1
ls
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

9.2 更加简洁的语法

9.2.1 解析语法

ls = [[0]*10 for i in range(5)]
ls
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls[0][0] = 1
ls
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

1、解析语法的基本结构——以列表解析为例(也称为列表推导)

[expression for value in iterable if conditihon]

  • 三要素:表达式、可迭代对象、if条件(可选)

执行过程

(1)从可迭代对象中拿出一个元素

(2)通过if条件(如果有的话),对元素进行筛选

 若通过筛选:则把元素传递给表达式  
   
 若未通过:  则进入(1)步骤,进入下一次迭代

(3)将传递给表达式的元素,代入表达式进行处理,产生一个结果

(4)将(3)步产生的结果作为列表的一个元素进行存储

(5)重复(1)~(4)步,直至迭代对象迭代结束,返回新创建的列表

# 等价于如下代码
result = []
for value in iterale:
    if condition:
        result.append(expression)

【例】求20以内奇数的平方

squares = []
for i in range(1,21):
    if i%2 == 1:
        squares.append(i**2)
print(squares)   
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
squares = [i**2 for i in range(1,21) if i%2 == 1]
print(squares) 
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]

支持多变量

x = [1, 2, 3]
y = [1, 2, 3]

results = [i*j for i,j in zip(x, y)]
results
[1, 4, 9]

支持循环嵌套

colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ["{} {}".format(color, size) for color in colors for size in sizes]
tshirts
['black S', 'black M', 'black L', 'white S', 'white M', 'white L']

2、其他解析语法的例子

  • 解析语法构造字典(字典推导)
squares = {i: i**2 for i in range(10)}
for k, v in squares.items():
    print(k, ":  ", v)
0 :   0
1 :   1
2 :   4
3 :   9
4 :   16
5 :   25
6 :   36
7 :   49
8 :   64
9 :   81
  • 解析语法构造集合(集合推导)
squares = {i**2 for i in range(10)}
squares
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
  • 生成器表达式
squares = (i**2 for i in range(10))
squares
<generator object <genexpr> at 0x000001DB37A58390>
colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ("{} {}".format(color, size) for color in colors for size in sizes)
for tshirt in tshirts:
    print(tshirt)
black S
black M
black L
white S
white M
white L

9.2.2 条件表达式

expr1 if condition else expr2

【例】将变量n的绝对值赋值给变量x

n = -10
if n >= 0:
    x = n
else:
    x = -n
x
10
n = -10
x = n if n>= 0 else -n
x
10

条件表达式和解析语法简单实用、运行速度相对更快一些,相信大家会慢慢的爱上它们

9.3 三大神器

9.3.1 生成器

ls = [i**2 for i in range(1, 1000001)]
for i in ls:
    pass

缺点:占用大量内存

生成器

(1)采用惰性计算的方式

(2)无需一次性存储海量数据

(3)一边执行一边计算,只计算每次需要的值

(4)实际上一直在执行next()操作,直到无值可取

1、生成器表达式

  • 海量数据,不需存储
squares = (i**2 for i in range(1000000))
for i in squares:
    pass
  • 求0~100的和

无需显示存储全部数据,节省内存

sum((i for i in range(101)))
5050

2、生成器函数——yield

  • 生产斐波那契数列

数列前两个元素为1,1 之后的元素为其前两个元素之和

def fib(max):
    ls = []
    n, a, b = 0, 1, 1
    while n < max:
        ls.append(a)
        a, b = b, a + b
        n = n + 1
    return ls


fib(10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

中间尝试

def fib(max):
    n, a, b = 0, 1, 1
    while n < max:
        print(a)
        a, b = b, a + b
        n = n + 1


fib(10)
1
1
2
3
5
8
13
21
34
55

构造生成器函数

在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

def fib(max):
    n, a, b = 0, 1, 1
    while n < max:
        yield a
        a, b = b, a + b
        n = n + 1
        

fib(10)
<generator object fib at 0x000001BE11B19048>
for i in fib(10):
    print(i)
1
1
2
3
5
8
13
21
34
55

9.3.2 迭代器

1、可迭代对象

可直接作用于for循环的对象统称为可迭代对象:Iterable

(1)列表、元组、字符串、字典、集合、文件

可以使用isinstance()判断一个对象是否是Iterable对象

from collections import Iterable

isinstance([1, 2, 3], Iterable)
True
isinstance({"name": "Sarah"}, Iterable)
True
isinstance('Python', Iterable)
True

(2)生成器

squares = (i**2 for i in range(5))
isinstance(squares, Iterable)
True

生成器不但可以用于for循环,还可以被next()函数调用

print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
0
1
4
9
16

直到没有数据可取,抛出StopIteration

print(next(squares))
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-66-f5163ac9e49b> in <module>
----> 1 print(next(squares))


StopIteration: 

可以被next()函数调用并不断返回下一个值,直至没有数据可取的对象称为迭代器:Iterator

2、迭代器

可以使用isinstance()判断一个对象是否是Iterator对象

(1) 生成器都是迭代器

from collections import Iterator

squares = (i**2 for i in range(5))
isinstance(squares, Iterator)
True

(2) 列表、元组、字符串、字典、集合不是迭代器

isinstance([1, 2, 3], Iterator)
False

可以通过iter(Iterable)创建迭代器

isinstance(iter([1, 2, 3]), Iterator)
True

for item in Iterable 等价于:

先通过iter()函数获取可迭代对象Iterable的迭代器

然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item

当遇到StopIteration的异常后循环结束

(3)zip enumerate 等itertools里的函数是迭代器

x = [1, 2]
y = ["a", "b"]
zip(x, y)
<zip at 0x1be11b13c48>
for i in zip(x, y):
    print(i)
    
isinstance(zip(x, y), Iterator)
(1, 'a')
(2, 'b')





True
numbers = [1, 2, 3, 4, 5]
enumerate(numbers)
<enumerate at 0x1be11b39990>
for i in enumerate(numbers):
    print(i)
    
isinstance(enumerate(numbers), Iterator)
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)





True

(4) 文件是迭代器

with open("测试文件.txt", "r", encoding = "utf-8") as f:
    print(isinstance(f, Iterator))
True

(5)迭代器是可耗尽的

squares = (i**2 for i in range(5))
for square in squares:
    print(square)
0
1
4
9
16
for square in squares:
    print(square)

(6)range()不是迭代器

numbers = range(10)
isinstance(numbers, Iterator)
False
print(len(numbers))   # 有长度
print(numbers[0])     # 可索引
print(9 in numbers)   # 可存在计算
next(numbers)         # 不可被next()调用
10
0
True



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-76-7c59bf859258> in <module>
      2 print(numbers[0])     # 可索引
      3 print(9 in numbers)   # 可存在计算
----> 4 next(numbers)         # 不可被next()调用


TypeError: 'range' object is not an iterator
for number in numbers:
    print(number)
0
1
2
3
4
5
6
7
8
9

不会被耗尽

for number in numbers:
    print(number)
0
1
2
3
4
5
6
7
8
9

可以称range()为懒序列

它是一种序列
  
但并不包含任何内存中的内容
  
而是通过计算来回答问题

9.3.3 装饰器

1、需求的提出

(1)需要对已开发上线的程序添加某些功能

(2)不能对程序中函数的源代码进行修改

(3)不能改变程序中函数的调用方式

比如说,要统计每个函数的运行时间

def f1():
    pass


def f2():
    pass


def f3():
    pass

f1()
f2()
f3()

没问题,我们有装饰器!!!

2、函数对象

函数是Python中的第一类对象

(1)可以把函数赋值给变量

(2)对该变量进行调用,可实现原函数的功能

def square(x):
    return x**2

print(type(square))      # square 是function类的一个实例
<class 'function'>
pow_2 = square          # 可以理解成给这个函数起了个别名pow_2
print(pow_2(5))
print(square(5))
25
25

可以将函数作为参数进行传递

3、高阶函数

(1)接收函数作为参数

(2)或者返回一个函数

满足上述条件之一的函数称之为高阶函数

def square(x):
    return x**2


def pow_2(fun):
    return fun


f = pow_2(square)
f(8)
64
print(f == square)
True

4、 嵌套函数

在函数内部定义一个函数

def outer():
    print("outer is running")
    
    def inner():
        print("inner is running")
        
    inner()


outer()
outer is running
inner is running

5、闭包

def outer():
    x = 1
    z = 10
    
    def inner():
        y = x+100
        return y, z
        
    return inner


f = outer()                # 实际上f包含了inner函数本身+outer函数的环境
print(f)
<function outer.<locals>.inner at 0x000001BE11B1D730>
print(f.__closure__)         # __closure__属性中包含了来自外部函数的信息
for i in f.__closure__:
    print(i.cell_contents)
(<cell at 0x000001BE0FDE06D8: int object at 0x00007FF910D59340>, <cell at 0x000001BE0FDE0A98: int object at 0x00007FF910D59460>)
1
10
res = f()
print(res)
(101, 10)

闭包:延伸了作用域的函数

如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包

闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)

  • 一旦在内层函数重新定义了相同名字的变量,则变量成为局部变量
def outer():
    x = 1
    
    def inner():
        x = x+100
        return x
        
    return inner


f = outer()             
f()
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-87-d2da1048af8b> in <module>
     10 
     11 f = outer()
---> 12 f()


<ipython-input-87-d2da1048af8b> in inner()
      3 
      4     def inner():
----> 5         x = x+100
      6         return x
      7 


UnboundLocalError: local variable 'x' referenced before assignment

nonlocal允许内嵌的函数来修改闭包变量

def outer():
    x = 1
    
    def inner():
        nonlocal x
        x = x+100
        return x  
    return inner


f = outer()             
f()
1





101

6、一个简单的装饰器

嵌套函数实现

import time

def timer(func):
    
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner


def f1():
    print("f1 run")
    time.sleep(1)



f1 = timer(f1)             # 包含inner()和timer的环境,如传递过来的参数func
f1()
inner run
f1 run
f1 函数运行用时1.00秒

语法糖

import time

def timer(func):
    
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner

@timer                      # 相当于实现了f1 = timer(f1)
def f1():
    print("f1 run")
    time.sleep(1)
    
    
f1()
inner run
f1 run
f1 函数运行用时1.00秒

7、装饰有参函数

import time


def timer(func):
    
    def inner(*args, **kwargs):
        print("inner run")
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner


@timer                # 相当于实现了f1 = timer(f1)
def f1(n):
    print("f1 run")
    time.sleep(n)

    
f1(2)
inner run
f1 run
f1 函数运行用时2.00秒

被装饰函数有返回值的情况

import time


def timer(func):
    
    def inner(*args, **kwargs):
        print("inner run")
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
        return res
    
    return inner


@timer                   # 相当于实现了f1 = timer(f1)
def f1(n):
    print("f1 run")
    time.sleep(n)
    return "wake up"
    
res = f1(2)
print(res)
inner run
f1 run
f1 函数运行用时2.00秒
wake up

8、带参数的装饰器

装饰器本身要传递一些额外参数

  • 需求:有时需要统计绝对时间,有时需要统计绝对时间的2倍
def timer(method):
    
    def outer(func):
    
        def inner(*args, **kwargs):
            print("inner run")
            if method == "origin":
                print("origin_inner run")
                start = time.time()
                res = func(*args, **kwargs)
                end = time.time()
                print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
            elif method == "double":
                print("double_inner run")
                start = time.time()
                res = func(*args, **kwargs)
                end = time.time()
                print("{} 函数运行双倍用时{:.2f}秒".format(func.__name__, 2*(end-start)))
            return res
    
        return inner
    
    return outer


@timer(method="origin")  # 相当于timer = timer(method = "origin")   f1 = timer(f1)
def f1():
    print("f1 run")
    time.sleep(1)
    
    
@timer(method="double")
def f2():
    print("f2 run")
    time.sleep(1)


f1()
print()
f2()
inner run
origin_inner run
f1 run
f1 函数运行用时1.00秒

inner run
double_inner run
f2 run
f2 函数运行双倍用时2.00秒

理解闭包是关键!!!

9、何时执行装饰器

  • 一装饰就执行,不必等调用
func_names=[]
def find_function(func):
    print("run")
    func_names.append(func)
    return func


@find_function
def f1():
    print("f1 run")
    

@find_function
def f2():
    print("f2 run")
    

@find_function
def f3():
    print("f3 run")
    

run
run
run
for func in func_names:
    print(func.__name__)
    func()
    print()
f1
f1 run

f2
f2 run

f3
f3 run

10、回归本源

  • 原函数的属性被掩盖了
import time

def timer(func):
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner

@timer                # 相当于实现了f1 = timer(f1)
def f1():
    time.sleep(1)
    print("f1 run")

print(f1.__name__)    
inner
  • 回来
import time
from functools import wraps

def timer(func):
    @wraps(func)
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner

@timer                # 相当于实现了f1 = timer(f1)
def f1():
    time.sleep(1)
    print("f1 run")

print(f1.__name__) 
f1()
f1
inner run
f1 run
f1 函数运行用时1.00秒

Python标准库

Python自身提供了比较丰富的生态,拿来即用,可极大的提高开发效率

10.1 time库

Python处理时间的标准库

1、获取现在时间

(1)time.localtime() 本地时间

(2)time.gmtime() UTC世界统一时间

北京时间比时间统一时间UTC早8个小时

import time

t_local = time.localtime()
t_UTC = time.gmtime()
print("t_local", t_local)           # 本地时间
print("t_UTC", t_UTC)               # UTC统一时间
t_local time.struct_time(tm_year=2023, tm_mon=11, tm_mday=1, tm_hour=16, tm_min=49, tm_sec=7, tm_wday=2, tm_yday=305, tm_isdst=0)
t_UTC time.struct_time(tm_year=2023, tm_mon=11, tm_mday=1, tm_hour=8, tm_min=49, tm_sec=7, tm_wday=2, tm_yday=305, tm_isdst=0)
time.ctime()                      # 返回本地时间的字符串
'Thu Aug 29 16:44:52 2019'

2、时间戳与计时器

(1)time.time()   返回自纪元以来的秒数,记录sleep

(2)time.perf_counter()   随意选取一个时间点,记录现在时间到该时间点的间隔秒数,记录sleep

(3)time.process_time()   随意选取一个时间点,记录现在时间到该时间点的间隔秒数,不记录sleep

perf_counter()精度较time()更高一些

t_1_start = time.time()
t_2_start = time.perf_counter()
t_3_start = time.process_time()
print(t_1_start)
print(t_2_start)
print(t_3_start)

res = 0
for i in range(1000000):
    res += i
    
time.sleep(5)
t_1_end = time.time()
t_2_end = time.perf_counter()
t_3_end = time.process_time()

print("time方法:{:.3f}秒".format(t_1_end-t_1_start))
print("perf_counter方法:{:.3f}秒".format(t_2_end-t_2_start))
print("process_time方法:{:.3f}秒".format(t_3_end-t_3_start))
1567068710.7269545
6009.0814064
2.25
time方法:5.128秒
perf_counter方法:5.128秒
process_time方法:0.125秒

3、格式化

(1)time.strftime 自定义格式化输出

lctime = time.localtime()
time.strftime("%Y-%m-%d %A %H:%M:%S", lctime)
'2019-08-29 Thursday 16:54:35'

4、睡觉觉

(1)time.sleep()

10.2 random库

随机数在计算机应用中十分常见

Python通过random库提供各种伪随机数

基本可以用于除加密解密算法外的大多数工程应用

1、随机种子——seed(a=None)

(1)相同种子会产生相同的随机数

(2)如果不设置随机种子,以系统当前时间为默认值

from random import *

seed(10)
print(random())
seed(10)
print(random())
0.5714025946899135
0.5714025946899135
print(random())
0.20609823213950174

2、产生随机整数

(1)randint(a, b)——产生[a, b]之间的随机整数

numbers = [randint(1,10) for i in range(10)]
numbers
[3, 5, 6, 3, 8, 4, 8, 10, 7, 1]

(2)randrange(a)——产生[0, a)之间的随机整数

numbers = [randrange(10) for i in range(10)]
numbers
[6, 3, 0, 0, 7, 4, 9, 1, 8, 1]

(3)randrange(a, b, step)——产生[a, b)之间以setp为步长的随机整数

numbers = [randrange(0, 10, 3) for i in range(10)]
numbers
[9, 6, 6, 9, 3, 6, 6, 3, 9, 3]

3、产生随机浮点数

(1)random()——产生[0.0, 1.0)之间的随机浮点数

numbers = [random() for i in range(10)]
numbers
[0.9819392547566425,
 0.19092611184488173,
 0.3486810954900942,
 0.9704866291141572,
 0.4456072691491385,
 0.6807895695768549,
 0.14351321471670841,
 0.5218569500629634,
 0.8648825892767497,
 0.26702706855337954]

(2)uniform(a, b)——产生[a, b]之间的随机浮点数

numbers = [uniform(2.1, 3.5) for i in range(10)]
numbers
[2.523598043850906,
 3.0245903649048116,
 3.4202356766870463,
 2.344031169179946,
 2.3465252151503173,
 3.181989084829388,
 2.5592895031615703,
 2.413131937436849,
 2.8627907782614415,
 2.16114212173462]

4、序列用函数

(1)choice(seq)——从序列类型中随机返回一个元素

choice(['win', 'lose', 'draw'])
'draw'
choice("python")
'h'

(2)choices(seq,weights=None, k)——对序列类型进行k次重复采样,可设置权重

choices(['win', 'lose', 'draw'], k=5)
['draw', 'lose', 'draw', 'draw', 'draw']
choices(['win', 'lose', 'draw'], [4,4,2], k=10)
['lose', 'draw', 'lose', 'win', 'draw', 'lose', 'draw', 'win', 'win', 'lose']

(3)shuffle(seq)——将序列类型中元素随机排列,返回打乱后的序列

numbers = ["one", "two", "three", "four"]
shuffle(numbers)
numbers
['four', 'one', 'three', 'two']

(4)sample(pop, k)——从pop类型中随机选取k个元素,以列表类型返回

sample([10, 20, 30, 40, 50], k=3)
[20, 30, 10]

5、概率分布——以高斯分布为例

gauss(mean, std)——生产一个符合高斯分布的随机数

number = gauss(0, 1)
number
0.6331522345532208

多生成几个

import matplotlib.pyplot as plt

res = [gauss(0, 1) for i in range(100000)]

plt.hist(res, bins=1000)
plt.show()

在这里插入图片描述

【例1】用random库实现简单的微信红包分配

import random


def red_packet(total, num):
    for i in range(1, num):
        per = random.uniform(0.01, total/(num-i+1)*2)          # 保证每个人获得红包的期望是total/num
        # 均匀分布的期望是(a+b)/2   0.01~0   i=1: ( [total/(num-i+1)]*2 )/2 = total/num=total/5   
        # i=2 剩余total的期望变成  [total/4]*2/2 = total / 4
        total = total - per
        print("第{}位红包金额: {:.2f}元".format(i, per))
    else:
        print("第{}位红包金额: {:.2f}元".format(num, total))
            
            
red_packet(10, 5)
第1位红包金额: 1.85元
第2位红包金额: 3.90元
第3位红包金额: 0.41元
第4位红包金额: 3.30元
第5位红包金额: 0.54元
import random
import numpy as np


def red_packet(total, num):
    ls = []
    for i in range(1, num):
        per = round(random.uniform(0.01, total/(num-i+1)*2), 2)     # 保证每个人获得红包的期望是total/num
        ls.append(per)
        total = total - per
    else:
        ls.append(total)
        
    return ls
            
            
# 重复发十万次红包,统计每个位置的平均值(约等于期望)
res = []
for i in range(100000):
    ls = red_packet(10,5)
    res.append(ls)

res = np.array(res)
print(res[:10])
np.mean(res, axis=0)
[[1.71 1.57 0.36 1.25 5.11]
 [1.96 0.85 1.46 3.29 2.44]
 [3.34 0.27 1.9  0.64 3.85]
 [1.99 1.08 3.86 1.69 1.38]
 [1.56 1.47 0.66 4.09 2.22]
 [0.57 0.44 1.87 5.81 1.31]
 [0.47 1.41 3.97 1.28 2.87]
 [2.65 1.82 1.22 2.02 2.29]
 [3.16 1.2  0.3  3.66 1.68]
 [2.43 0.16 0.11 0.79 6.51]]





array([1.9991849, 2.0055725, 2.0018144, 2.0022472, 1.991181 ])

【例2】生产4位由数字和英文字母构成的验证码

import random
import string

print(string.digits)
print(string.ascii_letters)

s=string.digits + string.ascii_letters
v=random.sample(s,4) 
print(v)
print(''.join(v))
0123456789
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
['n', 'Q', '4', '7']
nQ47

10.3 collections库——容器数据类型

import collections

1、namedtuple——具名元组

  • 点的坐标,仅看数据,很难知道表达的是一个点的坐标
p = (1, 2)
  • 构建一个新的元组子类

    定义方法如下:typename 是元组名字,field_names 是域名

collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
Point = collections.namedtuple("Point", ["x", "y"])
p = Point(1, y=2) #位置参数,关键词参数
p  
Point(x=1, y=2)
  • 可以调用属性
print(p.x)
print(p.y)
1
2
  • 有元组的性质
print(p[0])
print(p[1])
x, y = p
print(x)
print(y)
1
2
1
2
  • 确实是元组的子类
print(isinstance(p, tuple))
True

【例】模拟扑克牌

Card = collections.namedtuple("Card", ["rank", "suit"])
ranks = [str(n) for n in range(2, 11)] + list("JQKA")    
suits = "spades diamonds clubs hearts".split()
print("ranks", ranks)
print("suits", suits)
cards = [Card(rank, suit) for rank in ranks
                          for suit in suits]
cards
ranks ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
suits ['spades', 'diamonds', 'clubs', 'hearts']





[Card(rank='2', suit='spades'),
 Card(rank='2', suit='diamonds'),
 Card(rank='2', suit='clubs'),
 Card(rank='2', suit='hearts'),
 Card(rank='3', suit='spades'),
 Card(rank='3', suit='diamonds'),
 Card(rank='3', suit='clubs'),
 Card(rank='3', suit='hearts'),
 Card(rank='4', suit='spades'),
 Card(rank='4', suit='diamonds'),
 Card(rank='4', suit='clubs'),
 Card(rank='4', suit='hearts'),
 Card(rank='5', suit='spades'),
 Card(rank='5', suit='diamonds'),
 Card(rank='5', suit='clubs'),
 Card(rank='5', suit='hearts'),
 Card(rank='6', suit='spades'),
 Card(rank='6', suit='diamonds'),
 Card(rank='6', suit='clubs'),
 Card(rank='6', suit='hearts'),
 Card(rank='7', suit='spades'),
 Card(rank='7', suit='diamonds'),
 Card(rank='7', suit='clubs'),
 Card(rank='7', suit='hearts'),
 Card(rank='8', suit='spades'),
 Card(rank='8', suit='diamonds'),
 Card(rank='8', suit='clubs'),
 Card(rank='8', suit='hearts'),
 Card(rank='9', suit='spades'),
 Card(rank='9', suit='diamonds'),
 Card(rank='9', suit='clubs'),
 Card(rank='9', suit='hearts'),
 Card(rank='10', suit='spades'),
 Card(rank='10', suit='diamonds'),
 Card(rank='10', suit='clubs'),
 Card(rank='10', suit='hearts'),
 Card(rank='J', suit='spades'),
 Card(rank='J', suit='diamonds'),
 Card(rank='J', suit='clubs'),
 Card(rank='J', suit='hearts'),
 Card(rank='Q', suit='spades'),
 Card(rank='Q', suit='diamonds'),
 Card(rank='Q', suit='clubs'),
 Card(rank='Q', suit='hearts'),
 Card(rank='K', suit='spades'),
 Card(rank='K', suit='diamonds'),
 Card(rank='K', suit='clubs'),
 Card(rank='K', suit='hearts'),
 Card(rank='A', suit='spades'),
 Card(rank='A', suit='diamonds'),
 Card(rank='A', suit='clubs'),
 Card(rank='A', suit='hearts')]
from random import *
# 洗牌
shuffle(cards) 
cards
[Card(rank='J', suit='hearts'),
 Card(rank='A', suit='hearts'),
 Card(rank='3', suit='hearts'),
 Card(rank='8', suit='hearts'),
 Card(rank='K', suit='hearts'),
 Card(rank='7', suit='spades'),
 Card(rank='5', suit='hearts'),
 Card(rank='A', suit='spades'),
 Card(rank='10', suit='spades'),
 Card(rank='J', suit='diamonds'),
 Card(rank='K', suit='clubs'),
 Card(rank='4', suit='spades'),
 Card(rank='2', suit='diamonds'),
 Card(rank='Q', suit='spades'),
 Card(rank='A', suit='clubs'),
 Card(rank='A', suit='diamonds'),
 Card(rank='6', suit='hearts'),
 Card(rank='7', suit='diamonds'),
 Card(rank='5', suit='diamonds'),
 Card(rank='10', suit='clubs'),
 Card(rank='8', suit='clubs'),
 Card(rank='9', suit='clubs'),
 Card(rank='6', suit='clubs'),
 Card(rank='6', suit='diamonds'),
 Card(rank='5', suit='clubs'),
 Card(rank='3', suit='diamonds'),
 Card(rank='4', suit='hearts'),
 Card(rank='3', suit='clubs'),
 Card(rank='7', suit='hearts'),
 Card(rank='2', suit='spades'),
 Card(rank='J', suit='clubs'),
 Card(rank='9', suit='spades'),
 Card(rank='J', suit='spades'),
 Card(rank='10', suit='hearts'),
 Card(rank='2', suit='clubs'),
 Card(rank='8', suit='diamonds'),
 Card(rank='6', suit='spades'),
 Card(rank='10', suit='diamonds'),
 Card(rank='9', suit='hearts'),
 Card(rank='3', suit='spades'),
 Card(rank='8', suit='spades'),
 Card(rank='Q', suit='clubs'),
 Card(rank='Q', suit='hearts'),
 Card(rank='5', suit='spades'),
 Card(rank='7', suit='clubs'),
 Card(rank='4', suit='clubs'),
 Card(rank='2', suit='hearts'),
 Card(rank='K', suit='diamonds'),
 Card(rank='K', suit='spades'),
 Card(rank='Q', suit='diamonds'),
 Card(rank='4', suit='diamonds'),
 Card(rank='9', suit='diamonds')]
# 随机抽一张牌
choice(cards)
Card(rank='4', suit='hearts')
# 随机抽多张牌
sample(cards, k=5)
[Card(rank='4', suit='hearts'),
 Card(rank='2', suit='clubs'),
 Card(rank='Q', suit='diamonds'),
 Card(rank='9', suit='spades'),
 Card(rank='10', suit='hearts')]

2、Counter——计数器工具

from collections import Counter
s = "牛奶奶找刘奶奶买牛奶"
colors = ['red', 'blue', 'red', 'green', 'blue', 'blue']
cnt_str = Counter(s)
cnt_color = Counter(colors)
print(cnt_str)
print(cnt_color)
Counter({'奶': 5, '牛': 2, '找': 1, '刘': 1, '买': 1})
Counter({'blue': 3, 'red': 2, 'green': 1})
  • 是字典的一个子类
print(isinstance(Counter(), dict))
True
  • 最常见的统计——most_commom(n)
    提供 n 个频率最高的元素和计数
cnt_color.most_common(2)
[('blue', 3), ('red', 2)]
  • 元素展开——elements()
list(cnt_str.elements())
['牛', '牛', '奶', '奶', '奶', '奶', '奶', '找', '刘', '买']
  • 其他一些加减操作
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c+d
Counter({'a': 4, 'b': 3})

【例】从一副牌中抽取10张,大于10的比例有多少

cards = collections.Counter(tens=16, low_cards=36)
seen = sample(list(cards.elements()), k=10)
print(seen) 
['tens', 'low_cards', 'low_cards', 'low_cards', 'tens', 'tens', 'low_cards', 'low_cards', 'low_cards', 'low_cards']
seen.count('tens') / 10
0.3

3、deque——双向队列

列表访问数据非常快速

插入和删除操作非常慢——通过移动元素位置来实现

特别是 insert(0, v) 和 pop(0),在列表开始进行的插入和删除操作

双向队列可以方便的在队列两边高效、快速的增加和删除元素

from collections import deque

d = deque('cde') 
d
deque(['c', 'd', 'e'])
d.append("f")            # 右端增加
d.append("g")
d.appendleft("b")        # 左端增加
d.appendleft("a")
d
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
d.pop()           # 右端删除 
d.popleft()       # 左端删除
d
deque(['b', 'c', 'd', 'e', 'f'])

deque 其他用法可参考官方文档

10.4 itertools库——迭代器

1、排列组合迭代器

(1)product——笛卡尔积

import itertools

for i in itertools.product('ABC', '01'):
    print(i)
('A', '0')
('A', '1')
('B', '0')
('B', '1')
('C', '0')
('C', '1')
for i in itertools.product('ABC', repeat=3):
    print(i) 
('A', 'A', 'A')
('A', 'A', 'B')
('A', 'A', 'C')
('A', 'B', 'A')
('A', 'B', 'B')
('A', 'B', 'C')
('A', 'C', 'A')
('A', 'C', 'B')
('A', 'C', 'C')
('B', 'A', 'A')
('B', 'A', 'B')
('B', 'A', 'C')
('B', 'B', 'A')
('B', 'B', 'B')
('B', 'B', 'C')
('B', 'C', 'A')
('B', 'C', 'B')
('B', 'C', 'C')
('C', 'A', 'A')
('C', 'A', 'B')
('C', 'A', 'C')
('C', 'B', 'A')
('C', 'B', 'B')
('C', 'B', 'C')
('C', 'C', 'A')
('C', 'C', 'B')
('C', 'C', 'C')

(2) permutations——排列

for i in itertools.permutations('ABCD', 3):   # 3 是排列的长度
    print(i)
('A', 'B', 'C')
('A', 'B', 'D')
('A', 'C', 'B')
('A', 'C', 'D')
('A', 'D', 'B')
('A', 'D', 'C')
('B', 'A', 'C')
('B', 'A', 'D')
('B', 'C', 'A')
('B', 'C', 'D')
('B', 'D', 'A')
('B', 'D', 'C')
('C', 'A', 'B')
('C', 'A', 'D')
('C', 'B', 'A')
('C', 'B', 'D')
('C', 'D', 'A')
('C', 'D', 'B')
('D', 'A', 'B')
('D', 'A', 'C')
('D', 'B', 'A')
('D', 'B', 'C')
('D', 'C', 'A')
('D', 'C', 'B')
for i in itertools.permutations(range(3)):
    print(i)
(0, 1, 2)
(0, 2, 1)
(1, 0, 2)
(1, 2, 0)
(2, 0, 1)
(2, 1, 0)

(3)combinations——组合

for i in itertools.combinations('ABCD', 2):  # 2是组合的长度
    print(i)
('A', 'B')
('A', 'C')
('A', 'D')
('B', 'C')
('B', 'D')
('C', 'D')
for i in itertools.combinations(range(4), 3):
    print(i)
(0, 1, 2)
(0, 1, 3)
(0, 2, 3)
(1, 2, 3)

(4)combinations_with_replacement——元素可重复组合

for i in itertools.combinations_with_replacement('ABC', 2):  # 2是组合的长度
    print(i)
('A', 'A')
('A', 'B')
('A', 'C')
('B', 'B')
('B', 'C')
('C', 'C')
for i in itertools.product('ABC',repeat=2):
    print(i)
('A', 'A')
('A', 'B')
('A', 'C')
('B', 'A')
('B', 'B')
('B', 'C')
('C', 'A')
('C', 'B')
('C', 'C')

2、拉链

(1)zip——短拉链

for i in zip("ABC", "012", "xyz"):
    print(i)
('A', '0', 'x')
('B', '1', 'y')
('C', '2', 'z')

长度不一时,执行到最短的对象处,就停止

for i in zip("ABC", [0, 1, 2, 3, 4, 5]):          # 注意zip是内置的,不需要加itertools
    print(i)
('A', 0)
('B', 1)
('C', 2)

(2)zip_longest——长拉链

长度不一时,执行到最长的对象处,就停止,缺省元素用None或指定字符替代

for i in itertools.zip_longest("ABC", "012345"):
    print(i)
('A', '0')
('B', '1')
('C', '2')
(None, '3')
(None, '4')
(None, '5')
for i in itertools.zip_longest("ABC", "012345", fillvalue = "?"):
    print(i)
('A', '0')
('B', '1')
('C', '2')
('?', '3')
('?', '4')
('?', '5')

3、无穷迭代器

(1)count(start=0, step=1)——计数

创建一个迭代器,它从 start 值开始,返回均匀间隔的值
itertools.count(10)
10
11
12
.
.
.

(2)cycle(iterable)——循环

创建一个迭代器,返回 iterable 中所有元素,无限重复
itertools.cycle("ABC")
A
B
C
A
B
C
.
.
.

(3)repeat(object [, times])——重复

创建一个迭代器,不断重复 object 。除非设定参数 times ,否则将无限重复
for i in itertools.repeat(10, 3):
    print(i)
10
10
10

4、其他

(1)chain(iterables)——锁链

把一组迭代对象串联起来,形成一个更大的迭代器
for i in itertools.chain('ABC', [1, 2, 3]):
    print(i)
A
B
C
1
2
3

(2)enumerate(iterable, start=0)——枚举(Python内置)

产出由两个元素组成的元组,结构是(index, item),其中index 从start开始,item从iterable中取
for i in enumerate("Python", start=1):
    print(i)
(1, 'P')
(2, 'y')
(3, 't')
(4, 'h')
(5, 'o')
(6, 'n')

(3)groupby(iterable, key=None)——分组

创建一个迭代器,按照key指定的方式,返回 iterable 中连续的键和组
一般来说,要预先对数据进行排序
key为None默认把连续重复元素分组
for key, group in itertools.groupby('AAAABBBCCDAABBB'):
    print(key, list(group))
A ['A', 'A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
D ['D']
A ['A', 'A']
B ['B', 'B', 'B']
animals = ["duck", "eagle", "rat", "giraffe", "bear", "bat", "dolphin", "shark", "lion"]
animals.sort(key=len)
print(animals)
['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark', 'giraffe', 'dolphin']
for key, group in itertools.groupby(animals, key=len):
    print(key, list(group))
3 ['rat', 'bat']
4 ['duck', 'bear', 'lion']
5 ['eagle', 'shark']
7 ['giraffe', 'dolphin']
animals = ["duck", "eagle", "rat", "giraffe", "bear", "bat", "dolphin", "shark", "lion"]
animals.sort(key=lambda x: x[0])
print(animals)
for key, group in itertools.groupby(animals, key=lambda x: x[0]):
    print(key, list(group))
['bear', 'bat', 'duck', 'dolphin', 'eagle', 'giraffe', 'lion', 'rat', 'shark']
b ['bear', 'bat']
d ['duck', 'dolphin']
e ['eagle']
g ['giraffe']
l ['lion']
r ['rat']
s ['shark']

itertools 其他函数可参考官方文档

再谈编程

15.1 Python之禅

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
  • Beautiful is better than ugly

整齐、易读胜过混乱、晦涩

  • Simple is better than complex

简约胜过复杂

  • Complex is better than complicated

复杂胜过晦涩

  • Flat is better than nested

扁平胜过嵌套

  • Now is better than never.

  • Although never is often better than right now.

理解一:先行动起来,编写行之有效的代码,不要企图一开始就编写完美无缺的代码

理解二:做比不做要好,但是盲目的不加思考的去做还不如不做

  • If the implementation is hard to explain, it’s a bad idea.
  • If the implementation is easy to explain, it may be a good idea.

如果方案很难解释,很可能不是有一个好的方案,反之亦然

【个人感悟】

1、首先要行动起来,编写行之有效的代码;

2、如果都能解决问题,选择更加简单的方案;

3、整齐、易读、可维护性、可扩展性好;

4、强壮、健壮、鲁棒性好;

5、响应速度快,占用空间少。

有些时候,鱼和熊掌不可兼得,根据实际情况进行相应的取舍

15.2 时间复杂度分析

【1】代数分析

求最大值和排序

import numpy as np
x = np.random.randint(100, size=10)
x
array([13, 14, 33, 79, 18, 26, 17, 65, 87, 63])
  • 寻找最大值的时间复杂度为O(n)

  • 选择排序时间复杂度O(n^2)

代数分析

def one(x):
    """常数函数"""
    return np.ones(len(x))

def log(x):
    """对数函数"""
    return np.log(x)

def equal(x):
    """线性函数"""
    return x

def n_logn(x):
    """nlogn函数"""
    return x*np.log(x)

def square(x):
    """平方函数"""
    return x**2

def exponent(x):
    """指数函数"""
    return 2**x
import matplotlib.pyplot as plt
plt.style.use("seaborn-whitegrid")

t = np.linspace(1, 20, 100)
methods = [one, log, equal, n_logn, square, exponent]
method_labels = ["$y = 1$", "$y = log(x)$", "$y = x$", "$y = xlog(x)$", "$y = x^2$", "$y = 2^x$"]
plt.figure(figsize=(12, 6))
for method, method_label in zip(methods, method_labels):
    plt.plot(t, method(t), label=method_label, lw=3)
plt.xlim(1, 20)
plt.ylim(0, 40)
plt.legend()
<matplotlib.legend.Legend at 0x22728098e80>

在这里插入图片描述

我们的最爱:常数函数和对数函数

勉强接受:线性函数和nlogn函数

难以承受:平方函数和指数函数

【2】三集不相交问题

问题描述:
假设有A、B、C三个序列,任一序列内部没有重复元素,欲知晓三个序列交集是否为空

import random
def creat_sequence(n):
    A = random.sample(range(1, 1000), k=n)
    B = random.sample(range(1000, 2000), k=n)
    C = random.sample(range(2000, 3000), k=n)
    return A, B, C
A, B, C = creat_sequence(100)
def no_intersection_1(A, B, C):
    for a in A:
        for b in B:
            for c in C:
                if a == b == c:
                    return False
    return True

%timeit no_intersection_1(A, B, C)
no_intersection_1(A, B, C)
36.7 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)





True
def no_intersection_2(A, B, C):
    for a in A:
        for b in B:
            if a == b:
                for c in C:
                    if a == c:
                        return False
    return True

%timeit no_intersection_2(A, B, C)
301 µs ± 37.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
import time

res_n_3 = []
res_n_2 = []

for n in [10, 20, 100]:
    A, B, C = creat_sequence(n)
    start_1 = time.time() 
    for i in range(100):
        no_intersection_1(A, B, C)
    end_1 = time.time()
    for i in range(100):
        no_intersection_2(A, B, C)
    end_2 = time.time()
    res_n_3.append(str(round((end_1 - start_1)*1000))+"ms")
    res_n_2.append(str(round((end_2 - end_1)*1000))+"ms")

print("{0:<23}{1:<15}{2:<15}{3:<15}".format("方法", "n=10", "n=20", "n=100"))
print("{0:<25}{1:<15}{2:<15}{3:<15}".format("no_inte rsection_1", *res_n_3))
print("{0:<25}{1:<15}{2:<15}{3:<15}".format("no_intersection_2", *res_n_2))
方法                     n=10           n=20           n=100          
no_inte rsection_1       6ms            42ms           4001ms         
no_intersection_2        0ms            1ms            24ms           

【3】元素唯一性问题

问题描述:A 中的元素是否唯一

def unique_1(A):
    for i in range(len(A)):
        for j in range(i+1, len(A)):
            if A[i] == A[j]:
                return False
    return True
def unique_2(A):
    A_sort = sorted(A)  # n*log(n)
    for i in range(len(A_sort)-1):
            if A[i] == A[i+1]:
                return False
    return True
import random
res_n_2 = []
res_n_log_n = []

for n in [100, 1000]:
    A = list(range(n))
    random.shuffle(A)
    start_1 = time.time()
    for i in range(100):
        unique_1(A)
    end_1 = time.time()
    for i in range(100):
        unique_2(A)
    end_2 = time.time()
    res_n_2.append(str(round((end_1 - start_1)*1000))+"ms")
    res_n_log_n.append(str(round((end_2 - end_1)*1000))+"ms")

print("{0:<13}{1:<15}{2:<15}".format("方法", "n=100", "n=1000"))
print("{0:<15}{1:<15}{2:<15}".format("unique_1", *res_n_2))
print("{0:<15}{1:<15}{2:<15}".format("unique_2", *res_n_log_n))
方法           n=100          n=1000         
unique_1       49ms           4044ms         
unique_2       1ms            21ms           

【4】第n个斐波那契数

a(n+2) = a(n+1) + a(n)

def bad_fibonacci(n):
    if n <= 1:
        return n
    else:
        return  bad_fibonacci(n-2)+ bad_fibonacci(n-1)

O(2^n)

def good_fibonacci(n):
    i, a, b = 0, 0, 1
    while i < n:
        a, b = b, a+b
        i += 1
    return a
3

O(n)

%timeit  bad_fibonacci(10)
20.6 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit good_fibonacci(10)
875 ns ± 24.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

【5】最大盛水容器(leetcode第11题)

暴力求解——双循环

def max_area_double_cycle(height):
    """暴力穷举双循环"""
    i_left, i_right, max_area = 0,0,0
    for i in range(len(height)-1):
        for j in range(i+1, len(height)):
            area = (j-i) * min(height[j], height[i])
            if area > max_area:
                i_left, i_right, max_area = i, j, area
    return  i_left, i_right, max_area
height = np.random.randint(1, 50, size=10)
print(height)
max_area_double_cycle(height)
[10 11 41 26  2 44 26 43 36 30]





(2, 8, 216)
import matplotlib.pyplot as plt

plt.bar(range(10), height, width=0.5)
plt.xticks(range(0, 10, 1))
([<matplotlib.axis.XTick at 0x22728e01b00>,
  <matplotlib.axis.XTick at 0x227289ce518>,
  <matplotlib.axis.XTick at 0x22728e01358>,
  <matplotlib.axis.XTick at 0x22728f38c50>,
  <matplotlib.axis.XTick at 0x22728f38b00>,
  <matplotlib.axis.XTick at 0x22728f4f4a8>,
  <matplotlib.axis.XTick at 0x22728f4f978>,
  <matplotlib.axis.XTick at 0x22728f4fe48>,
  <matplotlib.axis.XTick at 0x22728f60358>,
  <matplotlib.axis.XTick at 0x22728f60828>],
 <a list of 10 Text xticklabel objects>)

在这里插入图片描述

双向指针

def max_area_bothway_points(height):
    """双向指针法"""
    
    i = 0
    j = len(height)-1
    i_left, j_right, max_area=0, 0, 0
    while i < j:
        area = (j-i) * min(height[i], height[j])
        if area > max_area:
            i_left, j_right, max_area = i, j, area
        if height[i] == min(height[i], height[j]):
            i += 1
        else:
            j -= 1
    return i_left, j_right, max_area
 max_area_bothway_points(height)
(2, 8, 216)
double_cycle = []
bothway_points = []

for n in [5, 50, 500]:
    height = np.random.randint(1, 50, size=n)
    start_1 = time.time()
    for i in range(100):
        max_area_double_cycle(height)
    end_1 = time.time()
    for i in range(100):
        max_area_bothway_points(height)
    end_2 = time.time()
    double_cycle.append(str(round((end_1 - start_1)*1000))+"ms")
    bothway_points.append(str(round((end_2 - end_1)*1000))+"ms")

print("{0:<15}{1:<15}{2:<15}{3:<15}".format("方法", "n=5", "n=50", "n=500"))
print("{0:<13}{1:<15}{2:<15}{3:<15}".format("暴力循环", *double_cycle))
print("{0:<13}{1:<15}{2:<15}{3:<15}".format("双向指针", *bothway_points))   
方法             n=5            n=50           n=500          
暴力循环         3ms            97ms           7842ms         
双向指针         2ms            8ms            56ms           

【6】是不是时间复杂度低就一定好?

100000n VS 0.00001n^2

【7】影响运算速度的因素

  • 硬件

  • 软件

  • 算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值