学习Python全套代码【超详细】Python入门、核心语法、数据结构、Python进阶

for i in range(1, 10):
   	for j in range(1, i + 1):
       	print("%s*%s=%s" % (j, i, i * j), end=" ")
   	print()


2.4.2.4 break 和continue

有时候在循环内部,我们需要临时略过某一次循环或者干脆跳出整个循环,这时候就需要用到break和continue。

在下面这个例子中,我们使用for循环和continue来打印出10以内的所有奇数相加的式子,并求出它们的和。

total = 0

for i in range(10):
   	if i % 2 == 0:
       	continue
   	print(i, end=" + ")
   	total += i

print(" = %s" % total)


关键字break用来打断整个循环并跳出。看这个例子,给一个字符串,这个字符串是一个小数,要求打印出小数的整数部分。

s = "238.9237834829"

for i in s:
   	if i == '.':
       	print()
       	break

   	print(i, end='')


小数的整数部分都在小数点的左边,而我们遍历字符串是从左往右,所以当遇到小数点后,就停止遍历,这样就正好实现了打印这个小数的整数部分。

2.4.3 演示:智能密码锁程序

最近几年很流行的智能密码锁程序,除了可以用指纹开锁、人脸识别开锁外,都会有密码开锁的功能, 以防万一。密码开锁功能是这样的,首先设定好密码,以后每次开锁的时候只要输入的数字中含有设定的密码,就视为解锁成功。这样的设定是为了防止别人的窥探,具有更高的安全性。

首先,由于涉及到了输入,先来学习一下Python内置的输入函数: input

password = input("请设置您的密码")


在执行到这行代码时,控制台就变成光标闪烁的状态,用户可以用键盘进行字符的输入,输入完成后, 再输入一个回车表示输入结束,输入的字符串赋给等号左边的变量。

# 设置初始密码
password = "123"

while True:
	pwd = input("请输入您想要设置的密码:")
   	# 如果没有输入任何密码,则使用初始密码作为密码
   	if not pwd:
       	break
   	confirm_password = input("请再次输入您的密码:")
   	if pwd == confirm_password:
       	password = pwd
       	break
   	else:
       	print("您两次输入的密码不一致,请重新输入。")

print("您的初始密码已设置为:" + password)
print("进入开锁程序。。。")

# 开锁
while True:
   	input_pwd = input("请输入您的密码:")
   	# 判断输入的密码中是否包含密码
   	if password in input_pwd:
       	print("开锁成功!")
       	break
   	else:
       	print("您输入的密码有误,请重新输入")


注意 password in input_pwd 这行代码,关键字in表示如果在指定的序列中找到值返回 True,否则返回 False。

第三部分 数据结构

第一节 字符串

在任何一门编程语言中,字符串都是最基础、最重要的数据结构。前面我们已经学习过字符串的基本使用方法,现在继续深入的学习更多的知识。

3.1.1 字符串的格式化输出
3.1.1.1 格式化运算符

在之前while循环的一个例子中, 有这样一行代码:

print("我跑完了第" + str(lap + 1) + "圈")


这里我们使用了两个加号做了字符串拼接,并且将整形转换成了字符串,现在介绍一种更好的办法,使用格式化输出来打印这句话。

print("我跑完了第%d圈" % 1)


这里的百分号就是格式化符号,跟模运算符一样,但在不同的地方用法不一样。%d是一种占位,表示 要显示在这里是一个整数,常用的占位符有以下几种:

占位符	描述
%d		整数占位符
%f		浮点数占位符
%.f		指定精度的浮点数占位符
%s		字符串占位符
%%		输出百分号%


如果给%d 传入一个浮点数,那它会自动将它转换成整数

print("%d" % 3.14)  # 输出3
print("%d" % 3.99)  # 输出3


转换成整数的规则和类型转换的规则一 样,不会四舍五入。

%f的用法有两种,一种就是直接使用,比如

print("%f" % 3.14)


它会输出“3.140000”,后面的0是自动补齐的,如果我们只想要输出小数点后两位,可以这样写: print(“%.2f” % 3.14)

print("%.2f" % 3.1415926)


上面的两行代码输出都是"3.14",“%.2f” 这种写法指定了小数点后面的位数,即使浮点数后面的小数部分超出了2位,也只会输出两位。如果不足两位,或者是个整数,会自动补零到两位。

print("%.2f" % 3)  # 3.00
print("%.2f" % 3.1) # 3.10
print("%s" % 100) print("%s" % 3.14)  # 输出100 # 输出3.14
print("%s" % "python")  # 输出python


%s 是胜任最广泛的占位符,它可以对应任何类型的变量。
在同一个字符串可以同时使用多个占位符:

report = "%d年%s公司营收增长了百分之%.2f" % (2019, "腾讯", 20.28) 
print(report)


当我们需要在字符串输出一个百分比,就需要用到%%,比如说:

report = "%d年%s公司营收增长了%.2f%%" % (2019, "腾讯", 20.28)
print(report)


3.1.1.2 format 函数

除了%运算符外,Python还为我们提供了字符串的format函数提供丰富的格式化。比如说,在输出一个较长的数字时,根据国际惯例,每三位用逗号分隔:

print('{:,}'.format(1234567890))  # 1,234,567,890


format函数也可以像%那样来格式化多个参数:

report = "{0}年{1}公司营收增长了{2}%".format(2019, "腾讯", 20.28)
print(report)


{0}表示第一个参数,{1}{2}表示第二、第三个参数,以此类推。这样做的好处是,如果有参数在字符串出现多次,可以不用重复的传入。

‘{0}的GDP为{1:,}…虽然它的GDP只有{1:,}美元,但它的人均GDP高达18万美元’.format(“摩纳哥”, 7100000000)

假设这个GDP数据在报告中多处被引用,万一需要修订的话,我们只需要修改一处就行了。

3.1.2 字符串的下标和切片

字符串其实也是一种序列,可以理解为一串整齐的按顺序排着队的字符,组成了字符串,那每个字符在队伍中都有自己的位置,这个位置就是下标,又叫作索引。

CHINA
12345

如上表,"CHINA"这个字符串,从左往右每一个字符对应了一个下标(索引),需要特别注意的是,在计算机编程中,所有的下标都是从0开始的,当我们要访问一个字符串的第1个字符时,使用的下标应该是0。

"CHINA"[0]


使用中括号加数字的方式,表示要访问的是具体哪个位置上的字符。

"CHINA"[1]  # 第2个字符"H"
"CHINA"[4]   # 第5个字符"A"


第5个字符"A"是这个字符串的最后一个,我们也可以这样来访问:

"CHINA"[-1]  # 最后一个字符"A" 
"CHINA"[-2]  # 倒数第二个字符"N"


使用负数下标可以从右往左访问,这种写法是Python特有的,非常的快捷,对于任意长度的字符串,我们都可以使用-1来获取它的最后一个字符,注意使用负数下标是是从-1开始的,因为-0也是0,产生重复了。

切片操作也是Python的一大特色,极大简化了序列访问和操作,

"CHINA"[0:3]


上面的切片将会得到"CHI",切片操作是以冒号隔开两个下标,冒号左边的代表开始下标,右边的代表结束下标,特别需要注意的是,结尾下标表示截取到该下标前一个位置的下标。那[0:3],一共截取了3-0=3个字符,从0开始数,0、1、2,恰好是三个。

所以如果我们用[0:0]这种写法,将会得到一个空的字符串,

"CHINA"[0:0] "CHINA"[0:-1]  # 空字符串 # CHIN


那如果想得到切片出整个字符串,可以这样写

"CHINA"[0:6]  # CHINA


但一般我们都会这样写,冒号右边的省略,表示一直截取到最后一个字符。

"CHINA"[0:] # CHINA


事实上,前面的0也可以不写,冒号左边为空表示从第一个字符开始截取。

# 从0到末尾   
"CHINA"[:]  # CHINA
# 从0到第3个
"CHINA"[:3] # CHI  
# 从第3个到末尾
"CHINA"[2:] # INA


如果想要隔一个字符取一个,可以这样写 # 每隔两个字符截取一个

"CHINA"[::2]  # CIA


第二个冒号表示截取步长,这里的2表示每两个字符取一个,如果不传,默认就是每一个都取。步长也可以为负数,如果传递了一个负数,则表示是从右往左进行截取。

# 从右往左每隔两个两个字符截取一个
"CHINA"[::-2]    # AIC


所以,如果想倒序输出一个字符串,可以这样写

"CHINA"[::-1]  # ANIHC


3.1.3 字符串函数

字符串本身有很多函数,前面其实已经学习过一个format函数,我们再来介绍几个其他的常用函数: 3.1.3.1 去除空白字符函数

先来了解一下什么是空白字符,空白符包括空格、换行(\n)、制表符(\t)。

print("A\tB\tC\nD\tE\tF")


在控制台里会整齐的输出成这样:

A B C
D E F


在C后面有一个\n换行符,所以D才会显示在了第二行。而ABC、DEF之间的空白则是\t制表符造成的。 按键盘上的空格键会产生一个空格,按回车键则会产生一个换行符,按Tab键则会产生一个制表符,用户在输入数据的时候有时候经常会误输入这几个字符,所以在在处理用户输入的数据时,要先去除头尾 的空白字符。

password = "123"
input_password = " 123"
print(password == input_password)


print输出为False,由于在1前面有一个空格,导致了密码校验失败,所以必须对用户的输入先进行处理 password = “123”

input_password = " 123"
print(password == input_password.strip())


我们在input_password后面加了一个函数strip(),现在输出变成了True。strip函数的作用就是去除字符串首尾的所有空白字符。

" abc ".strip()
"\t abc \n".strip()


得到的将是字符串"abc",但是strip函数并不会去除掉字符串之间的空白字符

" a   b   c ".strip()


得到的结果是"a b c",只去掉了首尾空白字符,保留了中间的。另外还有lstrip和rstrip函数,分别去除字符串左边和右边的空白字符。

3.1.3.2 大小写操作

这个比较简单,我们直接看代码。

# 将所有字符变成大写 
"china".upper()  
# CHINA

# 将字符串的首字母变成大写 
"china".capitalize() 
# China

# 将所有字符变成小写
"CHINA".lower()
# china

# 将每个单词的首字母变成大写 
"i have a dream".title()
# I Have A Dream


3.1.3.3 字符串判断

判断字符串是否以指定的字符串开头或者结尾

函数			说明
startswith	是否以指定的字符串开头
endswith	是否以指定的字符串结尾
isdigit		是否是一串数字
islower		是否全是小写字母
isupper		是否全是大写字母


3.1.3.4 查找与替换

在前面智能密码锁的案例中,我们用过in来判断一个字符串是否被包含在另一个字符中 password = ‘123’

input_pwd = '456123789' 

print(password in input_pwd)  # True


这样可以判断出是input_pwd中是否有password,但如果想要知道password在input_pwd中的确切位置,就需要使用find函数

input_pwd.find(password)  # 结果是3


结果是3。在input_pwd中寻找password,找到了,且它的出现的位置是3,也就是第4个字符。如果没有找到则会返回-1

input_pwd.find("notexists")   # 结果是-1


除了find函数,index函数也有相同的功能,唯一的区别是 ,index函数如果没有找到相应的字符串就会报错

input_pwd.index(password)  # 结果是3
# 这行代码将会在运行时报错 input_pwd.index("notexists")


count函数能够查找出指定的字符串一共出现了几次,如果没有出现,则返回0。

"abba".count('a')  # 2
 'abba'.count('c')   # 0


replace函数提供了替换字符串中某些部分的功能

"abba".replace('a', 'b')  # 结果是'bbbb'


'apple banana'.replace('apple', 'orange')  # 结果是'orange banana'


3.1.3.5 字符串长度

字符串本身没有测量长度的函数,需要借助一个Python内置函数len。

len("China")  # 5
len("")  # 0
len("a")  # 1


len函数非常重要,它不光可以测量字符串的长度,也可以测量其他所有有长度的对象。 r = range(10)

len(r)  # 10


3.1.4 综合案例:异常号码数据分析

结合以上的字符操作知识,可以开发一个电话号码识别程序,用户输入一串数字,程序识别它是不是一 个有效的电话号码,如果是有效电话号码,我们再识别它是一个固定电话号码、手机号码、还是400号 码。用户输入"exit"后,程序退出。

这是一个稍微复杂的需求,在动手写代码之前,我们先分析一下需求。先列举一下常见的几种电话号码 形式,手机号码是11位的,以1开头,不同运营商的前三位不一样,由于三位太多了,我们就以前两位来判断,包括13,15,17,18,19

再看固定号码,区号+电话号码的方式,区号可能是三位(010),也可能是四位(0888),电话号码是8位,那加起来一共是11位或12位。

最是400电话,这个特征很明显,以400开头,共10位。

实现代码如下:

cellphone_number_start = "13,15,17,18,19"
telephone_number_start = "010,021,022,025,0888,0555"

while True:
	num = input("请输入一个电话号码: \n")
	if num == 'exit':
		break
	if not num:
       	print("电话号码不能为空")
   	num = num.strip()
   	if not num.isdigit():
       	print("您输入的是一个无效电话号码")
       	continue

   	if num.startswith('1') and len(num) == 11 and num[0:2] in cellphone_number_start:
       	print("这是一个手机号码")
       	continue
   	elif num.startswith('400') and len(num) == 10:
       	print("这是一个广告电话")
       	continue
   	elif num.startswith("0"):   # 当代码太长时,可以用反斜杠分割成多行。
       	if (len(num) == 12 and num[0:4] in telephone_number_start) or \
(len(num) == 11 and num[0:3] in telephone_number_start):
           	print("这是一个固定电话")
           	continue
           
   	print("无法识别该号码")


第二节 元组 tuple
3.2.1 定义元组

现在我们知道了字符串是一种序列,它可以迭代循环,也可以按索引访问,也可以切片访问。但它的成员只能是单个的字符,现在来介绍一种更多元化的序列:元组,英文叫tuple,可这样来定义一个元组:

t = ('My', 'age', 'is', 18)


在这个元组中包含了3个字符串,一个整形数字,元组中的每一项称作元素,4个元素按照从左到右的顺序排列。可以用下标索引访问:

t[0]  # 'my'
t[-1]  # 18


也可以通过切片来访问,注意切片返回的是一个包含切片片段的新元组。

t[0:2]  # ('My', 'age')


事实上元组定义的时候也可以不用括号

t = 'My', 'age', 'is', 18


但当,元组中只有一个元素的时候,必须要加一个逗号:

t = ('solo',)

# 或者不带括号
t = 'solo',


可以将一个序列强制转换为元组

tuple('abc')  # ('a', 'b', 'c')

tuple(range(5)) # (0, 1, 2, 3, 4)


后面的逗号表明这是一个元组,否则就会被认为是一个字符串。

3.2.2 元组操作

现在我们介绍字符串的另一个函数join,有了它,可以把元组这样的序列拼接成一个整体的字符串。

# 注意最后一个元素
t = ('My', 'age', 'is', "18")
print(" ".join(t))  # 输出结果:'My age is 18'


注意最后一个元素,这次我们将它设置成了字符串,因为join函数要求参数序列中的每一个元素都必须是字符串。

和字符串一样,元组也有count, index函数,使用的方法也是一样:

t = ('a', 'b', 'b', 'a')

t.count('a')   # 2
t.index('b')   # 1
t.index('c')   # Error

# 查看长度 
len(t)  # 4


元组也支持 in 操作,想要判断元组中是否包含某个元素:

'a' in t  # True

'x' in t  # False


最后,需要记住的是元组和字符串都是只读的,也就是不可修改的。我们不能单独改变元组中的某个元素,或者是字符串中的某个字符。

3.2.3 遍历元组

元组属于序列,所以可以像字符串那样去遍历它:

lst = ('a', 'b', 'c', 'd', 'e')

for i in lst:
	print(i)


使用for循环可以方便快捷的遍历元组,上面的例子将打印出元组中的每一个元素。也可以使用while来遍历元组,虽然并不经常这样使用。

lst = list(range(10))
i = 0

while i < 10:
	print(lst[i])
 	i += 1


3.2.4 综合案例:销售数据统计-销冠

在真实的项目中,数据结构通常是比较复杂,经常碰到嵌套的元组,甚至是多层嵌套,我们来看一个例 子:

# 当元组元素较多、较长时,可以这样书写 
sales = (("Peter", (78, 70, 65)), ("John", (88, 80, 85)), ("Tony", (90, 99, 95)), ("Henry", (80, 70, 55)), ("Mike", (95, 90, 95)))


这是包含某公司所有销售人员第一季度销售业绩的元组,单位是万元,其中的每一个元素对应一个销售人员的信息,人员信息也是一个元组,包括姓名和业绩,业绩又是一个元组,按照顺序分别是1、2、3 月份的销售额。需求:找出总销售额最高的那个员工,并将TA的名字和总销售额输出。

champion = ''
max_amount = 0

for sale in sales:
   name = sale[0]
   quarter_amount = sale[1]
   total_amount = 0
   for month_amount in quarter_amount:
       total_amount += month_amount
       
   if total_amount > max_amount:
       max_amount = total_amount
       champion = name

print("第一季度的销冠是%s, TA的总销售额是%d万元" % (champion, max_amount))


上面的代码也可进一步优化一下,使得代码行数更少,结构更简单。

champion = ''
max_amount = 0

for name, quarter_amount in sales:
   total_amount = sum(quarter_amount)

   if total_amount > max_amount:
       champion, max_amount = name, total_amount

print("第一季度的销冠是%s, TA的总销售额是%d万元" % (champion, max_amount))


这里用到了一个sum函数,它是Python内置函数,可以计算出一个序列里所有数值的总和。

第三节 列表 list
3.3.1 定义列表

列表可以理解为可变的元组,它的使用方式跟元组差不多,区别就是列表可以动态的增加、修改、删除元素。

看一下列表的定义:

# 定义一个空列表
lst = []
lst = list()

# 定义带有初始值的列表
lst = [1, 2, 3]
lst = ["a", 1, 2, 3, "b", "c"]
lst = list(range(5))
lst = list("abc")
lst = list((1, 2, 3))


以上方式都可以定义一个列表。注意变量名使用了lst,有意的避开了list,虽然list不是关键字,但我们在命名变量的时候不要使用这些内置名称,否则可能会引起无法预知的错误。

3.3.2 增删改查

列表的访问和字符串、元组一样,索引或者下标都可以。

lst = ['a', 'b', 'c', 'd', 'e']
lst[0]  # 'a'
lst[1:3]  # ['b', 'c']


列表是可以修改的, 还是以上面的lst为例:

lst.append('x')


往lst里添加了一个元素,现在列表变成了

['a', 'b', 'c', 'd', 'e', 'x']


注意append函数总是在列表后面添加元素,列表的长度也增加了1.因此原来list[-1]的值从原来的’e’变成 了’x’,

len(lst)   # 6
lst[-1]  # 'x'


修改列表中的元素

lst[-1] = 'f'

# 修改后列表变为:
# ['a', 'b', 'c', 'd', 'e', 'f']


删除列表中的元素

del lst[0]
# 删除后列表变为:
# ['b', 'c', 'd', 'e', 'f']


注意,由于我们删除的是第一个元素,现在剩下的所有元素的索引都发生了变化,第一个lst[0]变成了’b’,后面的也都往前挪了一位。但是lst[-1]没有变,还是’f’。涉及到删除操作的时候要小心,防止使 用错误的索引。

3.3.3 列表函数

列表也是一种序列,它也具有index和count函数和支持len函数,这些函数的用法和元组一样,它的循环遍历也和元组一样,不再赘述。下面来介绍一下列表特有的一些函数。

insert

insert函数和刚刚介绍的append函数一样,用来向列表中添加一个新的元素,区别就是append是在最后添加,insert则可以向任意位置添加。

lst = ['a', 'c', 'e']

# 在第二个元素'c'前面插入一个字符串'b'
lst.insert(1, 'b')

# lst现在的值是['a', 'b', 'c', 'e']

# 在最后一个元素'e'前面插入一个字符串'd'
lst.insert(-1, 'd')

# lst现在的值是['a', 'b', 'c', 'd', 'e']


pop

每次调用pop函数会从列表中“弹”出一个元素,接着上面的lst操作

temp = lst.pop()

print(lst)  # ['a', 'b', 'c', 'd']
print(temp)  # 'e'


我们发现列表最后一个元素’e’不见了,并被在控制台打印出了。如果想“弹”出其他位置的元素,可以传 一个位置参数给pop函数,像这样:

temp = lst.pop(2)

print(lst)  # ['a', 'b', 'd']
print(temp)  # 'c'


remove

前面我们已经学习了使用del关键字去删除列表元素,del操作可删除指定下标索引的元素,如果我们要删除指定内容的元素,就需要用到remove函数。

lst = [1, 2, 1, 'a', 'b', 'c']

lst.remove('a')
print(lst)  # lst的值为[1, 2, 1, 'b', 'c']

lst.remove(1)  # 注意这里的1是元素值,不是索引
print(lst)  # lst的值为[2, 1, 'b', 'c']


remove函数会从左至右找到与指定的值相匹配的第一个元素,并将它删除。在使用的时候需要区分del, pop, remove的区别。

clear

clear函数会清空列表内的所有元素。

lst = [1,2,3,4]

lst.clear()
print(lst)  # 结果为[]


extend

extend函数有点像append函数,但append函数每次只能添加一个元素,而extend可以添加一组。 lst = []

lst.extend(range(5))
print(lst)  # [0, 1, 2, 3, 4]

lst.extend([5, 6, 7])  
print(lst)  # [0, 1, 2, 3, 4, 5, 6, 7]


reverse

将整个列表反转,以上一步的lst为例

lst.reverse()
print(lst)  # [7, 6, 5, 4, 3, 2, 1, 0]


sort

按照一定的规则将列表中的元素重新排序,对于数值,默认按从小到大的顺序排列。 lst = [3, 5, 2, 1, 4]

lst.sort()

print(lst)  # [1, 2, 3, 4, 5]


如果想要让列表从大到小排列,可以加上reverse参数。 lst = [3, 5, 2, 1, 4]

lst = [3, 5, 2, 1, 4]
lst.sort(reverse=True)

print(lst)  # [5, 4, 3, 2, 1]


对于字符串,则会按照它们的ASCII值的顺序排列。ASCII是基于拉丁字母的一套电脑编码系统,所有的编程语言都支持ASCII编码。ASCII值一共有128个字符,包含数字0~9,字母a-z, A-Z,还有一些常用的符号。每一个字符对应一个数字,比如字母’A’对应的就是65, 字母’B’对应66,等等。在Python中,可以使用内置函数将字符与它的ASSCII值互转。

ord('A')  # 65

chr(66)  # 'B'


sort函数会比对每个字符串的第一个字符,如果第一个字符相同,则比对第二个字符,以此类推。

fruits = ['apple', 'banana', 'orange', 'blueberry']

fruits.sort()
print(fruits)  # ['apple', 'banana', 'blueberry', 'orange']


注意观察"banana"和"blueberry"的顺序。

如果列表的元素不是简单的字符或者数字,那怎么进行排序呢,比如有下面一个列表,它存储了公司第一季度每个月的收入。

revenue = [('1月', 5610000), ('2月', 4850000), ('3月', 6220000)]


注意列表中的每一个元素是一个元组,元组的第一项是月份,第二项是销售额,现在想要按照它的销售额来从高到低排序。如果直接调用sort函数,它会按照元组中第一项的字符串顺序进行排序。

revenue.sort(reverse=True)  # 排序后为 [('3月', 6220000), ('2月', 4850000), ('1月', 5610000)]


这显然不对,2月的收入比1月低,应该排到最后。这时应该传递key参数

revenue.sort(reverse=True, key=lambda x:x[1])  # 排序后为 [('3月', 6220000), ('1月', 5610000), ('2月', 4850000)]


key参数接收的是一个函数,我们在这里给它传递了一个匿名函数,关于函数的使用后面再学习,这里我们需要了解是通过key参数,我们指定了sort排序的依据,就是每个元组里面的第二项。

copy
lst1 = [1, 2, 3]
lst2 = lst1
lst1.append(4)


上面的代码执行完成以后,lst 和 lst2的值都变成了 [1, 2, 3, 4] ,但我们在代码里面只修改了lst1, lst2的值也跟着改变了,这不符合我的预期,可能会导致bug。所以,如果我们想要创建一个跟lst1一模一样的新列表,且不再受它以后操作的影响,就可以使用copy函数:

lst1 = [1, 2, 3]
lst2 = lst1.copy()

lst1.append(4)

print(lst1)  # [1, 2, 3, 4]
print(lst2)  # [1, 2, 3]


3.3.4 列表表达式

列表表达式是一种快捷的创建列表的表达式,可以将多行代码省略为一行。比如,列出20以内的所有偶数

[i * 2 for i in range(10)]  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]


当然,上面的代码我们也可以这样实现

[i for i in range(0, 20, 2)]


range函数可以接收三个参数:第一个是起始数值(包含),可以省略,默认从0开始;第二个是结束数值(不包含);第三个是步长,可以省略,默认为1。是不是有点像切片操作?

上面的代码就相当于:

even_nums = []

for i in range(0, 20, 2):
	even_nums.append(i)


对比来看,列表表达式确实更简洁更优雅,再看一个例子,打印出大写的26个字母。

# 65是大写字母‘A’的ASCII值
print([chr(i) for i in range(65, 65 + 26)])


3.3.5 综合案例-销售数据统计-排行榜

再进一步,还记得前面写过的找出销售冠军的例子吗?

sales = (("Peter", (78, 70, 65)), ("John", (88, 80, 85)), ("Tony", (90, 99, 95)), ("Henry", (80, 70, 55)), ("Mike", (95, 90, 95)))


现在我们将计算出每人的总销售额,并按销售额从多到少,存放到一个列表里。

top_sales = []

for name, quarter_amount in sales:
   total_amount = sum(quarter_amount)
   top_sales.append((name, total_amount))

top_sales.sort(key=lambda x:x[1], reverse=True)
print(top_sales)


得到了一个下面的列表

[('Peter', 213), ('John', 253), ('Tony', 284), ('Henry', 205), ('Mike', 280)]


提示:这个案例用列表表达式来开发,将会非常快速。

top_sales = [(sale, sum(amount))for sale, amount in sales]
top_sales.sort(key=lambda x:x[1], reverse=True)

print(top_sales)


列表表达式是一种非常强大和方便的写法,不要求大家掌握,如果不会写列表表达式,也可以用for循环或while循环的方式来写,但至少要能看懂。

第四节 字典 dict

类似这种销售信息的数据结构,我们使用元组或者列表存储是可以的。

top_sales = [('Peter', 213), ('John', 253), ('Tony', 284), ('Henry', 205), ('Mike', 280)]


可以很方便的取出在这个榜单中第一名、第二名或者任意一名的销售数据。但它有一个缺点,如果我们想取出特定的某个销售人员的数据时,它会很麻烦。比如如果想要找出Mike的数据,只能去循环遍历,一个个的去比对。

for sale, amount in top_sales:
   if sale == 'Mike':
       print(sale, amount)


这样不光写起来麻烦,执行效率也很低,假设这是一个庞大的销售数据库,那要花多少时间寻找呢?所以必须使用字典。

3.4.1 字典的定义

使用花括号,可以直接定义字典

sales = {
 'Peter': 213,
 'John': 253,
 'Tony': 284,
 'Henry': 205,
 'Mike': 280
}


每一行冒号左边的是键(key),右边的是值(value),称作键值对,以逗号分隔开。在这里我们故意写成每行一个键值对,实际并不要求每行一个,只要用逗号分隔开来就可以。键是不能重复的,值可以重复。

对于top_sales 这种两个一组、一一对应的列表来说,可以直接转换为字典。

sales = dict(top_sales)  # sales现在的值变成了


{
 'Peter': 213,
 'John': 253,
 'Tony': 284,
 'Henry': 205,
 'Mike': 280
}


3.4.2 增删改查

有了字典,那现在就可以快速的取出任意一个人的数据了

sales['Mike']

sales['Henry']


注意,键的名称是区分大小写的,‘Mike’ 和 'mike’分别对应了不同的键。

sales['mike']  # KeyError: 'mike'


这行代码将会报错,因为不存在’mike’这个键。在访问之前,需要先往字典中添加数据相应的键:

# 值可以先设置为None或者0
sales['mike'] = 0


修改字典中的数据:

sales['mike'] = 300


删除字典中的数据

del sales['mike']


3.4.3 遍历字典

字典的遍历和列表、元组、字符串有所区别,由于它的每一项都是一个键值对,所以在遍历时也要注意。

for key_value in sales.items():
   	print(key_value)


注意 sales.items() 这种写法,在遍历字典时,这是一种通用的写法。items函数将字典内的内容转换成了一种可迭代的序列,以供for循环遍历,遍历出来的每一项是一个元组,包含了键和值。所以通常 我们直接写成这样:

for key, value in sales.items():
   	print(key, value)


注意中间的逗号和最后的打印结果。

如果不使用items函数,那遍历的就是字典所有的key。

for key in sales:
   	print(key)


3.4.4 字典函数

字典有一些函数和列表是一样的,比如clear, copy,这两个不再介绍,我们来看看其他的函数。

get

如果直接访问字典中一个不存在的key,就会产生报错,所以,通常我们如果不确定是否存在某个key 时,会先判断一下:

if 'mike' in sales:
	print(sales['mike'])

else:
 	print('mike', 0)


in关键字又一次派上用场了, 在这里用in来检查字典中是否包含"mike"这个键,如果包含则返回True, 无论"mike"所对应的值是否为空。这样的写法,很麻烦,每次都要先判断,再查询,这时候get函数就派上用场了。

sales.get('mike', 0)


这一行短短的代码就实现了上面4行代码的功能,它表示在字典中查询’mike’键所对应的值,如果不存在’mike’键,则返回0。这里的0可以替换成任何其他的默认值,这样就极大简化代码逻辑。

keys/values

如果只想单独列出所有的销售人员名单,可以这样写:

sales.keys()


可以对它进行遍历

for sale in sales.keys():
 print(sale)


如果只想计算出所有销售人员的销售额总和,也就是公司总销售额,可以直接取出value部分并求和: sum(sales.values())

第五节 集合 set

集合在Python中是一个无序的不重复的序列,一般用来删除重复数据,还可以计算交集、并集等。

3.5.1 集合的定义

这两方式都可以定义一个集合

nums = {1, 2, 3, 4, 5}

nums = set([1, 2, 3, 4, 5])


注意,集合是无序的,虽然我们在书写的时候是按照从小到大的顺序,有时候遍历出来也是有序的,但不能把它视为有序,并作为某些逻辑的依据。

集合最常用的用法是用来消除列表或者元组中的重复元素

lst = [1, 2, 1, 3, 4, 5]

list(set(lst))


列表里里面有两个1,先将lst转成了集合,再将集合转成了列表,最终得到了一个没有重复元素的列表 [1, 2, 3, 4, 5] ,注意最后得到的列表的顺序有可能跟原来是不一样的。

3.5.2 遍历集合

集合的遍历和列表、元组很相像,再次重申,它不是有序的。

for n in nums:
 	print(n)


也可以通过len函数来测量它的长度,准备地讲,在数学上叫集合的基数。

len(nums)  # 5


可以通过 in 来判断集合中是否有某个特定的元素

5 in nums  # True


3.5.3 增删改查

往集合里添加一个元素

nums.add(5) # do nothing

nums.add(6)


如果集合里已经有这个元素了,则什么也不做。像上面的第一行代码,什么也没有做。 已经加入集合的元素不能修改,只能删除,删除集合里的元素:

nums.remove(5)
nums.remove(5)  # Error


remove函数会从集合里删除指定元素,但如果元素不存在,则会报错,上面的第二行代码就会报错。 如果不想报错,可以使用diiscard函数。

nums.discard(5)


从集合内删除并返回一个元素:

num = nums.pop()


如果集合是空的,则会报错。有时候,我们也会使用pop函数来迭代一个集合。

while len(nums) > 0:
	print(nums.pop())


这样的好处是可以保证每个元素只被使用一次,不会重复使用。

3.5.4 集合函数
# 定义两个集合
s1 = {1, 2, 3}
s2 = {3, 4, 5}

# 求交集
s1.intersection(s2)  # {3}

# 求并集
s3 = s1.union(s2)   # {1, 2, 3, 4, 5}
print(s3)

# 是否是子集
s1.issubset(s3)   # True

# 是否是父集
s3.issuperset(s2)  # True


第四部分 Python进阶

第一节 函数

在前面的学习过程中,我们已经接触了很多次函数,现在我们来好好的认识一下这个重要的小伙伴。函数就是一段可以重复调用的代码,在Python中函数是非常重要的概念,在编程中几乎无处不在。

4.1.1 函数定义及调用

在Python在, 我们使用def关键字来定义函数

def hello(name):
	print("Hello", name)


上面的代码定义了一个最简单的函数,它的作用就是打印出"Hello"加一个名字。我们看看它的结构:

调用hello函数,将会打印“Hello, Python”

hello("Python")


在这个过程中发生什么什么事呢?这实际上是将name传递给hello函数,也就是将name的值设置为"Python",并执行函数体内的代码。

4.1.2 函数的参数

函数的参数可以有一个,也可以有多个,也可以没有参数。这取决于在定义函数的时候如何定义参数部分。刚才我们定义的函数只有一个参数,现在我们定义两个参数的函数。

def hello(name, sex):
	if sex == '男':
		print("Hello, Mr", name)

   	elif sex == '女':
   		print("Hello, Miss", name)


这个函数有两个参数,name和sex,分别表示用户的名字和性别,以便显示不同的尊称。我们在调用的时候,要注意参数的顺序,不能前后颠倒。调用函数:

hello("Zhang", "男")

hello("Wang", "女")


如果参数较多,记不清它们的顺序,可以写上参数名,这样就不用管顺序了。

hello(sex='男', name='Zhang')


如果说用户大多数是女的,只有少部分的男性,那这个函数还可以改造一下,我们让它的sex参数默认就是“女”:

def hello(name, sex='女'):
	if sex == '男':
		print("Hello, Mr", name)
	elif sex == '女':
		print("Hello, Miss", name)


和上面的函数相比,只是在sex参数后面加上一个默认值,这样,后面用户没有填性别信息的时候,就会默认为女性。

hello("Wang")


如果每一个参数都有默认值,在调用的时候甚至可以不传参数。

def hello(name='Anonym', sex='女'):
 	if sex == '男':
 		print("Hello, Mr", name)
	elif sex == '女':
		print("Hello, Miss", name)

hello()  # Hello, Miss Anonym


现在这个函数对于信息不完整的数据也有处理能力了。

4.1.3 函数的返回值

如果将函数比做一名员工,调用函数的我们就是老板,老板给员工一些具体的指示,员工则按规定好的流程去做事。有一些事情,比如老板说去打扫一下厕所,员工就默默地去干活了,干完了就完事了。如果是一些需要回复的事情,比如老板让员工去算一下去年公司的收入,那老板的意思肯定是要知道最后算出来的那个数字。对于函数也是一样,有一些函数我们是需要知道执行的结果。

def multiply(num1, num2):
	return num1 * num2

n = multiply(2, 4)
print(n)  # 8


multiply函数的功能是计算两个数相乘,我们传入两数字作为参数,希望能得到一个这两个数相乘的结 果。multiply的最后一行使用 return 关键字将结果返回。通常在函数的最后一行返回结果,但有时候 也有多种可能。

def permit(age):
   	if age >= 18:
       	print("准许进入")
       	return True
   	else:
       	print("禁止进入")
       	return False

   	print("end")


上面我们定义了一个只允许成年人进入的函数,如果年龄大于等于18岁,返回True,表示允许通过;如果小于18岁则不允许。虽然有两个return语句,但只返回一个结果,要么返回True要么返回False。注 意在return语句执行完后,函数就会结束执行,在它之后的任何语句都不会再执行,所以上例中 的“end”无论怎么样都不会被打印。

如果一个函数内没有任何return语句,那它的返回值就是None。

def do_nothing():
   pass

print(do_nothing())  # None


4.1.4 匿名函数

有时候我们临时需要一个函数,且它的逻辑比较简单,这时候就可以定义匿名函数。

lambda n: n * n


这就定义了一个匿名函数,这个匿名函数接收一个数值参数,计算出这个数的平方值并返回,就相当于下面的函数。

def square(n):
	return n * n


lambda是Python中的关键字,它的作用就是用来定义匿名函数,匿名函数的函数体一般只有一行代 码,省略了函数名和返回值语句。

这样的函数它的作用是什么呢?什么时候需要使用匿名函数而不是函数呢?回忆一下,我们在学习列表排序的时候用过一个匿名函数,现在是时候重温一下了。

revenue = [('1月', 5610000), ('2月', 4850000), ('3月', 6220000)]

revenue.sort(reverse=True, key=lambda x:x[1])


列表的sort函数key参数,它只能接收一个函数类型的值。单独看一下这段匿名函数:

key = lambda x: x[1]


现在key就是一个函数了,我们来调用一下它,x[1]这种索引操作说明x肯定是个序列,且长度至少为2。

key([1, 2])   # 2
key("abcd")  # 'b'


key函数的作用就是返回序列中的第二个元素,在sort排序的时候就会以每个元素的第二个元素作为比对的依据。在这个例子中,第二个元素就是月收入,所以是不是达到了以月收入排序的目的了?

这样的一个简单的功能,我们用不着单独定义一个函数,即用即抛,就像一次性手套一样,这正是匿名函数的使用场景。

第二节 面向对象
4.2.1 面向对象基本概念

面向过程:根据业务逻辑从上到下写代码。

面向对象:将变量与函数、属性绑定到一起,分类进行封装,每个程序只要负责分配给自己的功能,这样能够更快速的开发程序,减少了重复代码。

我们在前面写的代码都是面向过程的,这对初学者比较容易接受,而且,对于比较简单的需求,用面向过程实现起来确实更简单。

那什么是对象呢 ?我们可以理解为实际存在的实物,比如一个用户、一台ATM机、一幢建筑,亦或者是软件业务流程中产生的虚拟概念,比如订单、购物车、用户账户。我们发现,不管是实体还是虚拟产物,它们都是有一些共同点,都是名词,有自己的行为,有自己的属性。比如说用户对象,用户都有相同的几个属性,名字、年龄、性别、注册时间、上一次登录的时间等等。但不同用户这个几属性的值却都不一样。下面我们来看几个例子。如果你想向另一个人描述一只狗,那你会说出这只狗的哪几个特点?

品种
颜色
体型大小


狗会有哪些行为呢?

吃东西
奔跑
吠叫


这些都是我们基于常识总结出来的狗的特点和行为,对象的属性就是可以精确描述事物的特点,对象的函数就是事物的行为。

4.2.2 类和实例

现在我们用代码来实现一下“狗”对象,先介绍Python里的一个重要概念:类,它也是面试对象编程的基础。

class Dog:
	pass


这样我们就定义了一个类,使用class关键字,加上一个类名,这样我们就定义了一个空的类。

类名一般使用名词,且使用驼峰式命名法。

类是创建对象的模板,对象是类的实例。类就像生产线,有了类,就可以生产出许许多多的相同对象。使用上面的Dog类来创建一个Dog对象:

dog = Dog()

print(type(dog))


这里dog就是Dog的实例,通过内置的type函数,可以查看任何对象的类。

print(type(1))

print(type('abc'))

print(type([]))


这些都是我们学习过的数据类型,我们看到它们分别是整数、字符串和列表。那dog的类型就是Dog。 如果我们不知道一个对象是不是某种类型,就可以用type判断。

print(type('abc') == str)  # True
print(type(dog) == Dog)  # True
print(type(1) == int)  # True


也可以使用内置函数isinstance来判断对象与类的关系

print(isinstance('abd', str))  # True
print(isinstance(1, int))  # True
print(isinstance(dog, Dog))  # True


4.2.3 对象的属性与方法

现在我们来完整的实现一下Dog类:

class Dog:
	def __init__(self):
		self.breed = None
		self.color = None
		self.size = None
	def eat(self):
		print("I like bones")
	def run(self):
		print("I'll catch you.")
	def bark(self):
		print('Wang! Wang!')


大家应该注意到,类的每一个方法的第一个参数是 self ,但在调用的时候却不需要传参数给它。它是类方法和普通函数的区别,这个self代表的是实例自身,意思是“我的”,现在我们来创建Dog的实例

dog = Dog()
dog.eat()
dog.run()
dog.bark()

print('一只%s型%s色的%s' % (dog.size, dog.color, dog.breed))


调用不同的方法能打印出不同的内容,体现了不同的行为。但是最后一句话打印出来的内容却是None,因为我们还没有设置相应的属性。

dog.breed = '哈士奇'
dog.color = '黑白'
dog.size = '大'

print('一只%s型%s色的%s' % (dog.size, dog.color, dog.breed))  # 一只大型黑白色的哈士奇


如果每个创建完每个对象之后还要一个个的设置属性,会很麻烦。我们可以使用 init 函数来接收初始化参数,这样就可以把属性的值作为参数在初始化对象的时候就传给它。大家应该也注意到了,init 函数看起来与众不同的样子,它是Python的类用来初始化对象的构造函数,它的名字是固定的,必须这样写,创建对象时会首先调用它。改完后构造函数后代码如下:

class Dog:
	def __init__(self, size, color, breed='土狗'):
		self.breed = breed
		self.color = color
		self.size = size
	def eat(self):
		print("I like bones")
	def run(self):
		print("I'll catch you.")
	def bark(self):
		print('Wang! Wang!')


现在再来创建对象:

dog = Dog('中', '黄')

print('一只%s型%s色的%s' % (dog.size, dog.color, dog.breed))


第三个参数breed因为给了它默认值,所以可以不用传。

我们在之前学习的字符串、列表的所有函数,实际是调用字符串对象、列表对象的方法。

对象自身的属性是直接可以方法里使用的,比如我们改造一下bark方法,让狗可以开口自我介绍class Dog:

	def __init__(self, size, color, breed='土狗'):
		self.breed = breed
		self.color = color
		self.size = size
	def eat(self):
		print("I like bones")
	def run(self):
		print("I'll catch you.")	
	def bark(self):
		print('我是一只%s型%s色的%s' % (self.size, self.color, self.breed))


这里在bark方法里使用的self和构造函数里的self一样,都是指向对象自身。

dog = Dog("小", "棕", "泰迪犬")

dog.bark()


4.2.3 类属性与方法

对象是从类创造的,对象的属性和方法虽然是在类中定义的,但它们的值是各个对象独有的,互相不能共享。而类也有属性和方法,且它们可以和类创建的所有对象共享。我们先来定义一个类

class Goods:
	def __init__(self):
		self.name = ''
		self.price = 0
		self.discount = 1


Goods类有三个对象属性,每个商品有自己的名称、价格、折扣。我可以随意的创建商品

g1 = Goods()

g2 = Goods()


但如何知道一共创建了多少个商品呢?我们可以给Goods类加一个计数器。

class Goods:
	count = 0
	def __init__(self):
		Goods.count += 1
		self.name = ''
		self.price = 0
		self.discount = 1


我们给Goods类加了一个属性count,每当调用 init 函数时将它加1,这样我们就可以知道一共创建了多少商品对象了。这个count就是类属性,它可以通过对象访问,也可以通过类访问。

g1 = Goods()
g1.count # 1

g2 = Goods() 
Goods.count # 2


即使没有定义对象,也可以直接访问count属性,这就是类属性,同样,类方法也不需要创建对象,通过类名就可以访问。我们改造一下Goods类,给它增加一个属性id,表示商品唯一的序列号,为了保证 id不重复,我们使用计数器,每创建一个商品给它加1。

class Goods:
	id_count = 0
	@classmethod

	def generate_id(cls):
		cls.id_count += 1
		return cls.id_count
	
	def __init__(self):
		# zfill函数表示用“0”将数字补足5位
		self.id = str(self.generate_id()).zfill(5)
		self.name = ''
		self.price = 0
		self.discount = 1


这里的 generate_id 方法就是一个类方法,它的上面一行有一个 @classmethod ,声明了它是类方 法,它的参数不再是self,而是cls,指向类本身,用来在类方法内部访问类成员属性和方法。这个方法 每次将id_count属性加1,并返回。

这种 @ 符号的写法叫做装饰器,装饰器是用来装饰函数的,不同的装饰器赋予函数不同的特殊功能。对于classmethod装饰器,大家只要知道它是用来定义类方法的就行了。

现在我们再来试试:

g1 = Goods()
g2 = Goods()

g1.id  # 00001
g2.id  # 00002

Goods.id_count  # 2


4.2.4 一切皆对象

在Python中一切都是对象,我们使用的数字、字符串、函数甚至类本身,都是对象。所有的对象都可以用type函数来判断它的类型,同时可以用dir函数来查看它的属性和方法。

dir(dog)


会显示出dog对象的所有属性和方法,包括刚刚定义的那几个方法和属性。对于对象,也可以使用help函数查看它的帮助文档。

help(sum)

help(print)


这些帮助信息可以在定义的时候写入到代码里:

def bark(self):
"""一只狗的自我介绍"""
 	print('我是一只%s型%s色的%s' % (self.size, self.color, self.breed))


加上这句文档后,我们就可以使用help函数查看bark方法的帮助信息了,这有助于其他人使用我们的方 法。

help(dog.bark)

help(Dog.bark)


一切皆对象是一句简单的话,但它的精神却很深邃,试试下面的代码,你还能看得懂吗?

lst = []
lst.append(Dog)
dog = lst[0]('中', '黄')
lst.append(dog)
lst[1].bark()
lst[1].sleep = lambda: print('Good night.')
lst.pop().sleep()


有时候两个对象的值完全相同,我们可以说这两个对象是相等的,但不能说它们是同一个对象。

l1 = [1, 2, 3]
l2 = [1, 2,]

l1 == l2  # False
l2.append(3) 

l1 == l2  # True

print(l1 is l2)  # False


最后一行操作,使用 is 关键字来判断这两个对象是否是同一个对象,结果是False。它表明l1和l2是不同的对象,这一点可以使用id函数看出来:

id(l1)

id(l2)


分别返回两串不同的数字,这个一长串的数字代表了对象所指向的内存空间地址。

4.2.5 综合案例-电商购物车商品统计分析

项目需求:可以设置每个商品的名称、价格、折扣率,用户将商品加入到购物车以后,能够立即显示所有商品、总价、折扣情况和实际需要支付的金额,也就是折扣后的金额。商品的名称、价格、折扣率都 可以随意修改,且修改完成后,购物车中的相关信息和金额也会发生改变。

需求分析:在这个需求里面,提到了两个虚拟产物,商品与购物车,也就是说需要定义两个类。

class Goods:
	"""商品类"""
	id_count = 0
	@classmethod
	def generate_id(cls):
		cls.id_count += 1
		return cls.id_count
	def __init__(self, name, price, discount=1):
		self.id = str(self.generate_id()).zfill(5)
		self.name = name
		self.price = price
		self.discount = discount

   	def calc_price(self):
   		"""计算商品打折后的实际价格"""
       	return self.price * self.discount


这是商品类,它有四个属性:ID、商品名称、价格、折扣,另外它还有一个函数,计算出商品打完折后的价格。接下来我们来创建几个商品对象:

g1 = Goods('iPhone 11', 6000, 0.9)
g2 = Goods('U盘32G', 100, 0.8)
g3 = Goods('华为P40', 5000)


这样我们就创建了三个商品对象,并设置好了它们的名称、价格、折扣。接下来我们来编写购物车类:

class Cart:
	"""购物车"""
   	def __init__(self):
   		self.cart = {}
   		self.goods_list = []

   	def add(self, goods, num=1):
   		"""向购物车中添加商品"""
		if goods in self.goods_list:
			self.cart[goods.id] = self.cart[goods.id] + num
		else:
			self.goods_list.append(goods)
			self.cart[goods.id] = num

   	def remove(self, goods, num):
   		"""从购物车减少或删除商品"""
   		if goods not in self.goods_list:
   			return
		
		self.cart[goods.id] -= num
		
		if self.cart[goods.id] <= 0:
			del self.cart[goods.id]
           	self.goods_list.remove(goods)

   	def get_goods_by_id(self, id):
   		"""根据商品名称查找商品"""
   		for g in self.goods_list:
   			if g.id == id:
				return g

   	def get_total_amount(self):
   		"""获取当前购物车中的总价"""
		amount = 0
		for name, num in self.cart.items():
			goods = self.get_goods_by_id(name)
			amount += goods.price * num
		return amount

   	def get_pay_amount(self):
   		"""获取实际需要支付的总价"""
   		amount = 0
   		for name, num in self.cart.items():
   			goods = self.get_goods_by_id(name)
   			amount += goods.calc_price() * num

       	return amount

   	def show(self):
   		"""显示当前购物车中所有商品的数量、价格,以及总价"""
   		title = ('商品', '单价', '数量', '价格(元)')

       	def show_row(row):
       		"""内部函数,显示购物车中的一行"""
       		for col in row:
       			print(str(col).ljust(12), end='\t')
           	print()
       	print("-" * 70)
       	show_row(title)

       	for id, num in self.cart.items():
           	goods = self.get_goods_by_id(id)
           	price = '%.2f' % goods.price

           	if goods.discount < 1:
           		price = '%.2f (%d折)' % (goods.price, goods.discount * 10)            
           	show_row((goods.name, price, num, '%.2f' % (goods.calc_price() * num)))

       	total_amount = self.get_total_amount()
		pay_amount = self.get_pay_amount()

       	discount_amount = total_amount - pay_amount
		show_row(('', '', '', '总金额: %.2f' % total_amount))

       	if discount_amount > 0:
       		show_row(('', '', '', '优惠: %.2f' % discount_amount))

       	show_row(('', '', '', '实付金额: %.2f' % pay_amount))


这是购物车类,看起来它的代码很长。共有两个属性,6个方法。其实这个类主要就是提供了三个功能:

  1. 增加商品 add
  2. 减少商品 remove
  3. 显示商品 show

其他的函数都是辅助实现这三个主要功能。cart是一个字典,用来保存商品和数量的对应关系,它的键名是商品ID(字符串);goods_list是一个列表,保存了购物车所有商品的详细信息(商品类实例), 注意它们的数据结构。

有了Goods和Cart,我们就可以随意的增加删除商品,并可以随时查看购物车里的情况。

cart = Cart()
cart.add(g1)
cart.add(g2, 3)
cart.show()


我们可以继续增加或者删除,并随时可以查看购物车的商品、计算总金额。如果商品的数量为零,则会从购物车中被删除

cart.remove(g2, 2)
cart.show()
cart.remove(g2, 1)
cart.show()


如果商品的名称、价格或者折扣发生了变化,我们只需要修改商品对象就可以了,其他的代码都不用修改,购物车中的信息会实时的跟随调整。

cart.add(g3)
cart.show()
g3.name = '华为P40 pro'
cart.show()


可以看到,在修改了g3对象的商品名称之后,再次显示购物车时发生了变化 ,而我们的Cart类不用修改任何代码,这样做到了不同实体之间的操作隔离,是不是有点感受到面向面向对象的便捷了呢?

4.2.6 自定义属性-property

Cart类中的这两个函数 get_total_amount 和 get_pay_amount ,每次调用它们的时候都是直接调用, 没有传递任何参数,最后返回一个值。对于这种方法,其实可以把它们变成property(属性):

@property
def total_amount(self):
       	"""获取当前购物车中的总价"""
       	amount = 0

       	for name, num in self.cart.items():
			goods = self.get_goods_by_id(name)
			amount += goods.price * num

       	return amount

   	@property
   	def pay_amount(self):
       	"""获取实际需要支付的总价"""
       	amount = 0
       	for name, num in self.cart.items():
       		goods = self.get_goods_by_id(name)
       		amount += goods.calc_price() * num

       	return amount


我们在函数名前面加了一个property装饰器,修改方法名,去掉了 get_ ,其实方法名也可以不改,在这里修改是为了代码的可读性更通顺。改造后,使用这两个方法时,就可以像使用普通属性一样:

cart.total_amount

cart.pay_amount


这样使用起来是不是更简洁优雅了呢?

另外还有关于继承的部分,我后面有时间会再补充,这部分稍微难一点点,大家先不急着学。

第三节 模块和包管理

Python中具有丰富的标准库和第三方库,学习并掌握模块、包的概念尤为重要,决定了我们是否能够利用这些丰富的资源,以及如何妥善组织好我们自己的代码。

4.3.1 模块的导入

首先,我们来导入一个内置模块

import math


math是Python标准库中的一个模块,用来进行数学运算,我们在上面使用 import 关键字将它导入了,现在我们就可以使用它的功能啦。

# 求一个数的平方根
math.sqrt(4)


现在我们可以使用math模块里的所有函数了,可以使用dir来看一下都有哪些函数

dir(math)


我们也可以使用from ... import ...这种语句来具体导入某一个子模块或函数

from math import sqrt
sqrt(4)


这种方式更精确的导入某个函数,使用起来更方便, 但要注意重名的问题。如果说我们的代码本来就有一个叫sqrt的函数,那我们可以使用 as 关键字来给模块起一个别名

from math import sqrt as squarte

squarte(4)  # 2


或者是这样:

import math as math2

math = 0
math2.sqrt(4)  # 2


有时候需要一次性导入多个模块,可以这样写

import sys, os
from math import sqrt, pow


注意,在实际调用模块之前,必须先导入,否则将会产生报错。

# 将会产生NameError
math.sqrt(4)
import math


如果导入一个不存在的模块,也会产生报错。

# 将会产生ModuleNotFoundError: No module named 'maht'
import maht


4.3.2 自定义模块

很简单的一部分,先不讲了,反正大家刚开始学不会用到,等到后面要用到的时候你们就自然会了。

4.3.3 常用内置模块

除了上面使用过的math模块以外,Python还有大概100多个内置模块,下面我们来介绍一下常用的几个模块。

datetime - 日期时间类型

datetime模块中包含了几个常用的日期时间类,其中最常用的是datetime和timedelta。

注意,我们在下面使用的datetime是指datetime类而不是datetime模块。

from datetime import datetime, timedelta

# 返回当前时间的datetime对象
now = datetime.now()
type(now)

# 查看当前时间的年、月、日
print(now.year, now.month, now.day)

# 查看当前时间的时间戳,精确到微秒
now.timestamp()


计算机中时间的起始点都是1970年1月1日00:00:00,时间戳就是从1970年1月1日00:00:00到现在总秒数。所以如果时间戳A比时间戳B的值小,说明A在B之前。

datetime也提供将日期时间对象和字符串相互转换的操作,这在处理数据时会经常使用。

# 返回指定格式的日期字符串, 下面的返回 "2020-08-10 20:29:41"
datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# 将指定格式的字符串转换为日期时间对象
datetime.strptime('2020-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')


%Y这种形式是日期时间的格式代码,下面是一些常用的代码含义:

代码	含义										示例
%Y	十进制数表示的带世纪的年份。				2019,2020
%m	补零后,以十进制数显示的月份。				01, 02, ..., 12
%d	补零后,以十进制数显示的月份中的一天。		01, 02, ..., 31


**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/af9e0a81ad083d9e13ac5b396fa8ddc1.png)
![img](https://img-blog.csdnimg.cn/img_convert/740e4d468f9a589745818c2a31a92797.png)
![](https://img-blog.csdnimg.cn/img_convert/46506ae54be168b93cf63939786134ca.png)
![](https://img-blog.csdnimg.cn/img_convert/252731a671c1fb70aad5355a2c5eeff0.png)
![](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png) 
![](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)**
![img](https://img-blog.csdnimg.cn/img_convert/67229a1e50633ea4c4d811ef180b8655.png)



做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。



别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。

我先来介绍一下这些东西怎么用,文末抱走。

* * *



**(1)Python所有方向的学习路线(新版)**

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。



最近我才对这些路线做了一下新的更新,知识体系更全面了。



![在这里插入图片描述](https://img-blog.csdnimg.cn/8fc093dcfa1f476694c574db1242c05b.png)



**(2)Python学习视频**



包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。



![在这里插入图片描述](https://img-blog.csdnimg.cn/d66e3ad5592f4cdcb197de0dc0438ec5.png#pic_center)



**(3)100多个练手项目**

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。



![在这里插入图片描述](https://img-blog.csdnimg.cn/f5aeb4050ab547cf90b1a028d1aacb1d.png#pic_center)



**(4)200多本电子书**  

  

这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。



基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。



**(5)Python知识点汇总**

知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。



![在这里插入图片描述](https://img-blog.csdnimg.cn/c741a91b05a542ba9dc8abf2f2f4b1af.png)



**(6)其他资料**



还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。



![在这里插入图片描述](https://img-blog.csdnimg.cn/9fa77af248b84885a6ec779b2ead064d.png)

**这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。**




**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/6f4392f46af5dd70fa82428cb628892f.png)

:%S')

# 将指定格式的字符串转换为日期时间对象
datetime.strptime('2020-01-01 00:00:00', '%Y-%m-%d %H:%M:%S')


%Y这种形式是日期时间的格式代码,下面是一些常用的代码含义:

代码	含义										示例
%Y	十进制数表示的带世纪的年份。				2019,2020
%m	补零后,以十进制数显示的月份。				01, 02, ..., 12
%d	补零后,以十进制数显示的月份中的一天。		01, 02, ..., 31


**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中...(img-JoMfhKWp-1712949700192)]
[外链图片转存中...(img-jKGo6r7A-1712949700193)]
![](https://img-blog.csdnimg.cn/img_convert/46506ae54be168b93cf63939786134ca.png)
![](https://img-blog.csdnimg.cn/img_convert/252731a671c1fb70aad5355a2c5eeff0.png)
![](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png) 
![](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)**
[外链图片转存中...(img-F5i6NhEu-1712949700193)]



做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。



别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。

我先来介绍一下这些东西怎么用,文末抱走。

* * *



**(1)Python所有方向的学习路线(新版)**

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。



最近我才对这些路线做了一下新的更新,知识体系更全面了。



![在这里插入图片描述](https://img-blog.csdnimg.cn/8fc093dcfa1f476694c574db1242c05b.png)



**(2)Python学习视频**



包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。



![在这里插入图片描述](https://img-blog.csdnimg.cn/d66e3ad5592f4cdcb197de0dc0438ec5.png#pic_center)



**(3)100多个练手项目**

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。



![在这里插入图片描述](https://img-blog.csdnimg.cn/f5aeb4050ab547cf90b1a028d1aacb1d.png#pic_center)



**(4)200多本电子书**  

  

这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。



基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。



**(5)Python知识点汇总**

知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。



![在这里插入图片描述](https://img-blog.csdnimg.cn/c741a91b05a542ba9dc8abf2f2f4b1af.png)



**(6)其他资料**



还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。



![在这里插入图片描述](https://img-blog.csdnimg.cn/9fa77af248b84885a6ec779b2ead064d.png)

**这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。**




**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-8AITHGG7-1712949700193)]

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值