python系列教程之基础特性第一篇-数据类型

前言

python, 是最近几年非常非常流行的一门编程语言, 简单易用的语法和丰富的库让它成为几乎所有领域的开发首选. 只要是将来想从事编程相关的工作, 尤其是想要在数据分析和机器学习领域混口饭吃的同学, 迟早都是要学学python的.

python是Guido Van Rossum在1989年圣诞节在家里打发时间(反正我是不会用写代码打发时间)而编写的用来改善ABC缺点的编程语言, 至于为什么最初取名叫Python其实和它现在的logo(一条可爱的蟒蛇)并没有关系, 只是因为作者喜欢一个叫Monty Python’s的英国喜剧团体.

相信大多数想来学学python的同学都是因为在网上看到有人说”人生苦短,我用python”的吧, 就好像我看到很多人说vim是最好的编辑器就想去学学vim. 最初这句话是 Bruce Eckel说的, 英文原文是”life is short, you need python”, 你可能没听说过这个人, 但是你大概听说过它写的书, Thinking in c++Thinking in java都是出自于他手, 也都是非常经典的作品. 因为其语法设计的原因, python对于小白来说非常友好,所以对于想要迅速完成某个任务的人来说, python可以说是最好上手的.

人生苦短,我用python

当然啦, 虽然是简单易学, 但是想要掌握python的精髓和哲学, 还是需要长时间的使用和练习. 这个系列的教程, 我想达到的目的呢, 一是重新回顾一下python的基础,二是给异地的女朋友写一个比较清楚的中文教程, 再者就是让对python没有了解但是想了解和已经了解但是了解的不多的同学能够对python的各个方面都有一个比较清楚的认识, 知道遇到一个问题如何用pythonic的方式思考和解决.当然啦,我也是一边学一边写, 如果有觉的我写的不对的地方还请大佬指出.

python和编辑器的安装我就不说了, 大家根据的操作系统和喜好安装就好. 整个大概会分为以下几个部分:

  • 基础特性
  • 函数
  • 内置函数
  • 函数式编程
  • 类和对象
  • 面向对象编程
  • 高级特性
  • 使用模块和发布模块
  • 内置模块
  • 常用第三方模块
  • 测试和调试
  • 网络应用
  • 网络应用实战
  • 数据分析
  • 数据分析实战
  • 机器学习
  • 机器学习实战

有几点问题先提前声明一下:

  • 文章内容有参考网上资料,如果你有错误或者侵权的地方,烦请告知我。
  • 每篇教程中的代码都是在python3.6的环境下测试过的, 建议大家也直接使用python3。如果你们有不同的结果, 应该是我的笔误, 烦请告知我。
  • 可能我会不自觉的省略一些我认为你们知道但是你们不知道的内容。如果有不清楚的地方,烦请告知我。
  • 某些名词我觉得翻译成中文表达不是很好我就直接用的英文,如果你觉得某个中文翻译比英文表达要好,烦请告知我。
  • 每一节的代码我都会写在jupyter notebook,方便大家练习。如果大家觉得有比较好的代码示例,烦请告知我。

每篇文章的长度尽量不会写太长方便大家阅读. 如果能够帮助到你们学习python那是最好啦~

基础特性

变量

在编程语言中,变量是存储单个数据的基本单位,比如最简单的正数和字符串,学过c/c++的同学知道,声明一个变量的时候同时需要指定变量的类型,但是在python中,我们不需要这样,这是诸如python的动态类型语言拥有的特性,与之相反,诸如c/c++, java之类的语言,属于静态类型语言,在编译期间就需要确定数据类型。比如下面的代码:

1
2
def sum(a, b):
return a + b

只有在运行的过程才能确定是否合法。只有调用了该函数,解释器才能确定是否a + b是否有问题。

我们甚至还可以给一个变量赋予不同数据类型的数据,但是这并不意味着python是弱类型语言,相反,python是强类型语言,即在强制转换之前不允许不同类型变量的相互操作。写过js的同学就知道,经常要检查函数参数是不是正确的数据类型是一件很麻烦的事情。

再者,python的程序是可以在运行期间改变其结构的。可以引进新的函数,也可以删除已有的函数,具有这种特性的语言叫做动态语言。

因此,python是一种动态类型,强类型的动态语言。

这种语言的运行效率相比于c/c++而言是很慢的,但是写起来可以说是非常舒服。语言本身决定了其用处,那么python必然是用在对速度要求不是那么高的地方,因此既然如果你需要用到python,那么就不用考虑运行速度之类的问题。

变量命名

对于我来说,人生的两大难题,一个是中午吃什么,一个是给变量命名。给变量命名看似很不起眼,实际上决定了阅读你代码的人或者一周后的你自己会不会骂你是傻逼。

在给变量起名字方面,我们的祖先已经总结出一套实用的规则,一般来讲,我们取的名字要是meaningful的,也就是要让人看到名字就知道是拿来干嘛的。具体来讲,一个变量由几个名词或者形容词组成,遵循从具体到宽泛的原则,连接方式有camelcasesnakecase两种,在python中,除了类的名字遵循camelcase之外,其余都是遵循snakecase,下面是一个合理的变量名

1
top_student_name

除了变量名,还有类名,函数名等等的规范,大家可以查看pep

标准数据类型

一段程序, 实际上就是对一定的输入进行某种操作, 得到我们想要的输出. 因为处理对象的的存储方式和表达特征不同, 我们要给不同的数据赋予不同的类型, 以此来方便处理。

在python中主要有以下集中数据类型:

  • numerics
  • sequences
  • mappings
  • classes
  • instances
  • exceptions

这是比较宽泛的分类,在这篇文章中我们不会注意解释,只会了解几个常用的比较简单的数据类型。即:

  • Number
  • String
  • List
  • Range
  • Tuple
  • Sets
  • Dictionary

需要注意的是,在这六种数据类型中,

  • 不可变的有四个: Number, String, Tuple, Sets
  • 可变的有两个:Lists, Dictionary

所谓不可变就是一旦定义就不可改变,反之为可变。具体使用那种要根据场景来。

另外,List, tuple,range属于sequence type,具有一些共同的性质,比如:

  • x in s, x not in x 判断x是是否在s中
  • s + t, n * s s和t拼接, 将s重复n次
  • s[i:j:K] 切片操作
  • len(s) / min(s) / max(s) s的长度,最小值,最大值
  • s.count(x) 统计s中x的次数

Number

我们大可以把python解释器当做计算器来用, 实际上我有时候就是这么干的. 在python中, 我们用+,-,*,/来代表数学中的加减乘除四中运算,除此之外,我们还有 // 来获取除法结果的向下取整部分, 用 % 来获取除法结果的余数. 用 ** 表示幂运算, 也可以用math模块中的pow()函数达到同样的目的,关于内置模块的介绍会在后面部分. 这几种运算可以用在int和float类型的数据上.下面演示这几种运算:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 2 + 2  
4
>>> 2 - 2
0
>>> 2 * 2
4
>>> 5 / 2
2.5
>>> 5 // 2
2
>>> 5 % 2
1

String

我们可以用” “或者’ ‘来向python的解释器表明这是一个字符串而不是一个变量名或者函数名之类的. 那如果想要在字符串里面用”或者’呢? 这里分几种情况, 如果要使用”也就是双引号, 可以在外层用单引号或者用转义符\, 如果要用’也就是单引号, 可以在外层使用双引号或者用转义符\, 转义符的意思是跟在它后面的字符不是它本身的含义. 下面举例子说明:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 'I am handsome'
'I am handsome'
>>> 'I\'m handsome'
"I'm handsome"
>>> "I'm handsome"
"I'm handsome"
>>> "I am \"handsome\""
'I am "handsome"'
>>> 'I am "handsome"'
'I am "handsome"'
>>> ""I\'m" handsome"
'"I\'m " handsome'

不难注意到,第2,4,6个例子中,原字符串和结果中的字符串最外层的引号是不一样的,这是因为在命令行的解释器中为了美观做的调整, 遵循一个原则, 如果原字符串只包含单引号而不包含双引号那么结果最外层用双引号包裹, 其余情况都用单引号包裹.

那又有同学可能会问了, 为什么第六个例子中跟在斜杠后面的单引号为什么没有被转义呢? 答案是….没有为什么, 如果想显示出来就只能扔到内置的print函数里面打印出来, print函数用来使字符串更加具有可读性, 主要作用就是去除最外层的引号和字符串里面的特殊字符比如换行(\n)和制表符(\t).值得一提的是, 通过转义的特殊字符是不会直接通过解释器打印出来的, 只能通过print函数显示出来. 具体见下面的例子:

1
2
3
4
5
6
7
8
9
>>> ""I\'m" handsome"
'"I\'m " handsome'
>>> print(""I\'m" handsome")
"I'm" handsome
>>> 'I am \n handsome'
'I am \n handsome'
>>> print('I am \n handsome')
I am
handsome

那仍然会有同学问了, 如果我就是想在字符串里面显示诸如\n或者\t的内容呢? 能不能不让它被转义?答案是有的, 我们可以在字符串前面加一个r来避免转义. 例如:

1
2
3
4
5
>>> print("my \name is lalala")
my
name is lalala
>>> print(r"my \name is lalala")
my \name is lalala

如果想要输出多行字符串,除了每一行用一个print函数之外,还可以用更优雅的三引号””” “””或者’’’ ‘’’.

1
2
3
4
5
6
7
8
>>> print("""\
I
am
handsome
""")
I
am
handsome

加一个\的意思是避免将第一个换行也包含到字符串中.

如果你想把几个字符串拼接在一起, 可以用+(加号或者*乘号),如果用变量表示字符串也是可以的, 另外相邻的两个字符串会自动拼接, 这个特性可以用来将一行文本拆分成多行文本(避免在编辑器中显示太宽, 但是实际打印出来还是一行文本), 但是变量和字符串不会自动拼接.

1
2
3
4
5
6
7
8
9
10
11
>>> 2 * 's' + 'b'
ssb
>>> s = 's'
>>> 2 * s + 'b'
ssb
>>> 's''b'
sb
>>> s'b' # error
>>> print('I am'
'handsome')
I am handsome

总之,单引号双引号都可表示字符串是一个人性化的设计,一般来说,如果字符串里有单引号就用双引号,反之用双引号,但是建议优先使用单引号。如果需要换行使用三引号。

String Format

有时候,我们需要在字符串中嵌入变量的值,或者让字符串的输出更加美观,string的format方法就是用来做这个事情的,在需要format的字符串中,会用{}来表示待替换的位置,即'...{}...'.format([arguments])。括号里面的语法规则概括来说就是[field_name] ![conversion] :[format_spec]

field_name 传入的关键字参数名字或者index。如果是第一个参数那么index就是0

conversion 就是在参数传入字符串之前先进行一些转变。目前支持三种转变:

  • {!s} 调用str()函数先
  • {!r} 调用repr()函数先
  • {!a} 调用ascii()函数先

format_spec就是一个format string,代表传入的字符串该以什么形式显示。比如用多少宽度显示,左对齐还是右对齐,用什么字符填充,显示精度以及显示类型。具体可以用什么选项可以取参考官方文档

下面举几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> '{0}, {1}, {2}'.format('a', 'b', 'c')
'a, b, c'
>>> '{2}, {1}, {0}'.format('a', 'b', 'c')
'c, b, a'
>>> '{first}, {second}, {third}'.format(first='a', second='b', third='c')
'a, b, c'
>>> '{:*^30}'.format('centered') # use '*' as a fill char
'***********centered***********'
>>> "int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42)
'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010'
>>> import datetime
>>> d = datetime.datetime(2010, 7, 4, 12, 15, 58)
>>> '{:%Y-%m-%d %H:%M:%S}'.format(d)
'2010-07-04 12:15:58'

List

列表List可以说是python中最常用的数据类型, 一般用来存储待操作的数据,这可能是得益于其丰富的内置函数,切片以及列表推导的特性,使用起来方便而灵活,学过c的同学可以理解成加强版的数组。理论上来说,列表可以存储不同的数据类型,但是实际上一般都是存储的同样的数据类型。

比如我们用一个列表存储过去一小时网站新注册的用户名:

1
>>> names= ['zhuhaihao', 'shenliangshan', 'leijian', 'liaoyuan']

然后我们对其进行一些处理数据基本的最基本操作:增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> names                     
['zhuhaihao', 'shanzi', 'leijian', 'liaoyuan']
>>> names.append('shazi')
>>> names
['zhuhaihao', 'shanzi', 'leijian', 'liaoyuan', 'shazi']
>>> names.pop() # 移除指定位置的元素,如果不给定位置,默认移除最后一个元素
'shazi'
>>> names
['zhuhaihao', 'shanzi', 'leijian', 'liaoyuan']
>>> names.remove('leijian') # 移除第一个和给定参数相同的元素
>>> names
['zhuhaihao', 'shanzi', 'liaoyuan']
>>> names[0] = 'zhuhaohai' #中括号里面的0是表示元素的位置
>>> names
['zhuhaohai', 'shanzi', 'liaoyuan']
>>> names[0]
'zhuhaohai'

在后面的内容中我们会知道,列表是python的内置对象,它总共有9个已经实现的方法我们可以直接使用,在上面的代码中出现的append, remove, pop是其中的三种,剩下的分别是:

  • extend(iterable) 将所有参数中的所有元素添加到列表末尾,参数是一个可迭代对象,什么是可迭代对象后面会详细解释
  • insert(i, x) 在指定位置i插入指定元素x
  • index(x, start, end) 在指定范围内获取指定元素相对于原列表的索引, start和end是可选参数
  • count(x) 统计某元素出现的次数
  • sort() 给列表排序,默认是升序,如果要降序,传入False即可, 即sort(False)
  • reverse() 将列表中元素倒置

前面说了,列表之所以如此常用除了这些直接拿来用内置的函数之外,更得益于其独特的切片特性和强大的推到功能,下面简要介绍一下。

列表和字符串这种数据类型,都是能够被“索引的”,也就是能够用[index]的形式访问。就切片特性而言,你可以想象字符串python['p', 'y', 't', 'h', 'o', 'n']是一样的效果。

切片

切片的意思是,当你只需要对可索引对象的一部分的时候,你需要“切取”那一部分出来,语法很简单, 用var[start:end:step]即可获取var变量以start到end之间步长为step的元素,当step为正数的时候,为正向切除,step为负的时候为反向切除。这里的start和end并不是索引的位置,而是索引之间的空隙,即:

1
2
3
4
5
 +---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1

如果不写start,默认是第一个空隙,同样如果不写end,默认是最后一个位置。

下面是切片的常见例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> word = ['p', 'y', 't', 'h', 'o', 'n']                   
>>> word[1:]
['y', 't', 'h', 'o', 'n']
>>> word[::-1]
['n', 'o', 'h', 't', 'y', 'p']
>>> word[::-1]
['n', 'o', 'h', 't', 'y', 'p']
>>> word[::-2]
['n', 'h', 'y']
>>> word[1:3]
['y', 't']
>>> word[:3]
['p', 'y', 't']
>>> word[1:]
['y', 't', 'h', 'o', 'n']
>>> word[0:6:2]
['p', 't', 'o']

这里的例子是基于数组的,对于字符串也有同样的操作,但是有一点值得注意的是字符串是不可变对象,而列表是可变对象,意思就是我们可以更改数组中的某一部分的元素,但是不能更改字符串。比如:

1
2
3
4
5
6
7
8
>>> word[1:2] = 'l'           
>>> word
['p', 'l', 't', 'h', 'o', 'n']
>>> word_ = 'python'
>>> word_[1:2] = 'l'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
列表推导

我们在创建列表的时候,最简单的方法就是直接赋值,比如numbers = [0, 1, 2],但是大多数的情况是,我们需要对另外一个可迭代对象的每个元素进行某种操作或者判断是否满足某种条件来得到我们想要的列表。

比如我们有一个列表存储着班上同学的年龄,我们想知道他们的出生年份,那么我们只需要用2018减去列表中的每个元素即可。用for循环实现就是:

1
2
3
4
5
6
7
>>> ages = [19, 18, 20, 19, 18]
>>> birth = []
>>> for i in ages:
... birth.append(2018 - i)
...
>>> birth
[1999, 2000, 1998, 1999, 2000]

在python中,我们还可以用一种更为简洁的方式来实现:

1
2
3
>>> ages = [19, 18, 20, 19, 18]
>>> birth = list(map(lambda x: 2018 - x, ages))
>>> birth = [2018 - i for i in ages]

实际上在python的代码中,我们更倾向于这种方式来创建列表,当你要创建一个列表的时候第一个想到的是for循环,那说明你还是用c的思维方式来写python代码,倒也不是说for循环不能用,只是后者更”pythonic”一些。

下面再举一个例子,还是班上所有人的年龄,这次我们想要找出偶数年龄。

首先用for循环的思路:

1
2
3
4
5
6
7
8
9
>>> ages                      
[19, 18, 20, 19, 18]
>>> even_ages = []
>>> for i in ages:
... if i % 2 == 0:
... even_ages.append(i)
...
>>> even_ages
[18, 20, 18]

简洁一点的方式:

1
2
3
>>> even_ages = [i for i in ages if i % 2 == 0]
>>> even_ages
[18, 20, 18]

通过上面的两个例子我们可以总结,列表推导的一般组成是:中括号,然后里面包含一个表达式,紧跟着一个for循环,然后还有零个或多个for循环或者if语句。

del关键字

del关键字用来删除列表中的某个元素或者删除整个列表,下面是使用示例:

1
2
3
4
5
6
7
8
9
10
>>> ages                      
[19, 18, 20, 19, 18]
>>> del ages[0]
>>> ages
[18, 20, 19, 18]
>>> del ages
>>> ages
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'ages' is not defined

range

range类型是不可变整数序列,主要用于在for语句中控制循环的次数,比如,如果我们要循环十次做某种重复性操作操作的话:

1
2
for i in range(10):
# do something

range不支持sequence type 中的拼接和重复操作,因为这两种操作会违背其连续整数序列的性质。

tuple

我们可以发现前面讲过的字符串和列表有很多相似的地方,比如索引和切片操作,他们都属于有序数据类型,而tuple也是一种有序数据类型,所谓有序其实也就是可以索引,后面还会讲到无序数据类型,元素的顺序不固定,没办法索引。

tuple由一系列逗号分开的值组成,如果只包含一个元素,要在最后面加一个逗号,如果不包含元素,用一对括号初始化即可。和数组一样,tuple可以嵌套tuple,和字符串一样,tuple也是不可变对象,不能改变某个元素的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> t                         
(1, 'egweg', (1, 2))
>>> t[0]
1
>>> t[0] = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t = ()
>>> t
()
>>> t = 1,
>>> t
(1,)

一个很重要都额问题是: 什么时候用List,什么时候用Tuple。一般而言,Tuple用于异构集合,类似于C语言中的结构体,通过packing初始化,通过unpacking操作或者索引来获取。而List一般用于同质集合,也就是所有元素都是一样类型,通过列表推导初始化,通过遍历或者索引来获取。另外一个重要的区别是,Tuple是不可变对象,而List是可变对象。

set

之前提到过,前面说的三种数据类型都是有序集合,而set则是一种无序集合,可以通过大括号中显示声明元素初始化,不过更常见的方式是提供一个字符串或者列表给构造函数set()来初始化。如果是要初始化一个空的set,不能使用{},而要使用set()函数,因为前者会初始化一个Dict(后面会讲到)。

1
2
3
4
5
6
7
8
9
>>> s = {'p', 'y', 't', 'h', 'o', 'n'}                      
>>> s
set(['h', 'o', 'n', 'p', 't', 'y'])
>>> s = set('python')
>>> s
set(['h', 'o', 'n', 'p', 't', 'y'])
>>> s = set(['p', 'y', 't', 'h', 'o', 'n'])
>>> s
set(['h', 'o', 'n', 'p', 't', 'y'])

可以看到,虽然三种初始化的方式显示出来的结果是一样的顺序,但是这并不能说明set是有序对象。

set最重要的性质是,集合内不能有相同的元素,本质上set是数学意义上集合的实现,因此元素不能重复,以及无序等性质都是因为数学集合也有该性质。比如,set可以进行交集并集运算。

dict

最后一个python的基本数据类型是dict字典,我们可以把他看做是无序的key-value集合,通过大括号包裹,里面用逗号分割开key:value对来初始化,也可以直接传入key-value序列给dict()函数来初始化。dict不可以用索引获取,只能用key获取对应的value值,而且key只能是不可变对象比如字符串,数字,只包含字符串或者数字的tuple,任何直接或者间接包含可变对象的都不能作为key值。

如果要删除某个value值,用del关键字即可。

1
2
3
4
5
6
7
8
9
10
11
>>> info = {'zhu': 18, 'shan': 13} 
>>> info = dict([('zhu', 18), ('shan', 13)])
>>> info
{'shan': 13, 'zhu': 18}
>>> info['zhu']
18
>>> 'zhu' in info
True
>>> del info['zhu']
>>> 'zhu' in info
False

和列表一样,还可以通过字典推导来初始化。

1
2
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

最后

到这里,python的基本数据类型就介绍完了,下一节是python的流程控制。

有钱的捧个钱场,没钱的捧个人场