学习Python全套代码【超详细】Python入门、核心语法、数据结构、Python进阶【致那个想学好Python的你】

大家早上好,本人姓吴,如果觉得文章写得还行的话也可以叫我吴老师。欢迎大家跟我一起走进数据分析的世界,一起学习!

感兴趣的朋友可以关注我的数据分析专栏,里面有许多优质的文章跟大家分享哦。


文末附上详细的Python学习笔记链接。

目录

本篇文章的目标

通过本篇文章的学习,带你养成编程思维,掌握Python所有的基础语法。

而且,看完就完完全全可以说自己会Python了!

第一部分 Python入门

第一节 Python语言介绍

Python是世界上最流行的编程语言,已经连续多年占据各种语言的排行榜第一位。(当然不是所有榜单)

在各个领域都有着广泛应用,包括数据分析、服务器开发、运维开发、人工智能开发,甚至少儿编程也开始学习Python。

这也不经让无数程序猿朋友感叹:人生苦短,我用Python!

而关于Python的发展历史,这里我不会详细讲解,就放一张创始人的照片吧!

Python语言创始人

在这里插入图片描述

Java语言创始人

在这里插入图片描述

C++语言创始人

在这里插入图片描述

JavaScript语言创始人

在这里插入图片描述

Go语言创始人

在这里插入图片描述

看完各个编程语言的创始人,选什么语言不用我多说了吧(手动狗头)

第二节 Python安装及配置

1.2.1 Python版本介绍

Python目前有两个版本共存,Python 2 和 Python 3,其中Python官方宣布:Python 2于2020年1月1 日起不再维护,所以如果你现在开始学Python,肯定是要学习Python3了。

但由于Python2的生命周期较长,而且是刚刚停止维护,所以在许多公司中还在使用,有些面试官喜欢 在面试的时候问你:Python2 和 Python3有何区别?不要担心,他们的区别其实没那大,只要掌握了 Python3,很快你就能掌握它们的一些细微差别。

现在你只需要知道,有这么两个版本,且它们之间有一些差别。在之后的过程中,我们会具体的讲。

1.2.2 安装Python环境

  1. 访问Python官网,https://www.python.org , Python官网会通过浏览器识别你的操作系统,所以 只要按下图操作即可下载相应的版本。

在这里插入图片描述

  1. 双击运行下载下来的安装文件,按照提示一步步的进行安装(除了地址可以更改,其他默认即可)。

  2. 测试是否正确安装。

    1. 打开控制台,Windows在搜索栏里输入"cmd"并回车。(Mac打开系统自带的"终端"应用程序)

    2. 在控制台内输入"python",并回车,如果出现以下界面,则说明安装成功。在这里插入图片描述

1.2.3 Hello World

Python的代码以简洁高效著称,比如下面这四行代码,就可以实现包含10000个数据的正态分布直方图。

# 以下这段新手直接运行一般都会报错,咱先不运行,这里仅用作讲解
import matplotlib.pyplot as plt
import numpy as np

plt.hist(np.random.randn(10000), bins=40)
plt.show()

效果如下:

在这里插入图片描述
你看,短短4行代码,就能画出这么一个图,是不是很nice!

那接下来,我们来写个Hello world吧,Python的第一行代码绝对比你想象的简单,在上一步打开的控制台内,输入以下的代码:

print("Hello world")

然后敲回车键,如果你的显示器上显示了“Hello world”,恭喜你,你的第一个Python程序已经运行成功了,是不是很简单!试着把Hello world替换成你想说的话,再次运行看看。

这里的print是一个内置函数,关于函数的概念我们可以先不管,我们只要知道使用print函数可以在屏幕上输出指定的内容。print函数也可以支持这样的写法:

print("Hello", "world")
print("Hello", "my", "friends")

逗号隔开了多个单词,我们没有输入空格,但在输出的时候Python会自动用空格把每个单词隔开,实际上,我们可以输入100个,1000个无数个单词。大家注意逗号在这里的用法,后面我们会经常用到这种写法。

第三节 交互式编程

在上面的步骤中,我们输入了一行代码,按回车键后,将代码提交给了Python内置的解释器,解释器运行了代码后,将运行结果打印在了控制台上, 这种一次输入对应一次输出的形式,我们称之为交互式编程。

试试将Hello world案例中右边的引号去掉,看看会输出什么。

你会看到一句话,像这样:

SyntaxError: EOL while scanning string literal

解释器尝试运行你的代码,但由于那个缺失的引号,导致执行的过程中出错,解释器向你打印了这个错误, 这时候屏幕显示的不是输出,而是error(异常或者说报错)。一个有经验的程序员,看到这个error 信息后就能判断出是什么地方出现了问题。

而如果后续你还跟着看我的博文的话,你也是能成为老司机的哦!

qis交互式编程并不是Python独有的,但Python的交互式界面是最强大的,它还有很丰富的插件,提供了一系列非常强大的功能,这些有机会的话我也会在后面详细介绍。

第四节 Python开发工具

其实python的开发工具有很多,但单从使用功能以及安装体验上来讲,最适合大家的我认为是Pycharm。

Pycharm其实安装起来也非常简单,点击官网下载地址https://www.jetbrains.com/pycharm/download/点击下载就可以啦(蓝色的download是收费版,一般我们用社区版即可)。

在这里插入图片描述

安装除了地址外,其他按默认即可。

安装完之后打开,点击create new project(你们左边应该是空白的)。在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

这样,我们就能在pycharm上运行程序啦。

第二部分 核心语法

第一节 变量

2.1.1 变量的定义

变量就是可变的量,对于一些有可能会经常变化的数据,我们需要使用一个符号,这样才能计算中使用它,就像我们在小学时学过的一元方程中的"x"一样。比如说,我们在控制台内输入:

x = "world"

这样我们就新定义了一个变量,它的值是"world", 我们稍稍修改一下Hello world的例子,这次我们使用变量x。

print("Hello", x)

运行后输出的结果和我们之前的例子是一样的,在这里,x就相当于"world"。对于变量,我们可以理解为一个带有抓手的容器,我们可以往里任何我们想放的东西,而这个抓手,就是变量名,提着这个抓 手,我们可以更方便的使用容器里的东西。

我们再写几个例子,巩固一下:

name = "Smith"
print("Mr", name)
print("Mrs", name)

在这个例子里,有一对史密斯夫妇,对他们的称呼应该分别叫Mr Smith 和 Mrs Smith,注意我们在代码中只写了一次Smith,就可以实现2次使用。还有一个好处是,假如换了一对夫妇,他们的名字不一 样,我们只要修改name的值就行了,其他的代码完全不用修改,是不是很方便呢?

大家思考一下,假设上面的例子中,我们要打印100遍他们的名字,如果不用定义变量,那当我们修改时要怎么做?理解到变量的作用了吧?

2.1.2 变量名规则

在Python中,变量名遵循以下的规则:

  1. 变量名由字母、数字和下划线组成,也就是a-z, A-Z, 0-9和_

  2. 变量名不能以数字开头

  3. 变量名是区分大小写的,比如a 和 A就是两个不同的变量。

这个规则大家不用背,我们来举几个例子看一看,很快就明白了。

这些都是合法的变量名:

a = "hello"

MyJob = "analyst"

player1 = "姚明"

_private_name = "ss"

player1_score = 41

_ = "something"

下面这些都是不合法的变量名:

1abc   # 不能以数字开头

123

abc-   # 注意这是横杠符号,不是下划线

另外,在此基础上,我们也约定了一些变量名的命名规范。比如:

  1. 普通变量一般用小写字母

  2. 最好不要用单词加数字的方式来命名变量,尤其是没有意义的随机数字。

  3. 有两种风格的变量名:单词之间用下划线隔开;每个单词的首字母使用大写字母。

注意,上面的规范只是程序员们的一种约定俗成规则,并不是Python语言的硬性要求。但是遵循了这些规范,写出来的变量名可读性更高,更有利于团队合作。

2.1.3 关键字

以下的这些单词都是Python的关键字,每个编程语言都有各自的关键字,它们组成了编程语言基本的语法规则。大家先不用管它们的作用,只要在命名的时候避免使用这些名字就行了。

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

事实上,如果你一不小心定义了一个与关键字相同的变量,代码会在运行的时候报错。比如下面这个:

True = 0

上面这行代码运行会输出下面的信息,提示不能给关键字赋值。

SyntaxError: can’t assign to keyword

2.1.4 变量赋值

变量是可以重复使用,并且是可以修改的,由于Python的变量不需要先声明,所以变量的声明和赋值是在同一行代码完成的,比如我们上面声明的name变量

name = "Smith"

在这一行里,我们先定义了一个名为"name"的变量,然后给它赋值为"Smith",这样,我们就有了一个 变量"name",它的值是"Smith"。变量必须要先声明,才能使用。如果使用了一个从未定义过的变量 xxx,Python解释器会抛给你一个这样的错误:

NameError: name ‘xxx’ is not defined

在变量定义完成后,我们可以随意读取、修改它的值,比如:

# 定义name变量,赋值为"Smith"
name = "Smith"
print("Mr", name)

# 修改变量的值为"Jones"
name = "Jones"
print("Mr", name)

这上面这个例子中,我们使用了同一个变量"name",但它的值发生了变化,导致程序中两次输出的结果也发生了相应的变化。

现在我们掌握了如何给变量赋值,我们再来学习稍微高级一点的技巧,让大家领略Python的高效。有时候,我们需要定义好几个变量,以进行计算。比如现在我想要定义三个变量,并把它们的值都设为1,可以这样写:

a = b = c = 1

这样就定义三个变量,且它们的初始值都是1,那如果我想定义的三个变量值都不一样呢?

a, b, c = 1, 2, 3

大家可以试一下,在控制台里输入这样一行代码,然后分别打印一下a,b,c这三个变量的值、

通过测试可以发现,这样写其实就相当于是:

a = 1
b = 2
c = 3

这也是其他大部分编程语言的写法,大家比较一下,哪种写法更高效呢?

我们再思考一下,既然可以同时给多个变量赋予不同的值,那可不可以直接交换几个变量的值呢?

# 定义两个变量,其中a=1, b=2
a,  b= 1, 2

# 进行交换,现在a=2, b=1
a, b = b, a

这样的操作非常优雅、高效,大家可以试试更多的变量的交换。

2.1.5 变量的销毁

在Python中,我们一般无需手动的去销毁变量,Python的垃圾回收机制会帮我们处理掉那些不再使用的变量,如果我们想刻意销毁删除某个变量,可以使用del关键字,像这样

del name

销毁后,这个变量就像没有定义过一样,再使用它的话就会产生异常。(不过说实话我自己倒是基本没用这个功能。

第二节 数据类型

如果有学习过其他编程语言的同学,可能在学习变量的时候就会想问,在Python中定义一个变量的时 候,怎么没有先声明它的类型呢?

这正是Python的简洁高效之处。变量在Python中是不区分类型的,但它的值有类型,不同的值有不同的类型,我们来了解一下下面这三种基本的数据类型:

数值,包括整数和浮点数
字符串
布尔值

2.2.1 数值

Python中数值有两种类型,整形(int)和浮点型(float),通俗一点的讲,一个是不带小数点的,一个带小数点的。

# 这是一个整形
a = 100

# 这是一个浮点型变量
b = 3.14

整形和浮点形数值可以相互运算,比如

# c是一个浮点型变量
c = a + b

这个很容易理解,一个整数加上一个小数,得到的肯定还是一个小数。我们再看另一个例子,可以先猜猜是什么类型。

a = 0.14
b = 3.14 
c = b - a
# c=3.0

这里面变量c还是一个浮点型,它的值是3.0,可以得出一个结论,只要有浮点数参与的运算,它的结果肯定也是浮点数。

2.2.2 字符串

我们在Hello world例子中使用的就是字符串类型

a = "Hello world"

当如果字符串含有特殊字符,比如双引号,我们可以加一个\来进行转义。

print("Buddha: \"What we think, we become.\"")

# 输出的结果是:Buddha: "What we think, we become."

截止目前,我们在定义字符串时使用的都是双引号,其实Python也支持单引号,它们并没有什么区别。

a = "Hello"
b = 'world'

print(a, b)
# 输出的结果仍然是Hello world

这种灵活的写法给了我们便利,比如上面那个字符串中包含双引号的例子中,我们可以使用单引号定义字符串,这样就不用对字符中的字符串进行转义了,这样看起来是不是清爽了很多。

print('Buddha: "What we think, we become."')

有时候我们要定义的字符串比较长,超出了一行的长度,在编写的时候使用一个反斜杠""来连接多行:

sentence = "This's a very long long long \
long long sentence............"

print(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."""

print(zen)

当然,上面的双引号也可以用单引号代替。

2.2.3 布尔值(boolean, bool)

布尔值只有两个值: True 和 False,顾名思义,True代表真,False代表假,我们用布尔值一般是用来做条件判断。在Python3中,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(),我们在控制台按顺序输入以下的代码:

Python
# 以下值都为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() 将其他类型转换为字符串,

True, False, None 这三个值转换为字符串就是它们的单词本身,实际上在实际编程中,我们很少需要把它们转换成字符串来使用。

str(True)
str(False)
str(None)
# 结果是'True' # 结果是'False' # 结果是'None'

对于数值类型,我们也可以用str()将它们转换为字符串,

print("My age is", str(18))

在上面的例子中,我们使用str()将18转换为"18",然后再输出。实际上,在这个例子中,如果不进行强制转换的话,也不会出错,那是因为,print函数会自动将数值先转换成字符串再输出。

2.2.5.3 数值转换

数值转换是我们在数据分析过程中经常用到的,因为我们从任何文件中读到的数字一开始都是字符串, 需要将它们转换为数值才能进行运算。

如果想要把一个整数字符串转换为int类型,使用int();

num = "1"
int(num) + 1

如果将int()去掉,这段代码则会报错,因为num是个字符串,不能将字符串和数值进行相加。

数值的前后带有空格也可以成功转换:

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

注意第二行中,9.9并没有转换为10,虽然这样更符合我们的常识,四舍五入嘛,但float在被转换为int的过程中,它的小数部分精度将被丢弃,只取整数部分。所以在将float转换为int的过程中,永远要小 心。

那如果想要四舍五入的来转换呢,毕竟这样更符合我们的需求。Python给我们提供了一个内置函数: round

round(9.9)  #结果为10 
round(9.5)  #结果为10
round(9.49) #结果为9

甚至,round函数让我们可以指定要保留小数点后几位:
保留小数点后两位,由于第三位是1,所以结果是3.14 round(3.1415926, 2)
保留小数点后三位,由于第四位是5,所以结果是3.142 round(3.1415926, 3)

布尔值也可以转换为int或者float

int(True)  # 结果是1
int(False)  # 结果是0

float(True) # 结果是1.0
float(False)# 结果是0.0

观察一下,恰好是数值类型转换为布尔类型的相反过程。

第三节 运算符

2.3.1 算术运算符

Python中进行数值运算需要的符号如下,我们来一一学习一下。

运算符	描述	    实例
+	    加	    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(1 + 1) 
print(10 - 5) 
print(4 * 2) 
print(4 / 2)  # 结果是2.0

上面的例子都是简单的加减乘除,我们用肉眼就能观察出结果,需要注意的是除法运算, 当我们使用单斜杠除法运算符时,即使除数和被除数都是整数,它的返回结果也是一个浮点数。

print(5 / 2)  # 结果是2.5 
print(5 // 2)  # 结果是2

当我们需要对两个整数进行除法运算,且对结果的小数精度要求不高的时候,一般使用双斜杠来进行运算,这样能保证结果也是整数,不用再进行类型转换。

print(10 / 3)  # 结果是3.3333333333333335

上面这行代码的结果很有意思,我们都清楚,10 ÷ 3的结果是3.333…无限循环小数,但无限循环小数毕竟是个数学概念,计算机无法表达,所以只能显示到一定的位数。细心的你可能会发现最后一位为什么是5呢?四舍五入的话也应该是3呀。这是因为小数以二进制形式表示时的有穷性导致的,也就是计算机底层机制的原因。这里面的细节大家可以先不管,但要记住进行浮点数运算时是有精度问题的。

print(10 % 4)  # 结果是2
print((2 + 8) * 3)  # 结果是30

取模运算符大家先暂时可以理解为取余数,记住以下几个规则:

  1. 当两个数能整除时,取模运算的结果为0,比如 8 % 4 的结果是0

  2. 当0<a<b时,a % b = a,比如 3 % 8 的结果是3

  3. 当两个数中有负数时,结果可能会跟我们预料的不同,记住这个公式就行了 :a % b 就相当于a - (a // b) * b

2.3.2 使用算术运算符操作字符串

对,没有看错,字符串在Python中也可以“运算”,有两种操作,我们先看看加法。

print("Hello " + "world")

加号可以将两个字符串拼接成一个字符串,也可以将多个字符串拼接在一起:

print("apple " + "apple " + "apple ")

那如果有太多的"apple"怎么办呢?总不能一直加下去吧,这时候我们可以使用"*"运算符,表示将该字符串重复几次并拼接成一个字符串。

print("apple " * 5)

使用加号拼接字符串时需要注意,不能将字符串与数值一起相加,比如说我们要给用户发现一条查询积分的消息:

print("您账户积分为:" + 500)

这样写将会报错,所以我们要在拼接之前,先将500转换为字符串,运用我们上面学过的知识,修改成这样:

print("您账户积分为:" + str(500))

2.3.3 赋值运算符

赋值操作其实我们已经用过了,在前面定义变量的时候我们都会使用等于号。

num = 1
name = "Smith"

结合上面的运算符,我们可以做更多的赋值操作:

运算符	描述				实例
+=		加法赋值运算符	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

我们在日常编程中最常用的是加法赋值和减法赋值,比如设置一个计数器,每次+1

count = 0
count += 1
count += 1

执行了两次加法赋值操作后,count的值变成了2。

2.3.4 比较运算符

比较运算符是用来运算布尔值的, 将两个值放在一起比较,最后得到True或者False。

运算符	描述		实例
==		等于		100 == 100
!=		不等于	100 != 99
>		大于		2 > 1
<		小于		1 < 2
>=		大于等于	3 >= 2
<=		小于等于	2 <= 3

我们可以在控制台cmd中的python里输入一些比较运算,看看它们的返回值是什么

100 == 100  # True
100 == "abc"  # False
1 != 2  # True
2 != 2  # False
3 > 3  # False
3 >= 3  # True
2 < 3  # True
2 <= 3  # True

这都是符合我们的常识的操作,大家在控制台里试着敲一遍就明白了。下面再试试一些比较特殊的例子:

100 == "100"   # False
100 == 50 * 2  # True
str(100) == "100"  # True
1.0 != 1  # False
int(1.9) == 1  # True

需要注意的一点是,数值100 和字符串"100"是不相等的,这一点尤其要小心,这种隐藏的bug很难被发现。

2.3.4 逻辑运算符

真实的程序中,常常需要判断的条件是复杂的,所以我们需要逻辑运算符将多个比较运算连接起来。

运算符	逻辑表达式	描述	 
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

我们来看几个例子:

# 定义两个变量 
a, b = 1, 2 

a > 0 and a < b  # True

a % 2 == 0 or b % 2 == 0  # True

not a > b  # True

and, or, not它们也是Python中的关键字,这是经常需要使用的三个关键字。掌握好逻辑运算符是学好任何一门编程语言的基础。

逻辑运算符可以与赋值操作组合,让你的代码更老练:

a = 0 or 100   # a=100
b = 1 or 2  # b=1

第四节 流程控制

经过前面三个小节的学习,我们已经对Python编程语言有了一个初步的了解。如果说Python是一条蟒蛇,变量、数据类型、运算符就是它的肌肉和骨架,而流程控制相当于它的神经系统,掌握了流程控 制知识,就可以让Python灵活的游动起来。

Python的流程控制比较简洁,主要分为两块:条件判断和循环控制。

2.4.1 条件判断

2.4.1.1 if…else…语句

在这里要介绍两个重要的关键字,if 和 else,看它们的字面意思就能猜到它们表示“如果”… “否则”… 我们来看看在代码中如何使用

num = 3

# 通过取模运算判断num是否能被2整除
if num % 2 == 0:
   print(str(num) + "是一个偶数")
else:
   print(str(num) + "是一个奇数")

这一波段代码是一个完整的逻辑,今后我们写的程序会更复杂,但也只不过是这段代码的重复和组合而 已。

2.4.1.2 elif

上面的例子中是一个非常简单的逻辑,一个整数,它要么是偶数要么是奇数,如果说再复杂一点的条件,我们该如何写呢?比如说来判断一个学生的考试成绩,60分以下的为不及格,60~90分为合格,90 分以上为优秀,这里面有三个条件。

score = 59

if score < 60:
   print("您的考试成绩不及格")
elif score < 90:
   print("您的考试成绩合格")
else:
   print("您的考试成绩优秀")

在这个例子中,使用了一个新的关键字"elif", 它只能用在if判断之后,else之前。

2.4.1.3 if 条件嵌套

还是以学生考试成绩的例子来说,现在我们想对60-100分的进行更细的划分,60-70分为合格,70-90 分为良好,90分以上为优秀。

score = 100

if score >= 60:
	if score < 70:
       print("您的考试成绩为合格")
   elif score < 90:
       print("您的考试成绩为良好")
   else:
       print("您的考试成绩为优秀")
else:
   print("您的考试成绩不及格")

嵌套循环可以嵌套无数层,但通常我们都建议尽量减少嵌套的层数,以增加代码的可读性。

2.4.1.3 与逻辑运算符组合
age = 22
if age > 18 and age < 60:
   print("你已经不是个孩子啦,该去工作啦")

上面的例子表示,年龄大于18岁并且小于60岁,也可以这样写:

age = 22

if 18 < age < 60:
   print("你已经不是个孩子啦,该去工作啦")

这样的代码更简洁,可读性更高,这是Python特有的简洁写法。

2.4.1.4 自动类型转换

if 和 elif 的后面总是跟着一个表达式,这个表达式的结果必须是True或者False,如果表达式运算出来的结果不是一个布尔值,则会自动将结果转换为布尔值,无论它是什么类型的值。转换的结果遵循我们之 前学过的布尔转换规律。

count = 0

if count:
	print("条件成立")
else:
	print("条件不成立")

试着将count的值改为1,条件则成立了。

result = None
if result:
   pass
else:
   print("什么收获都没有")

记住:0值、None 和空字符串转换为布尔值后都是False

pass是Python的关键字,它表示什么也不做。

2.4.2 循环

看到循环我们首先会想到一些物体,比如旋转的风扇、车轮,一个圆的形状。一个圆形的跑道,如果沿着它跑,除非我们自己停下来,永远不会有终点,但是可以计算我们跑了多少圈。假设我们在跑步之前,给自己设定一个目标,跑完10圈就不跑了,然后去一圈一圈的跑完了10圈,停止。这个过程就很像代码里的循环语句了。

2.4.2.1 while循环
lap = 0

while lap < 10:
   lap += 1

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

运行上面的代码,会得到以下的输出:

我跑完了第1圈
我跑完了第2圈
我跑完了第3圈
我跑完了第4圈
我跑完了第5圈
我跑完了第6圈
我跑完了第7圈
我跑完了第8圈
我跑完了第9圈
我跑完了第10圈
2.4.2.2 for循环

for循环可以用来遍历序列,序列指的是一个可迭代的有序的集合,比如字符串就是一个序列,下面我 们用for循环来打印出一个字符串中的所有字符。

seq = "hello"

for s in seq:
   print(s)

这段代码的输出如下:

h
e
l
l
o

也可以用for循环来打印一连串的数字,这里需要用到一个新的内置函数:range。

for i in range(5):
   print(i)

range函数的作用就是提供一个从0到4的迭代器,而for可以遍历这个迭代器。注意是从0开始的,整个循环一共打印了5个数。

输出结果是:

0
1
2
3
4

我们可以修改一下之前写的跑圈的while循环,改用for循环加range函数来实现:

for lap in range(10):
   print("我跑完了第" + str(lap + 1) + "圈")

对比一下,哪种写法更简洁,更优雅 ?很明显是用for循环这种。这里面有一个细节需要注意,由于lap的值是从0开始到9结束,所以我们需要在输出的时候给它+1。

2.4.2.3 嵌套循环

既然掌握了两种循环写法,那可以把它们组合起来运用,我们来写一个例子,在控制台中打印出指定边长的长方形或者正方形图案。

# 指定长方形的宽和高
width, height = 10, 5

# 因为是从上往下开始打印,所以先遍历高度

for i in range(height):
	for j in range(width):
		print("*", end="")
	print()

在这里,print函数有了第二个参数,end表示在打印完指定的内容后,在结尾再打印一个指定的字符串,默认每个print语句在结尾会加一个换行符"\n", 传递一个空字符串给它,表示打印完星号以后不再添加任何输出内容。

第二个print函数没有任何参数,那它会输出一个换行符。

所以整个程序的逻辑就是每输出10个星号后,输出一个换行符,最终输出的图案如下:

**********
**********
**********
**********
**********

既然可以打印出一个长方形,那我们也可以打印一个直角三角形:

*
**
***
****
*****

代码如下

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

再来一个稍微复杂一点的案例,打印出这样一个九九乘法表:

1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

代码如下:

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
%H	以补零后的十进制数表示的小时(24 小时制)。	00, 01, ..., 23
%M	补零后,以十进制数显示的分钟。				00, 01, ..., 59
%S	补零后,以十进制数显示的秒。				00, 01, ..., 59

只要得到了datetime对象,我们就可以把它转换成各种格式。同样,只要有一个相对标准化的格式,我们就可以将它转换为datetime对象。

得到datetime对象后,可以对它进行修改,显示去年的今天现在这个时候的时间:

now = datetime.now()
last_year = now.replace(year=2019)
print(last_year.strftime('%Y-%m-%d %H:%M:%S'))

如果我们想知道两个datetime对象之间相差多长时间,可以将这两个对象相减

delta = now - last_year
type(delta)

得到的对象就是一个timedelta对象,我们可以根据timedelta对象知道这两个时间相差多少天多少分多少秒。

delta.days

delta.seconds

现在得到delta就是一个timedelta对象, 它表示366天整的时间,我们也可以将一个datetime对象和一个timedelta对象相加,将会得到一个新的datetime对象:

now + delta

将当前时间加上366天,就是明年的明天。timedelta类提供了非常便捷的方式帮我们处理日期时间,比如我们想要构造:

# 从当前开始20天后的时间
datetime.now() + timedelta(days=20)

# 两个半小时之前的时间
datetime.now() - timedelta(hours=2, minutes=30

time - 时间的访问和转换

还有一个经常使用的时间模块time

import time

# 返回当前时间戳
time.time()

# 返回当前时间的格式化字符串
time.strftime('%Y-%m-%d %H:%M:%S')

其实time模块的另一个函数我们也经常使用,它可以使我们的程序暂时睡一会儿

print("好累呀,我要小睡3秒钟")
time.sleep(3)
print("好啦,我又元气满满!")

sleep函数会将当前的程序暂停若干秒数。

random - 生成随机数

准确的说是生成伪随机数,这是一个数学问题。默认random模块会根据当前的系统时间作为随机数种子,所以可以保证生成的随机数不会重复。

# 生成一个随机浮点数,范围[0.0, 1.0)
random.random()

# 生成1到100之间的随机整数,包括1和100
random.randint(1, 100)

# 从序列中随机抽出一个元素
random.choice(['a', 'b', 'c', 'd', 'e', 'f', 'g'])

# 从序列中随机抽出k个元素,注意抽出来的元素可能会重复
random.choices(['a', 'b', 'c', 'd', 'e', 'f', 'g'], k=2)

# 跟choices函数类似,但它是不重复的随机抽样
random.sample(['a', 'b', 'c', 'd', 'e', 'f', 'g'])

# 将一个序列随机打乱,注意这个序列不能是只读的
lst = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
random.shuffle(lst)

os - 操作系统接口

os模块提供了一种使用与操作系统相关的功能的便捷式途径。需要大家了解一些操作系统的知识。

# 获取当前目录的路径
os.getcwd()

# 创建指定目录
os.mkdir(path)

# 与 mkdir() 类似,但会自动创建到达最后一级目录所需要的中间目录。
os.makedirs(path)

# 返回一个列表,该列表包含了 path 中所有文件与目录的名称。
os.listdir()

另一个很常用的子模块就是os.path,它提供了常用的路径操作。

# 显示当前目录的绝对路径
os.path.abspath('./')
os.path.abspath("__file__")

在大部分操作系统中,一般用 . 表示当前目录,用 . . 表示父级目录

相对路径:相对于当前目录的路径

绝对路径:以根目录为开始的路径(windows和Mac、Linux的根目录不同)

目录分隔符:windows 是 \ , Mac 和 Linux中是 /

# 如果 path 是 现有的 目录,则返回 True。
os.path.isdir(path)

# 如果 path 是 现有的 常规文件,则返回 True。
os.path.isfile()

# 目录分隔符
os.sep

# 合理地拼接一个或多个路径部分。
os.path.join(path, *paths)

# 返回路径 path 的目录名称
os.path.dirname("/tmp/test.txt")  # '/tmp'

# 返回路径 path 的基本名称,文件名或是最后一级的目录名
os.path.basename("/tmp/test.txt")  # 'test.txt'


os.path.basename("/tmp/test")  # 'test'

sys - 系统相关参数及函数

首先看的是 sys.path ,它返回的是一个列表,包含了若干个路径,它表示的是Python查找包的路径顺序,第一个是一个空字符串,它表示当前目录。之所以我们使用import 语句可以导入模块,靠的就是 它。

sys.path

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是一个列表,它的第一个元素就是脚本的文件名。所以传递它的启动参数,都会放在列表的后面。我们可以使用这种方式接收用户传递的参数。

结束语

最后,送给大家一点小福利,我把自己y学习python的资料也放到网盘啦,大家再尝试完本文的博客代码后,也可以看看这些,帮助你夯实基础和进阶。(当然,本文博客代码也有包含在里面)

链接:https://pan.baidu.com/s/1UPDlpr8SnyUE3X_XQRrMYA
提取码:1024


关注我,了解更多相关知识!

CSDN@报告,今天也有好好学习

  • 47
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

报告,今天也有好好学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值