life is short, you need python
人生苦短,我用python
— bruce eckel
给深度学习入门者的python快速教程
基础篇
numpy和matplotlib篇
5.1 python简介
本章将介绍python的最基本语法,以及一些和深度学习还有计算机视觉最相关的基本使用。
5.1.1 python简史
python是一门解释型的高级编程语言,特点是简单明确。python作者是荷兰人guido van rossum,1982年他获得数学和计算机硕士学位后,在荷兰数学与计算科学研究所(centrum wiskunde & informatica, cwi)谋了份差事。在cwi期间,guido参与到了一门叫做abc的语言开发工作中。abc是一门教学语言,所以拥有简单,可读性好,语法更接近自然语言等特点。在那个c语言一统天下的年代,abc就是一股简单的清流,毕竟是门教学语言,最后没有流行起来,不过这段经历影响了guido。1989年的圣诞假期,闲得蛋疼的guido决定设计一门简单易用的新语言,要介于c和shell之间,同时吸取abc语法中的优点。guido用自己喜欢的一部喜剧电视剧来命名这门语言:《monty python‘s flying circus》。
1991年,第一版基于c实现的python编译器诞生,因为简单,拓展性好,python很快就在guido的同事中大受欢迎,不久python的核心开发人员就从guido一人变成了一个小团队。后来随着互联网时代的到来,开源及社区合作的方式蓬勃发展,python也借此上了发展的快车道。因为python非常容易拓展,在不同领域的开发者贡献下,许多受欢迎的功能和特征被开发出来,渐渐形成了各种各样的库,其中一部分被加入到python的标准库中,这让本来就不需要过多思考底层细节的python变得更加强大好用。在不过多考虑执行效率的前提下,使用python进行开发的周期相比传统的c/c++甚至java等语言都大大缩短,代码量也大幅降低,所以出bug的可能性也小了很多。因此有了语言专家bruce eckel的那句名言:life is short, you need python. 后来这句话的中文版“人生苦短,我用python”被guido印在了t恤上。发展至今,python渐渐成了最流行的语言之一,在编程语言排行榜tobie中常年占据前5的位置。另外随着python的用户群越来越壮大,慢慢在本身特点上发展出了自己的哲学,叫做python的禅(the zen of python)。遵循python哲学的做法叫做很python(pythonic),具体参见:
pep 20 — the zen of python
或者在python中执行:
python
1
>>importthis
python拥有很好的扩充性,可以非常轻松地用其他语言编写模块供调用,用python编写的模块也可以通过各种方式轻松被其他语言调用。所以一种常见的python使用方式是,底层复杂且对效率要求高的模块用c/c++等语言实现,顶层调用的api用python封装,这样可以通过简单的语法实现顶层逻辑,故而python又被称为“胶水语言”。这种特性的好处是,无需花费很多时间在编程实现上,更多的时间可以专注于思考问题的逻辑。尤其是对做算法和深度学习的从业人员,这种方式是非常理想的,所以如今的深度学习框架中,除了matlab,或是deeplearning4j这种摆明了给java用的,其他框架基本上要么官方接口就是python,要么支持python接口。
5.1.2 安装和使用python
python有两个大版本,考虑到用户群数量和库的各种框架的兼容性,本文以python2(2.7)为准,语法尽量考虑和python3的兼容。
unix/linux下的python基本都是系统自带的,一般默认为python2,使用时在终端直接键入python就能进入python解释器界面:
在解释器下就已经可以进行最基本的编程了,比如:
写程序的话还是需要保存成文件再执行,比如我们写下面语句,并且保存为helloworld.py:
print(“hello world!”)
然后在终端里执行:
安装更多的python库一般有两种方法,第一是用系统的软件包管理,以ubuntu 16.04 lts为例,比如想要安装numpy库(后面会介绍这个库),软件包的名字就是python-numpy,所以在终端中输入:
>> sudo apt install python-numpy
python自己也带了包管理器,叫做pip,使用如下:
>> pip install numpy
安装和深度学习相关的框架时,一般来说推荐使用系统自带的包管理,出现版本错误的可能性低一些。另外也可以使用一些提前配置好很多第三方库的python包,这些包通常已经包含了深度学习框架中绝大多数的依赖库,比如最常用的是anaconda:
download anaconda now!
windows下的python安装简单一些,从官方网站下载相应的安装程序就可以了,当然也有更方便的已经包含了很全的第三方库的选择,winpython:
winpython
并且是绿色的,直接执行就可以用了。
5.2 python基本语法
there should be one– and preferably only one –obvious way to do it.
对于一个特定的问题,应该只用最好的一种方法来解决。
— tim peters
5.2.1 基本数据类型和运算
基本数据类型
python中最基本的数据类型包括整型,浮点数,布尔值和字符串。类型是不需要声明的,比如:
2
3
4
5
a=1# 整数
b=1.2# 浮点数
c=true# 布尔类型
d=false# 字符串
e=none# nonetype
其中#是行内注释的意思。最后一个none是nonetype,注意不是0,在python中利用type函数可以查看一个变量的类型:
type(a)#
type(b)#
type(c)#
type(d)#
type(e)#
注释中是执行type()函数后的输出结果,可以看到none是单独的一种类型nonetype。在很多api中,如果执行失败就会返回none。
变量和引用
python中基本变量的赋值一般建立的是个引用,比如下面的语句:
a=1
b=a
c=1
a赋值为1后,b=a执行时并不会将a的值复制一遍,然后赋给b,而是简单地为a所指的值,也就是1建立了一个引用,相当于a和b都是指向包含1这个值的这块内存的指针。所以c=1执行的也是个引用建立,这三个变量其实是三个引用,指向同一个值。这个逻辑虽然简单,不过也还是常常容易弄混,这没关系,python内置了id函数,可以返回一个对象的地址,用id函数可以让我们知道每个变量指向的是不是同一个值:
id(a)# 35556792l
id(b)# 35556792l
id(c)# 35556792l
注释中表示的仍是执行后的结果。如果这时候我们接下面两个语句:
b=2# b的引用到新的一个变量上
id(b)# 35556768l
可以看到b引用到了另一个变量上。
运算符
python中的数值的基本运算和c差不多,字符串的运算更方便,下面是常见的例子:
6
7
8
9
10
11
12
13
14
15
a=2
b=2.3
c=3
a+b# 2 + 2.3 = 4.3
c–a# 3 - 2 = 1
a/b# 整数除以浮点数,运算以浮点数为准,2 / 2.3 = 0.8695652173913044
a/c# python2中,整数除法,向下取整 2 / 3 = 0
a**c# a的c次方,结果为8
a+=1# python中没有i++的用法,自增用+=
c-=3# c变成0了
d='hello'
d+' world!'# 相当于字符串拼接,结果为'hello world!'
d+=' world!'# 相当于把字符串接在当前字符串尾,d变为'hello world!'
e=r\\t\'
print(e)# '\n\t\\'
需要提一下的几点:1)字符串用双引号和单引号都可以,区别主要是单引号字符串中如果出现单引号字符则需要用转义符,双引号也是一样,所以在单引号字符串中使用双引号,或者双引号字符串中使用单引号就会比较方便。另外三个双引号或者三个单引号围起来的也是字符串,因为换行方便,更多用于文档。2)python2中两个数值相除会根据数值类型判断是否整数除法,python3种则都按照浮点数。想要在python2种也执行python3中的除法只要执行下面语句:
from__future__importpision# 使用python3中的除法
1/2
3)字符串前加r表示字符串内容严格按照输入的样子,好处是不用转义符了,非常方便。
python中的布尔值和逻辑的运算非常直接,下面是例子:
a=true
b=false
aandb# false
aorb# true
nota# false
基本上就是英语,操作符优先级之类的和其他语言类似。python中也有位操作:
~8# 按位翻转,1000 -->-(1000+1)
8>>3# 右移3位,1000 -->0001
1<1000
5&2# 按位与,101 & 010 = 000
5|2# 按位或,101 | 010 = 111
4^1# 按位异或,100 ^ 001 = 101
==, !=和is
判断是否相等或者不等的语法和c也一样,另外在python中也常常见到is操作符,这两者的区别在于==和!=比较引用指向的内存中的内容,而is判断两个变量是否指向一个地址,看下面的代码例子:
b=1.0
a==b# true,值相等
aisb# false,指向的不是一个对象,这个语句等效于 id(a) == id(b)
aisc# true,指向的都是整型值1
所以一定要分清要比较的对象应该用那种方式,对于一些特殊的情况,比如none,本着pythonic的原则,最好用is none。
注意关键字
python中,万物皆对象。不过这并不是这里要探讨的话题,想说的是一定要注意关键字,因为所有东西都是对象,所以一个简简单单的赋值操作就可以把系统内置的函数给变成一个普通变量,来看下边例子:
id(type)# 506070640l
type=1# type成了指向1的变量
id(type)# 35556792l
id=2# id成了指向2的变量
from__future__importprint_function
print=3# print成了指向3的变量
注意print是个很特殊的存在,在python3中是按照函数用,在python2中却是个命令式的语句,最早print的用法其实是下边这样:
printhello world!
这么用主要是受到abc语法的影响,但这个用法并不pythonic,后来加入了print函数,为了兼容允许两种用法并存。所以单纯给print赋值是不灵的,在python2中使用python3中的一些特性都是用from __future__ import来实现。
模块导入
因为提到了对象名覆盖和import,所以简单讲一下。import是利用python中各种强大库的基础,比如要计算cos(π)的值,可以有下面4种方式:
# 直接导入python的内置基础数学库
importmath
print(math.cos(math.pi))
# 从math中导入cos函数和pi变量
frommathimportcos,pi
print(cos(pi))
# 如果是个模块,在导入的时候可以起个别名,避免名字冲突或是方便懒得打字的人使用
importmathasm
print(m.cos(m.pi))
# 从math中导入所有东西
frommathimport*
一般来说最后一种方式不是很推荐,因为不知道import导入的名字里是否和现有对象名已经有冲突,很可能会不知不觉覆盖了现有的对象。
5.2.2 容器
列表
python中的容器是异常好用且异常有用的结构。这节主要介绍列表(list),元组(tuple),字典(dict)和集合(set)。这些结构和其他语言中的类似结构并无本质不同,来看例子了解下使用:
a=[1,2,3,4]
b=[1]
c=[1]
d=b
e=[1,hello world!,c,false]
print(id(b),id(c))# (194100040l, 194100552l)
print(id(b),id(d))# (194100040l, 194100040l)
print(b==c)# true
f=list(abcd)
print(f)# ['a', 'b', 'c', 'd']
g=[0]*3+[1]*4+[2]*2# [0, 0, 0, 1, 1, 1, 1, 2, 2]
因为变量其实是个引用,所以对列表而言也没什么不同,所以列表对类型没什么限制。也正因为如此,和变量不同的是,即使用相同的语句赋值,列表的地址也是不同的,在这个例子中体现在id(b)和id(c)不相等,而内容相等。列表也可以用list()初始化,输入参数需要是一个可以遍历的结构,其中每一个元素会作为列表的一项。“*”操作符对于列表而言是复制,最后一个语句用这种办法生成了分段的列表。
列表的基本操作有访问,增加,删除,和拼接:
16
17
18
a.pop()# 把最后一个值4从列表中移除并作为pop的返回值
a.append(5)# 末尾插入值,[1, 2, 3, 5]
a.index(2)# 找到第一个2所在的位置,也就是1
a[2]# 取下标,也就是位置在2的值,也就是第三个值3
a+=[4,3,2]# 拼接,[1, 2, 3, 5, 4, 3, 2]
a.insert(1,0)# 在下标为1处插入元素0,[1, 0, 2, 3, 5, 4, 3, 2]
a.remove(2)# 移除第一个2,[1, 0, 3, 5, 4, 3, 2]
a.reverse()# 倒序,a变为[2, 3, 4, 5, 3, 0, 1]
a[3]=9# 指定下标处赋值,[2, 3, 4, 9, 3, 0, 1]
b=a[2:5]# 取下标2开始到5之前的子序列,[4, 9, 3]
c=a[2:-2]# 下标也可以倒着数,方便算不过来的人,[4, 9, 3]
d=a[2:]# 取下标2开始到结尾的子序列,[4, 9, 3, 0, 1]
e=a[:5]# 取开始到下标5之前的子序列,[2, 3, 4, 9, 3]
f=a[:]# 取从开头到最后的整个子序列,相当于值拷贝,[2, 3, 4, 9, 3, 0, 1]
a[2:-2]=[1,2,3]# 赋值也可以按照一段来,[2, 3, 1, 2, 3, 0, 1]
g=a[::-1]# 也是倒序,通过slicing实现并赋值,效率略低于reverse()
a.sort()
print(a)# 列表内排序,a变为[0, 1, 1, 2, 2, 3, 3]
因为列表是有顺序的,所以和顺序相关的操作是列表中最常见的,首先我们来打乱一个列表的顺序,然后再对这个列表排序:
importrandom
a=range(10)# 生成一个列表,从0开始+1递增到9
print(a)# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
random.shuffle(a)# shuffle函数可以对可遍历且可变结构打乱顺序
print(a)# [4, 3, 8, 9, 0, 6, 2, 7, 5, 1]
b=sorted(a)
print(b)# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
c=sorted(a,reverse=true)
print(c)# [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
元组
元组和列表有很多相似的地方,最大的区别在于不可变,还有如果初始化只包含一个元素的tuple和列表不一样,因为语法必须明确,所以必须在元素后加上逗号。另外直接用逗号分隔多个元素赋值默认是个tuple,这在函数多返回值的时候很好用:
a=(1,2...