5.1 Python基础
1. Python入门
1.1 Python语言介绍
1.2 安装及配置
1.3 交互式编程
1.4 Python开发工具
2. 核心语法
2.1 变量
2.1.1 变量定义
2.1.2 变量命名规则
- 变量名由字母、数字和下划线组成,也就是a-z, A-Z, 0-9和_
- 变量名不能以数字开头
- 变量名是区分大小写的,比如a 和 A就是两个不同的变量。
# 这些都是合法的变量名
a = "hello"
MyJob = "analyst"
player1 = "姚明"
_private_name = "ss"
player1_score = 41
_ = "something"
# 下面这些都是不合法的变量名
1abc # 不能以数字开头
123
abc- # 注意这是横杠符号,不是下划线
约定了一些变量名的命名规范(不是Python语言的硬性要求):
- 普通变量一般用小写字母
- 最好不要用单词加数字的方式来命名变量,尤其是没有意义的随机数字。
- 有两种风格的变量名:单词之间用下划线隔开;每个单词的首字母使用大写字母(也称为驼峰命名法)
2.1.3 关键字
在命名的时候避免使用这些关键字
False await else import pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield
2.1.4 变量赋值
-
变量是可以重复使用,并且是可以修改的。
-
由于Python的变量不需要先声明,所以变量的声明和赋值是在同一行代码完成的。
-
有时候,我们需要定义好几个变量,以进行计算:
a = b = c = 1
a, b, c = 1, 2, 3
- 还可以直接交换几个变量的值
# 定义两个变量,其中a=1, b=2
a, b = 1, 2
# 进行交换,现在a=2, b=1
a, b = b, a
2.2 数据类型
2.2.1 数值(整形、浮点型)
# 这是一个整形
a = 100
# 这是一个浮点型变量
b = 3.14
整形和浮点形数值可以相互运算,只要有浮点数参与的运算,它的结果肯定也是浮点数。
a = 0.14
b = 3.14
c = b - a # c=3.0
2.2.2 字符串
- 当如果字符串含有特殊字符,比如双引号,我们可以加一个"\"来进行转义
print("Buddha: \"What we think, we become.\"")
# 输出的结果是:Buddha: "What we think, we become."
- 另外,可以使用单引号定义字符串,这样就不用对字符中的字符串进行转义了
print('Buddha: "What we think, we become."')
- 在编写的时候还可以使用一个反斜杠""来连接多行:
sentence = "This's a very long long long \
long long sentence............"
- 还有更长的字符串,类似一整段话,我们可以使
用三引号
zen = """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."""
2.2.3 布尔值
布尔值只有两个值: True 和 False,用布尔值一般是用来做条件判断。True和False是关键字,所以我们不能定义为变量名。
2.2.4 空值 None
空值None是Python中的一个特殊值,它代表空值,表示“什么都没有”,None同时也是关键字。
None的用处有很多,比如说你想定义一个变量,但一时还没想好给它赋什么值,甚至连它用什么类型也没决定好,这时可以先用None
temp = None
print(temp)
None被输出的时候显示为一个字符串"None"
2.2.5 类型转换
2.2.5.1 布尔值转换
使用内置函数bool()
- 数值转换为布尔值
# 以下值都为True
bool(1)
bool(-1)
bool(255)
bool(0.0000001)
bool(-99.99)
# 下面的值为False
bool(0)
bool(0.0)
对于数值类型,所有的非零值转换为True, 只有零值才转换为False。
- 字符串转化为布尔值
# 这是一个空字符串,转换结果为False
bool("")
# 转换结果为True
bool("abc")
# 这是一个只包含一个空格的字符串,转换结果为为True
bool(" ")
对于任何非空的字符串,转换为布尔值都是True。
- 空值转换为布尔值永远都是False
# 结果为False
bool(None)
2.2.5.2 字符串转换
使用内置函数str()
print("My age is", str(18))
2.2.5.3 数值转换
使用int()
把一个整数字符串转换为int类型
num = "1"
int(num) + 1
数值的前后带有空格也可以成功转换
int(" 100 ") # 结果为100
带有符号的数字也可以成功转换
int("-1") # 结果为-1
int("+1") # 结果为1
使用float()
把一个带小数点的字符串转换为float类型
pi = "3.1415926"
float(pi)
int类型和float类型之间也可以相互转换
int(3.14) #结果为3
int(9.9) # 结果为9
float(100) # 结果为100.0
float在被转换为int的过程中,它的小数部分精度将被丢弃,只取整数部分
布尔值也可以转换为int或者float
int(True) # 结果是1
int(False) # 结果是0
float(True) # 结果是1.0
float(False)# 结果是0.0
2.3 运算符
2.3.1 算术运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 加 | 1 + 1; a + b |
- | 减 | 10 - 5; a - b -c |
* | 乘 | 4 * 2 相当 4 × 2 |
/ | 除 | 4 / 2 相当于 4 ÷ 2 |
// | 取整除 | 10 // 4 结果是 2 |
% | 取模 | 10 % 4 相当于 10 - (10 // 4) × 4 |
** | 指数 | 2 ** 3 相当于 2 * 2 * 2,也就是2的3次方 |
() | 小括号 | 提高运算优先级,比如: (2 + 8) * 3 |
- 浮点数运算时是有精度问题的,因为小数以二进制形式表示时的有穷性导致的,也就是计算机底层机制的原因。
print(10 / 3)
>>>3.3333333333333335
- 取模运算符大家先暂时可以理解为取余数,记住以下几个规则:
- 当两个数能整除时,取模运算的结果为0,比如 8 % 4 的结果是0
- 当0<a<b时,a % b = a,比如 3 % 8 的结果是3
- 当两个数中有负数时,结果可能会跟我们预料的不同,记住这个公式就行了 :a % b 就相当于a -(a // b) * b
- 0不能作为除数,也不能用来取模。
2.3.2 赋值比较运算符
赋值运算符 | 作用 | 使用 |
---|---|---|
+= | 加法赋值运算符 | c += a 等效于 c = c + a |
-= | 减法赋值运算符 | c -= a 等效于 c = c - a |
*= | 乘法赋值运算符 | c *= a 等效于 c = c * a |
/= | 除法赋值运算符 | c /= a 等效于 c = c / a |
//= | 取整除赋值运算符 | c //= a 等效于 c = c // a |
%= | 取模赋值运算符 | c %= a 等效于 c = c % a |
**= | 幂赋值运算符 | c = a 等效于 c = c a |
比较运算符 | 作用 | 使用 |
---|---|---|
== | 等于 | 100 == 100 |
!= | 不等于 | 100 != 99 |
> | 大于 | 2 > 1 |
< | 小于 | 1 < 2 |
>= | 大于等于 | 3 >= 2 |
<= | 小于等于 | 2 <= 3 |
2.3.3 逻辑运算符
运算符 | 逻辑表达式 | 描述 | |
---|---|---|---|
and | x and y | 任意一个是False,结果就是False | True and False 结果为False |
or | x or y | 任意一个是True,结果就是True; | True or False 结果为True |
not | not x | 将条件取反 | not False 结果为True |
2.4 流程控制
2.4.1 条件判断
2.4.1.1 if…elif…else…
嵌套循环可以嵌套无数层,但通常我们都建议尽量减少嵌套的层数,以增加代码的可读性。
2.4.1.2 自动类型转换
if 和 elif 的后面总是跟着一个表达式,这个表达式的结果必须是True或者False,如果表达式运算出来的
结果不是一个布尔值,则会自动将结果转换为布尔值,无论它是什么类型的值。
count = 0
if count:
print("条件成立")
else:
print("条件不成立")
>>> 条件不成立
result = None
if result:
pass
else:
print("什么收获都没有")
>>> 什么收获都没有
2.4.2 循环语句
2.4.2.1 while循环
可执行无限次循环,只有当条件不满足时,才终止循环
2.4.2.2 for循环
用来遍历序列,序列指的是一个可迭代的有序的集合,当序列穷尽时,循环结束
可使用range函数制造一个迭代器,再使用for循环遍历这个迭代器
2.4.2.3 break、continue与pass语句
break 语句可以跳出 for 和 while 的循环体。如果你从 for 或 while 循环中终止,任何对应的循环 else 块将不执行。
continue 语句被用来告诉 Python 跳过当前循环块中的剩余语句,然后继续进行下一轮循环。
pass语句不做任何事情,一般用做占位语句,为了保持程序结构的完整性
2.4.3 演示:智能密码锁程序
最近几年很流行的智能密码锁程序,除了可以用指纹开锁、人脸识别开锁外,都会有密码开锁的功能,以防万一。密码开锁功能是这样的,首先设定好密码,以后每次开锁的时候只要输入的数字中含有设定的密码,就视为解锁成功。这样的设定是为了防止别人的窥探,具有更高的安全性。
3. Python数据结构
3.1 字符串
3.1.1 字符串格式化
占位符 | 描述 |
---|---|
%d | 整数占位符 |
%f | 浮点数占位符 |
%.f | 指定精度的浮点数占位符 |
%s | 字符串占位符 |
%% | 输出百分号% |
format函数
>>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
'world hello world'
3.1.2 字符串的索引与切片
字符串其实也是一种序列,可以理解为一串整齐的按顺序排着队的字符,组成了字符串,那每个字符在队伍中都有自己的位置,这个位置就是下标,又叫作索引。
切片:截取字符串的部分
‘str’[开始位置:结束位置:步长]
3.1.3 字符串函数
函数 | 说明 |
---|---|
.format() | 格式化 |
.strip() | 去除空格 |
.lstrip() | 去除左侧空格 |
.rstrip() | 去除右侧空格 |
.upper() | 替换大写 |
.lower() | 替换小写 |
.capitalize() | 首字母大写 |
.title() | 每个单词首字母大写 |
.islower() | 判断是否全小写 |
.isupper() | 判断是否全大写 |
.isdigit() | 判断是否全数字 |
.stratswith() | 判断是否以…开头 |
.endswith() | 判断是否以…结尾 |
.find() | 搜索字符串中内容,并返回索引 ,找不到时返回-1 |
.index() | 搜索字符串中内容,并返回索引,找不到时报错 |
.count() | 返回在字符串中出现次数 |
.replace(被替换,替换内容,n) | 替换字符串内替换前n个内容 |
len() | 返回字符串长度 |
3.1.4 综合案例:异常号码数据分析
结合以上的字符操作知识,可以开发一个电话号码识别程序,用户输入一串数字,程序识别它是不是一个有效的电话号码,如果是有效电话号码,我们再识别它是一个固定电话号码、手机号码、还是400号码。用户输入"exit"后,程序退出。
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("无法识别该号码")
3.2 元祖
元祖和字符串一样不可变,也可以迭代循环,可以按索引访问,可以切片访问
综合案例:销售数据统计-销冠
# 当元组元素较多、较长时,可以这样书写
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 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))
3.3 列表
列表可以理解为可变的元组,它的使用方式跟元组差不多,区别就是列表可以动态的增加、修改、删除元素。
3.3.1 列表函数
函数 | 说明 |
---|---|
.append() | 尾部添加元素 |
.insert(索引,插入对象) | 在特定位置添加元素 |
.extend(列表) | 以列表形式插入多个元素 |
.pop() | 随机(输入索引后可指定)删除元素并返回删除对象 |
del | 删除元素 |
.remove(删除的元素) | 根据元素直接删除第一个匹配到的元素 |
.clear() | 清空列表 |
.reverse() | 反转列表排序 |
sort() | 从小到大排序 |
sort(reverse=True,key) | 从大到小排序,key可以定义由哪部分排序 |
.copy() | 拷贝列表 |
3.3.2 列表表达式
[i for i in range(10)]
>>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3.3.3 综合案例-销售数据统计-排行榜
sales = (
("Peter", (78, 70, 65)),
("John", (88, 80, 85)),
("Tony", (90, 99, 95)),
("Henry", (80, 70, 55)),
("Mike", (95, 90, 95)),
)
top_sales = [(sale, sum(amount))for sale, amount in sales]
top_sales.sort(key=lambda x:x[1], reverse=True)
print(top_sales)
3.4 字典
- 字典没有顺序,”键“区分大小写
- dict(含嵌套的序列)可强制转化为字典
3.4.1 遍历字典
for key,value in dic.items():
print(key,value)
3.4.2 字典函数
函数 | 说明 |
---|---|
.items() | 用以遍历字典 |
.get(key, default=None) | 返回指定键的值,键不在字典中返回默认值 None 或者设置的默认值 |
.keys() | 用以遍历”键“ |
.values() | 用以遍历”值“ |
3.5 集合
集合,是一个无序的、不重复的序列,一般用来删除重复数据,还可以计算交集、并集等。
函数 | 说明 |
---|---|
.add() | 添加元素 |
.remove() | 移除元素,如没有则报错 |
.discard() | 移除元素,没有不报错 |
.pop() | 随机(输入索引后可指定)删除元素并返回删除对象 |
.intersection() | 求交集 |
.union() | 求并集 |
.issubset() | 求是否子集 |
.issuperset | 求是否是父集 |
4. python进阶
4.1 函数
4.1.1 函数参数
- 函数的参数可以有一个,也可以有多个,也可以没有参数
- 参数是有顺序的,如果传参,要么按顺序传,要么就连着参数名一起传
- 参数可以设置默认值,但有传参的情况下,以传参为最后值
4.1.2 函数返回值
def permit(age):
if age >= 18:
print("准许进入")
return True
else:
print("禁止进入")
return False
print("end")
如果一个函数内没有任何return语句,那它的返回值就是None
def do_nothing():
pass
print(do_nothing()) # None
return也可返回多个参数
4.1.3 匿名函数
有时候我们临时需要一个函数,且它的逻辑比较简单,这时候就可以定义匿名函数。
square = lambda n: n * n
square(4)
>>> 16
lambda是Python中的关键字,它的作用就是用来定义匿名函数,匿名函数的函数体一般只有一行代码,省略了函数名和返回值语句
匿名函数也可设置多个参数
4.2 面向对象
4.2.1 面向对象基本概念
- 面向过程:根据业务逻辑从上到下写代码。
- 面向对象:将变量与函数、属性绑定到一起,分类进行封装,每个程序只要负责分配给自己的功能,这样能够更快速的开发程序,减少了重复代码。
什么是对象呢 ?我们可以理解为实际存在的实物,比如一个用户、一台ATM机、一幢建筑,亦或者是软件业务流程中产生的虚拟概念,比如订单、购物车、用户账户。
我们发现,不管是实体还是虚拟产物,它们都是有一些共同点,都是名词,有自己的行为,有自己的属性。
4.2.2 类和实例
现在我们用代码来实现一下“狗”对象,先介绍ython里的一个重要概念:类,它也是面试对象编程的基础。
class Dog:
pass
这样我们就定义了一个类,使用class关键字,加上一个类名,这样我们就定义了一个空的类。
类名一般使用名词,且使用驼峰式命名法
类是创建对象的模板,对象是类的实例。类就像生产线,有了类,就可以生产出许许多多的相同对象。
使用上面的Dog类来创建一个Dog对象:
dog = Dog()
type(dog)
>>> __main__.Dog
这里dog就是Dog的实例
也可以使用内置函数isinstance来判断对象与类的关系
isinstance('abd', str) # True
isinstance(1, int) # True
isinstance(dog, Dog) # True
4.2.3 对象的属性与方法
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.breed = '哈士奇'
dog.color = '黑白'
dog.size = '大'
print('一只%s型%s色的%s' % (dog.size, dog.color, dog.breed))
# 一只大型黑白色的哈士奇
使用**__init__ 函数来接收初始化参数**,这样就可以把属性的值作为参数在初始化对象的时候就传给它。
__init__ 函数是Python的类用来初始化对象的构造函数,它的名字是固定的,必须这样写,创建对象时会首先调用它
参数如果给了默认值,可以不用传;我们在之前学习的字符串、列表的所有函数,实际是调用字符串对象、列表对象的方法。
对象自身的属性是直接可以在方法里使用的,比如我们改造一下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))
4.2.4 类属性与方法
对象是从类创造的,对象的属性和方法虽然是在类中定义的,但它们的值是各个对象独有的,互相不能共享。
而类也有属性和方法,且它们可以和类创建的所有对象共享。
我们先来定义一个类
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,并返回。
这种@ 符号的写法叫做装饰器,装饰器是用来装饰函数的,不同的装饰器赋予函数不同的特殊功能。
现在我们再来试试:
g1 = Goods()
g2 = Goods()
g1.id # 00001
g2.id # 00002
Goods.id_count # 2
4.2.5 一切皆对象
在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
l1 is l2 # False
最后一行操作,使用is 关键字来判断这两个对象是否是同一个对象,结果是False。它表明l1和l2是不同的对象,这一点可以使用id函数看出来:
id(l1)
id(l2)
分别返回两串不同的数字,这个一长串的数字代表了对象所指向的内存空间地址。
4.2.6 综合案例-电商购物车商品统计分析
项目需求:可以设置每个商品的名称、价格、折扣率,用户将商品加入到购物车以后,能够立即显示所有商品、总价、折扣情况和实际需要支付的金额,也就是折扣后的金额。商品的名称、价格、折扣率都可以随意修改,且修改完成后,购物车中的相关信息和金额也会发生改变。
需求分析:在这个需求里面,提到了两个虚拟产物,商品与购物车,也就是说需要定义两个类。
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个方法。其实这个类主要就是提供了三个功能:
- 增加商品 add
- 减少商品 remove
- 显示商品 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.7 自定义属性-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
4.3 模块和包管理
最小化导入原则:应导入尽量小的包
4.3.1 自定义模块
当导入的模块与自定义模块名称冲突时,优先调用自定义模块
除了使用标准库的模块,我们也可以自己定义模块,实际上,这也是我们在项目中组织代码的基本方式。
以上一个综合案例“电商购物车”为例,实际的文件结构应该是这样的:
└── project
├── cart.py # Cart类
├── goods.py # Goods类
└── main.py # 入口文件
shopcart目录里面,有三个文件,不同的类写在不同的文件内,cart.py和goods.py文件和原来保持一致即可。
我们来看看main.py
from cart import Cart
from goods import Goods
g1 = Goods('iPhone 11', 6000, 0.9)
g2 = Goods('U盘32G', 100, 0.8)
g3 = Goods('华为P40', 5000)
cart = Cart()
cart.add(g1)
cart.add(g2, 3)
cart.show()
main.py作为项目的入口文件,我们实际运行的时候就是从它启动
python main.py
那Python解释器是怎么找到cart和goods模块的呢?因为它们和main.py在同一个目录下,所以可以自动的发现它们。那如果有很多这样的模块,我们想把它们放到同一个目录下,以便统一管理。
比如,需要将cart.py 和 goods.py放到shopcart包内,调整目录结构如下:
├── project
│ ├── main.py
│ └── shopcart
│ ├── __init__.py
│ ├── cart.py
│ └── goods.py
如果发现目录内有名为__pycache__ 的文件夹,那是Python在导入时自动生成的缓存文件,可以忽略。
在mian.py同级目录下,多了一个shopcart目录,注意,shopcart目录里除了cart.py和goods.py目录,还多了一个__init__.py ,这是一个空文件,它的文件名看起来很奇怪,这是Python规定的命名规范,只要目录有一个名为__init__.py 的文件,则将这个目录视为包(package)
现在修改main.py,作一下调整:
from shopcart.cart import Cart
from shopcart.goods import Goods
同时将下面的部分也做一下调整
if __name__ == '__main__':
g1 = Goods('iPhone 11', 6000, 0.9)
g2 = Goods('U盘32G', 100, 0.8)
g3 = Goods('华为P40', 5000)
cart = Cart()
cart.add(g1)
cart.add(g2, 3)
cart.show()
__name__ 表示当前文件所在模块的名称,模块可以通过检查自己的 __name__ 来得知是否运行在main 作用域中,这使得模块可以在作为脚本运行时条件性地执行一些代码,而在被 import 时不会执行。
4.3.2 常用内置模块
4.3.2.1 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 | 十进制数表示的带世纪的年份。 | 2019,2020 |
%m | 补零后,以十进制数显示的月份。 | 01, 02, …, 12 |
%d | 补零后,以十进制数显示的月份中的一天。 | 01, 02, …, 31 |
%H | 以补零后的十进制数表示的小时(24 小时制)。 | 00, 01, …, 23 |
%M | 补零后,以十进制数显示的分钟。 | 00, 01, …, 59 |
%S | 补零后,以十进制数显示的秒。 | 00, 01, …, 59 |
如果我们想知道两个datetime对象之间相差多长时间,可以将这两个对象相减
now = datetime.now()
last_year = now.replace(year=2019)
delta = now - last_year
type(delta)
>>> datetime.timedelta
得到的对象就是一个timedelta对象,我们可以根据timedelta对象知道这两个时间相差多少天多少分多少秒。
delta.days
delta.seconds
我们也可以将一个datetime对象和一个timedelta对象相加,将会得到一个新的datetime对象:
now + delta
4.3.2.2 time - 时间的访问和转换
import time
# 返回当前时间戳
time.time()
# 返回当前时间的格式化字符串
time.strftime('%Y-%m-%d %H:%M:%S')
其实time模块的另一个函数我们也经常使用,它可以使我们的程序暂时睡一会儿,sleep函数会将当前的程序暂停若干秒数。
print("好累呀,我要小睡3秒钟")
time.sleep(3)
print("好啦,我又元气满满!")
4.3.2.3 random - 生成随机数
准确的说是生成伪随机数,这是一个数学问题。默认random模块会根据当前的系统时间作为随机数种子,所以可以保证生成的随机数不会重复。
函数 | 说明 |
---|---|
random.random() | 随机浮点数,范围[0.0, 1.0) |
random.randint(1, 100) | 生成1到100之间的随机整数 |
random.choice([list]) | 从序列中随机抽出一个元素 |
random.choices([list], k=n)) | 从序列中随机抽出k个元素,注意抽出来的元素可能会重复 |
random.choice([list]) | 跟choices函数类似,但它是不重复的随机抽样 |
random.shuffle(lst) | 将一个序列随机打乱,注意这个序列不能是只读的 |
4.3.2.4 os - 操作系统接口
os模块提供了一种使用与操作系统相关的功能的便捷式途径。
在大部分操作系统中,一般用. 表示当前目录,用… 表示父级目录
相对路径:相对于当前目录的路径
绝对路径:以根目录为开始的路径(windows和Mac、Linux的根目录不同)
目录分隔符:windows 是 \ , Mac 和Linux中是 /
os函数
|函数| 说明|
|–|--|
|import os | 导入 |
|os.getcwd() | 获取当前目录的路径 |
| os.mkdir(path)| 创建指定目录,但只能在一级目录下创建新目录 |
| os.makedirs(path) | 创建多级目录 |
| os.listdir() | 返回一个列表,该列表包含了 path 中所有文件与目录的名称 |
os.sep|目录分隔符
os函数
函数 | 说明 |
---|---|
import os | 导入 |
os.getcwd() | 获取当前目录的路径 |
os.mkdir(path) | 创建指定目录,但只能在一级目录下创建新目录 |
os.makedirs(path) | 创建多级目录 |
os.listdir() | 返回一个列表,该列表包含了 path 中所有文件与目录的名称 |
os.sep | 目录分隔符 |
os.path函数
函数 | 说明 |
---|---|
os.path.abspath(’./’) | 显示当前目录的绝对路径 |
os.path.isdir(path) | 如果 path是现有的目录,则返回 True。 |
os.path.isfile() | 如果 path是现有的常规文件,则返回 True |
os.path.join(path, *paths) | 合理地拼接一个或多个路径部分。 |
os.path.dirname("") | 返回文件路径 path 的目录名称 |
os.path.basename("") | 返回路径 path 的基本名称,文件名或是最后一级的目录名 |
4.3.2.5 系统相关参数及函数——sys
首先看的是sys.path ,它返回的是一个列表,包含了若干个路径,它表示的是Python查找包的路径顺序,第一个是一个空字符串,它表示当前目录。
之所以我们使用import 语句可以导入模块,靠的就是它。
sys.argv 表示启动的时候传递给Python脚本的命令行参数。
import sys
if __name__ == '__main__':
print("Hello", end=' ')
if len(sys.argv) > 1:
print(' '.join(sys.argv[1:]))
上述是一个Python脚本hello.py 的代码,现在我们试着用不同的方式启动它
python hello.py
python hello.py Bill
python hello.py Mr Gates
我们会看到每次的输出不一样,再加两行代码,看一看sys.argv是什么?
# sys.argv是个列表
type(sys.argv)
print(sys.argv)
可以看到sys.argv是一个列表,它的第一个元素就是脚本的文件名。所以传递它的启动参数,都会放在列表的后面。
我们可以使用这种方式接收用户传递的参数。
5. 直播
5.1 python安装
python3可以安装多个版本,一般默认使用第一个安装的版本,可以使用具体指定版本号指定使用特定版本
5.2 pycharm
pycharm可以创建虚拟环境,**虚拟环境(venu)**常用于与他人协作的项目
pycharm可以使用debug模式,点击代码左侧,就可以启动断点执行来调试代码