原文:https://zhuanlan.zhihu.com/p/24162430html
本章将介绍Python的最基本语法,以及一些和深度学习还有计算机视觉最相关的基本使用。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》。git
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),具体参见:github
或者在Python中执行:shell
>> import this编程
Python拥有很好的扩充性,能够很是轻松地用其余语言编写模块供调用,用Python编写的模块也能够经过各类方式轻松被其余语言调用。因此一种常见的Python使用方式是,底层复杂且对效率要求高的模块用C/C++等语言实现,顶层调用的API用Python封装,这样能够经过简单的语法实现顶层逻辑,故而Python又被称为“胶水语言”。这种特性的好处是,无需花费不少时间在编程实现上,更多的时间能够专一于思考问题的逻辑。尤为是对作算法和深度学习的从业人员,这种方式是很是理想的,因此现在的深度学习框架中,除了MATLAB,或是Deeplearning4j这种摆明了给Java用的,其余框架基本上要么官方接口就是Python,要么支持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:
Windows下的Python安装简单一些,从官方网站下载相应的安装程序就能够了,固然也有更方便的已经包含了很全的第三方库的选择,WinPython:
而且是绿色的,直接执行就能够用了。
There should be one-- and preferably only one --obvious way to do it.
对于一个特定的问题,应该只用最好的一种方法来解决。
-- Tim Peters
基本数据类型
Python中最基本的数据类型包括整型,浮点数,布尔值和字符串。类型是不须要声明的,好比:
a = 1 # 整数 b = 1.2 # 浮点数 c = True # 布尔类型 d = "False" # 字符串 e = None # NoneType
其中#是行内注释的意思。最后一个None是NoneType,注意不是0,在Python中利用type函数能够查看一个变量的类型:
type(a) # <type 'int'> type(b) # <type 'float'> type(c) # <type 'bool'> type(d) # <type 'str'> type(e) # <type 'NoneType'>
注释中是执行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差很少,字符串的运算更方便,下面是常见的例子:
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'\n\t\\' print(e) # '\\n\\t\\\\'
须要提一下的几点:1)字符串用双引号和单引号均可以,区别主要是单引号字符串中若是出现单引号字符则须要用转义符,双引号也是同样,因此在单引号字符串中使用双引号,或者双引号字符串中使用单引号就会比较方便。另外三个双引号或者三个单引号围起来的也是字符串,由于换行方便,更多用于文档。2)Python2中两个数值相除会根据数值类型判断是否整数除法,Python3种则都按照浮点数。想要在Python2种也执行Python3中的除法只要执行下面语句:
from __future__ import division # 使用Python3中的除法 1 / 2 # 0.5
3)字符串前加r表示字符串内容严格按照输入的样子,好处是不用转义符了,很是方便。
Python中的布尔值和逻辑的运算很是直接,下面是例子:
a = True b = False a and b # False a or b # True not a # False
基本上就是英语,操做符优先级之类的和其余语言相似。Python中也有位操做:
~8 # 按位翻转,1000 --> -(1000+1) 8 >> 3 # 右移3位,1000 --> 0001 1 << 3 # 左移3位,0001 --> 1000 5 & 2 # 按位与,101 & 010 = 000 5 | 2 # 按位或,101 | 010 = 111 4 ^ 1 # 按位异或,100 ^ 001 = 101
==, !=和is
判断是否相等或者不等的语法和C也同样,另外在Python中也经常见到is操做符,这二者的区别在于==和!=比较引用指向的内存中的内容,而is判断两个变量是否指向一个地址,看下面的代码例子:
a = 1 b = 1.0 c = 1 a == b # True,值相等 a is b # False,指向的不是一个对象,这个语句等效于 id(a) == id(b) a is c # True,指向的都是整型值1
因此必定要分清要比较的对象应该用那种方式,对于一些特殊的状况,好比None,本着Pythonic的原则,最好用is None。
注意关键字
Python中,万物皆对象。不过这并非这里要探讨的话题,想说的是必定要注意关键字,由于全部东西都是对象,因此一个简简单单的赋值操做就能够把系统内置的函数给变成一个普通变量,来看下边例子:
id(type) # 506070640L type = 1 # type成了指向1的变量 id(type) # 35556792L id = 2 # id成了指向2的变量 from __future__ import print_function print = 3 # print成了指向3的变量
注意print是个很特殊的存在,在Python3中是按照函数用,在Python2中倒是个命令式的语句,最先print的用法实际上是下边这样:
print "Hello world!"
这么用主要是受到ABC语法的影响,但这个用法并不Pythonic,后来加入了print函数,为了兼容容许两种用法并存。因此单纯给print赋值是不灵的,在Python2中使用Python3中的一些特性都是用from __future__ import来实现。
模块导入
由于提到了对象名覆盖和import,因此简单讲一下。import是利用Python中各类强大库的基础,好比要计算cos(π)的值,能够有下面4种方式:
# 直接导入Python的内置基础数学库 import math print(math.cos(math.pi)) # 从math中导入cos函数和pi变量 from math import cos, pi print(cos(pi)) # 若是是个模块,在导入的时候能够起个别名,避免名字冲突或是方便懒得打字的人使用 import math as m print(m.cos(m.pi)) # 从math中导入全部东西 from math import * print(cos(pi))
通常来讲最后一种方式不是很推荐,由于不知道import导入的名字里是否和现有对象名已经有冲突,极可能会不知不觉覆盖了现有的对象。
列表
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()初始化,输入参数须要是一个能够遍历的结构,其中每个元素会做为列表的一项。“*”操做符对于列表而言是复制,最后一个语句用这种办法生成了分段的列表。
列表的基本操做有访问,增长,删除,和拼接:
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]
由于列表是有顺序的,因此和顺序相关的操做是列表中最多见的,首先咱们来打乱一个列表的顺序,而后再对这个列表排序:
import random
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) b = tuple(['3', 4]) # 也能够从列表初始化 c = (5,) print(c) # (5,) d = (6) print(d) # 6 e = 3, 4, 5 print(e) # (3, 4, 5)
集合
集合是一种颇有用的数学操做,好比列表去重,或是理清两组数据之间的关系,集合的操做符和位操做符有交集,注意不要弄混:
A = set([1, 2, 3, 4]) B = {3, 4, 5, 6} C = set([1, 1, 2, 2, 2, 3, 3, 3, 3]) print(C) # 集合的去重效果,set([1, 2, 3]) print(A | B) # 求并集,set([1, 2, 3, 4, 5, 6]) print(A & B) # 求交集,set([3, 4]) print(A - B) # 求差集,属于A但不属于B的,set([1, 2]) print(B - A) # 求差集,属于B但不属于A的,set([5, 6]) print(A ^ B) # 求对称差集,至关于(A-B)|(B-A),set([1, 2, 5, 6])
字典
字典是一种很是常见的“键-值”(key-value)映射结构,键无重复,一个键不能对应多个值,不过多个键能够指向一个值。仍是经过例子来了解,构建一个名字->年龄的字典,并执行一些常见操做:
a = {'Tom': 8, 'Jerry': 7} print(a['Tom']) # 8 b = dict(Tom=8, Jerry=7) # 一种字符串做为键更方便的初始化方式 print(b['Tom']) # 8 if 'Jerry' in a: # 判断'Jerry'是否在keys里面 print(a['Jerry']) # 7 print(a.get('Spike')) # None,经过get得到值,即便键不存在也不会报异常 a['Spike'] = 10 a['Tyke'] = 3 a.update({'Tuffy': 2, 'Mammy Two Shoes': 42}) print(a.values()) # dict_values([8, 2, 3, 7, 10, 42]) print(a.pop('Mammy Two Shoes')) # 移除'Mammy Two Shoes'的键值对,并返回42 print(a.keys()) # dict_keys(['Tom', 'Tuffy', 'Tyke', 'Jerry', 'Spike'])
注意到初始化字典和集合很像,的确如此,集合就像是没有值只有键的字典。既然有了人名到年龄的映射,也许你立马想到是否能够给字典排序?在Python3.6以前,这个问题是错误的,字典是一种映射关系,没有顺序。固然了,若是要把(键, 值)的这种对进行排序,是没有问题的,前提是先把字典转化成可排序的结构,items()或者iteritems()能够作到这件事,接上段代码继续:
b = a.items() print(b) # [('Tuffy', 2), ('Spike', 10), ('Tom', 8), ('Tyke', 3), ('Jerry', 7)] from operator import itemgetter c = sorted(a.items(), key=itemgetter(1)) print(c) # [('Tuffy', 2), ('Tyke', 3), ('Jerry', 7), ('Tom', 8), ('Spike', 10)] d = sorted(a.iteritems(), key=itemgetter(1)) print(d) # [('Tuffy', 2), ('Tyke', 3), ('Jerry', 7), ('Tom', 8), ('Spike', 10)] e = sorted(a) print(e) # 只对键排序,['Jerry', 'Spike', 'Tom', 'Tuffy', 'Tyke']
items()能够把字典中的键值对转化成一个列表,其中每一个元素是一个tuple,tuple的第一个元素是键,第二个元素是值。变量c是按照值排序,因此须要一个操做符itemgetter,去位置为1的元素做为排序参考,若是直接对字典排序,则其实至关于只是对键排序。字典被看成一个普通的可遍历结构使用时,都至关于遍历字典的键。若是以为字典没有顺序不方便,能够考虑使用OrderedDict,使用方式以下:
from collections import OrderedDict a = {1: 2, 3: 4, 5: 6, 7: 8, 9: 10} b = OrderedDict({1: 2, 3: 4, 5: 6, 7: 8, 9: 10}) print(a) # {1: 2, 3: 4, 9: 10, 5: 6, 7: 8} print(b) # OrderedDict([(1, 2), (3, 4), (9, 10), (5, 6), (7, 8)])
这样初始化时的顺序就保留了,除了有序的特性之外,用法上和字典没有区别。2016年9月,Guido宣布在Python3.6中,字典将默认有序,这样就不用纠结了。另外须要注意的一点是字典是经过哈希表实现的,因此键必须是可哈希的, list不能被哈希,因此也不能做为字典的键,而tuple就能够。
由于上上段代码中用到了iteritems(),因此这里顺带提一下迭代器(iterator),迭代器至关于一个函数,每次调用都返回下一个元素,从遍历的角度来看就和列表没有区别了。iteritems()就是一个迭代器,因此效果同样,区别是迭代器占用更少内存,由于不须要一上来就生成整个列表。通常来讲,若是只须要遍历一次,用迭代器是更好的选择,如果要屡次频繁从一个可遍历结构中取值,且内存够,则直接生成整个列表会更好。固然,用迭代器生成一个完整列表并不麻烦,因此有个趋势是把迭代器做为默认的可遍历方式,好比前面咱们使用过用来生成等差数列列表的range(),在Python2中对应的迭代器形式是xrange()。在Python3中,range()就再也不产生一个列表了,而是做为迭代器,xrange()直接没了。
从这节开始,代码就未必适合在Python终端中输入了,选个顺手的编辑器或者IDE。做者良心推荐PyCharm,虽然慢,但好用,社区版免费:
for循环
上面提到的4种容器类型都是可遍历的,因此该讲讲用来遍历的for循环了。for循环的语法也是简单的英语:
a = ['This', 'is', 'a', 'list', '!'] b = ['This', 'is', 'a', 'tuple', '!'] c = {'This': 'is', 'an': 'unordered', 'dict': '!'} # 依次输出:'This', 'is', 'a', 'list', '!' for x in a: print(x) # 依次输出:'This', 'is', 'a', 'tuple', '!' for x in b: print(x) # 键的遍历。不依次输出:'This', 'dict', 'an' for key in c: print(key) # 依次输出0到9 for i in range(10): print(i)
注意到每一个for循环中,print都有缩进,这是Python中一个让人爱恨交织的特色:强行缩进来代表成块的代码。这样作的好处是代码十分清晰工整,还有助于防止写出过长的函数或者过深的嵌套,坏处是有时候不知为何tab和空格就一块儿出现了,又或是多重if-else不知怎得就没对齐,仍是挺麻烦的。
回到for循环上,这种把每一个元素拿出来的遍历方式叫作for_each风格,熟悉Java的话就不会陌生,C++11中也开始支持这种for循环方式。不过若是仍是须要下标呢?好比遍历一个list的时候,但愿把对应下标也打印出来,这时能够用enumerate:
names = ["Rick", "Daryl", "Glenn"] # 依次输出下标和名字 for i, name in enumerate(names): print(i, name)
须要注意的是,经过取下标遍历固然是可行的,好比用len()函数得到列表长度,而后用range()/xrange()函数得到下标,可是并不推荐这样作:
words = ["This", "is", "not", "recommended"] # not pythonic :( for i in xrange(len(words)): print(words[i])
在使用for循环时,有时会遇到这样一种场景:咱们须要对遍历的每一个元素进行某种判断,若是符合这种判断的状况没有发生,则执行一个操做。举个例子某神秘部门要审核一个字符串列表,若是没有发现不和谐的字眼,则将内容放心经过,一种解决办法是下面这样:
wusuowei = ["I", "don't", "give", "a", "shit"] # 无所谓 hexie = True # 默认和谐社会 for x in wusuowei: if x == "f**k": print("What the f**k!") # 发现了不应出现的东西,WTF! hexie = False # 不和谐了 break # 赶忙停下!不能再唱了 if hexie: # 未发现不和谐元素! print("Harmonious society!") # 和谐社会!
这样须要设置一个标记是否发现不和谐因素的状态变量hexie,循环结束后再根据这个变量判断内容是否能够放心经过。一种更简洁不过有些小众的作法是直接和else一块儿,若是for循环中的if块内的语句没有被触发,则经过else执行指定操做:
wusuowei = ["I", "don't", "give", "a", "shit"] for x in wusuowei: if x == "f**k": print("What the f**k!") hexie = False break else: # for循环中if内语句未被触发 print("Harmonious society!") # 和谐社会!
这样不须要一个标记是否和谐的状态变量,语句简洁了不少。
if和分支结构
上一个例子中已经出现if语句了,因此这部分讲讲if。Python的条件控制主要是三个关键字:if-elif-else,其中elif就是else if的意思。仍是看例子:
pets =['dog', 'cat', 'droid', 'fly'] for pet in pets: if pet == 'dog': # 狗粮 food = 'steak' # 牛排 elif pet == 'cat': # 猫粮 food = 'milk' # 牛奶 elif pet == 'droid': # 机器人 food = 'oil' # 机油 elif pet == 'fly': # 苍蝇 food = 'sh*t' # else: pass print(food)
须要提一下的是pass,这就是个空语句,什么也不作,占位用。Python并无switch-case的语法,等效的用法要么是像上面同样用if-elif-else的组合,要么能够考虑字典:
pets = ['dog', 'cat', 'droid', 'fly'] food_for_pet = { 'dog': 'steak', 'cat': 'milk', 'droid': 'oil', 'fly': 'sh*t' } for pet in pets: food = food_for_pet[pet] if pet in food_for_pet else None print(food)
这里还用到了一个if-else常见的行内应用,就是代替三元操做符,若是键在字典中,则food取字典的对应值,不然为None。
if表达式中的小技巧
经过链式比较让语句简洁:
if -1 < x < 1: # 相较于 if x > -1 and x < 1: print('The absolute value of x is < 1')
判断一个值是否是等于多个可能性中的一个:
if x in ['piano', 'violin', 'drum']: # 相较于 if x == 'piano' or x == 'violin' or x =='drum': print("It's an instrument!")
Python中的对象都会关联一个真值,因此在if表达式中判断是否为False或者是否为空的时候,是无需写出明确的表达式的:
a = True if a: # 判断是否为真,相较于 a is True print('a is True') if 'sky': # 判断是否空字符串,相较于 len('sky') > 0 print('birds') if '': # 判断是否空字符串,同上 print('Nothing!') if {}: # 判断是否空的容器(字典),相较于len({}) > 0 print('Nothing!')
隐式表达式为False的是以下情况:
- None
- False
- 数值0
- 空的容器或序列(字符串也是一种序列)
- 用户自定义类中,若是定义了__len__()或者__nonzero__(),而且被调用后返回0或者False
while循环
while的就是循环和if的综合体,是一种单纯的基于条件的循环,自己没有遍历的意思,这是和for_each的本质差异,这种区别比起C/C++中要明确得多,用法以下:
i = 0
while i < 100: # 笑100遍
print("ha")
while True: # 一直笑
print("ha")
仍是从几个例子看起:
def say_hello(): print('Hello!') def greetings(x='Good morning!'): print(x) say_hello() # Hello! greetings() # Good morning! greetings("What's up!") # What's up! a = greetings() # 返回值是None def create_a_list(x, y=2, z=3): # 默认参数项必须放后面 return [x, y, z] b = create_a_list(1) # [1, 2, 3] c = create_a_list(3, 3) # [3, 3, 3] d = create_a_list(6, 7, 8) # [6, 7, 8] def traverse_args(*args): for arg in args: print(arg) traverse_args(1, 2, 3) # 依次打印1, 2, 3 traverse_args('A', 'B', 'C', 'D') # 依次打印A, B, C, D def traverse_kargs(**kwargs): for k, v in kwargs.items(): print(k, v) traverse_kargs(x=3, y=4, z=5) # 依次打印('x', 3), ('y', 4), ('z', 5) traverse_kargs(fighter1='Fedor', fighter2='Randleman') def foo(x, y, *args, **kwargs): print(x, y) print(args) print(kwargs) # 第一个pring输出(1, 2) # 第二个print输出(3, 4, 5) # 第三个print输出{'a': 3, 'b': 'bar'} foo(1, 2, 3, 4, 5, a=6, b='bar')
其实和不少语言差很少,括号里面定义参数,参数能够有默认值,且默认值不能在无默认值参数以前。Python中的返回值用return定义,若是没有定义返回值,默认返回值是None。参数的定义能够很是灵活,能够有定义好的固定参数,也能够有可变长的参数(args: arguments)和关键字参数(kargs: keyword arguments)。若是要把这些参数都混用,则固定参数在最前,关键字参数在最后。
Python中万物皆对象,因此一些状况下函数也能够当成一个变量似的使用。好比前面小节中提到的用字典代替switch-case的用法,有的时候咱们要执行的不是经过条件判断获得对应的变量,而是执行某个动做,好比有个小机器人在坐标(0, 0)处,咱们用不一样的动做控制小机器人移动:
moves = ['up', 'left', 'down', 'right'] coord = [0, 0] for move in moves: if move == 'up': # 向上,纵坐标+1 coord[1] += 1 elif move == 'down': # 向下,纵坐标-1 coord[1] -= 1 elif move == 'left': # 向左,横坐标-1 coord[0] -= 1 elif move == 'right': # 向右,横坐标+1 coord[0] += 1 else: pass print(coord) # 打印当前位置坐标
不一样条件下对应的是对坐标这个列表中的值的操做,单纯的从字典取值就办不到了,因此就把函数做为字典的值,而后用这个获得的值执行相应动做:
moves = ['up', 'left', 'down', 'right'] def move_up(x): # 定义向上的操做 x[1] += 1 def move_down(x): # 定义向下的操做 x[1] -= 1 def move_left(x): # 定义向左的操做 x[0] -= 1 def move_right(x): # 定义向右的操做 x[0] += 1 # 动做和执行的函数关联起来,函数做为键对应的值 actions = { 'up': move_up, 'down': move_down, 'left': move_left, 'right': move_right } coord = [0, 0] for move in moves: actions[move](coord) print(coord)
把函数做为值取到后,直接加一括号就能使了,这样作以后起码在循环部分看上去很简洁。有点C里边函数指针的意思,只不过更简单。其实这种用法在以前讲排序的时候咱们已经见过了,就是operator中的itemgetter。itemgetter(1)获得的是一个可调用对象(callable object),和返回下标为1的元素的函数用起来是同样的:
def get_val_at_pos_1(x): return x[1] heros = [ ('Superman', 99), ('Batman', 100), ('Joker', 85) ] sorted_pairs0 = sorted(heros, key=get_val_at_pos_1) sorted_pairs1 = sorted(heros, key=lambda x: x[1]) print(sorted_pairs0) print(sorted_pairs1)
在这个例子中咱们用到了一种特殊的函数:lambda表达式。Lambda表达式在Python中是一种匿名函数,lambda关键字后面跟输入参数,而后冒号后面是返回值(的表达式),好比上边例子中就是一个取下标1元素的函数。固然,仍是那句话,万物皆对象,给lambda表达式取名字也是一点问题没有的:
some_ops = lambda x, y: x + y + x*y + x**y some_ops(2, 3) # 2 + 3 + 2*3 + 2^3 = 19
生成器(Generator)
生成器是迭代器的一种,形式上看和函数很像,只是把return换成了yield,在每次调用的时候,都会执行到yield并返回值,同时将当前状态保存,等待下次执行到yield再继续:
# 从10倒数到0 def countdown(x): while x >= 0: yield x x -= 1 for i in countdown(10): print(i) # 打印小于100的斐波那契数 def fibonacci(n): a = 0 b = 1 while b < n: yield b a, b = b, a + b for x in fibonacci(100): print(x)
生成器和全部可迭代结构同样,能够经过next()函数返回下一个值,若是迭代结束了则抛出StopIteration异常:
a = fibonacci(3) print(next(a)) # 1 print(next(a)) # 1 print(next(a)) # 2 print(next(a)) # 抛出StopIteration异常
Python3.3以上能够容许yield和return同时使用,return的是异常的说明信息:
# Python3.3以上能够return返回异常的说明 def another_fibonacci(n): a = 0 b = 1 while b < n: yield b a, b = b, a + b return "No more ..." a = another_fibonacci(3) print(next(a)) # 1 print(next(a)) # 1 print(next(a)) # 2 print(next(a)) # 抛出StopIteration异常并打印No more消息
类(Class)
Python中的类的概念和其余语言相比没什么不一样,比较特殊的是protected和private在Python中是没有明确限制的,一个惯例是用单下划线开头的表示protected,用双下划线开头的表示private:
class A: """Class A""" def __init__(self, x, y, name): self.x = x self.y = y self._name = name def introduce(self): print(self._name) def greeting(self): print("What's up!") def __l2norm(self): return self.x**2 + self.y**2 def cal_l2norm(self): return self.__l2norm() a = A(11, 11, 'Leonardo') print(A.__doc__) # "Class A" a.introduce() # "Leonardo" a.greeting() # "What's up!" print(a._name) # 能够正常访问 print(a.cal_l2norm()) # 输出11*11+11*11=242 print(a._A__l2norm()) # 仍然能够访问,只是名字不同 print(a.__l2norm()) # 报错: 'A' object has no attribute '__l2norm'
类的初始化使用的是__init__(self,),全部成员变量都是self的,因此以self.开头。能够看到,单下划线开头的变量是能够直接访问的,而双下划线开头的变量则触发了Python中一种叫作name mangling的机制,其实就是名字变了下,仍然能够经过前边加上“_类名”的方式访问。也就是说Python中变量的访问权限都是靠自觉的。类定义中紧跟着类名字下一行的字符串叫作docstring,能够写一些用于描述类的介绍,若是有定义则经过“类名.__doc__”访问。这种先后都加双下划线访问的是特殊的变量/方法,除了__doc__和__init__还有不少,这里就不展开讲了。
Python中的继承也很是简单,最基本的继承方式就是定义类的时候把父类往括号里一放就好了:
class B(A): """Class B inheritenced from A""" def greeting(self): print("How's going!") b = B(12, 12, 'Flaubert') b.introduce() # Flaubert b.greeting() # How's going! print(b._name()) # Flaubert print(b._A__l2norm()) # “私有”方法,必须经过_A__l2norm访问
map能够用于对可遍历结构的每一个元素执行一样的操做,批量操做:
map(lambda x: x**2, [1, 2, 3, 4]) # [1, 4, 9, 16] map(lambda x, y: x + y, [1, 2, 3], [5, 6, 7]) # [6, 8, 10]
reduce则是对可遍历结构的元素按顺序进行两个输入参数的操做,而且每次的结果保存做为下次操做的第一个输入参数,尚未遍历的元素做为第二个输入参数。这样的结果就是把一串可遍历的值,减小(reduce)成一个对象:
reduce(lambda x, y: x + y, [1, 2, 3, 4]) # ((1+2)+3)+4=10
filter顾名思义,根据条件对可遍历结构进行筛选:
filter(lambda x: x % 2, [1, 2, 3, 4, 5]) # 筛选奇数,[1, 3, 5]
须要注意的是,对于filter和map,在Python2中返回结果是列表,Python3中是生成器。
列表生成是Python2.0中加入的一种语法,能够很是方便地用来生成列表和迭代器,好比上节中map的两个例子和filter的一个例子能够用列表生成重写为:
[x**2 for x in [1, 2, 3, 4]] # [1, 4, 9 16] [sum(x) for x in zip([1, 2, 3], [5, 6, 7])] # [6, 8, 10] [x for x in [1, 2, 3, 4, 5] if x % 2] # [1, 3, 5]
zip()函数能够把多个列表关联起来,这个例子中,经过zip()能够按顺序同时输出两个列表对应位置的元素对。有一点须要注意的是,zip()不会自动帮助判断两个列表是否长度同样,因此最终的结果会以短的列表为准,想要以长的列表为准的话能够考虑itertools模块中的izip_longest()。若是要生成迭代器只须要把方括号换成括号,生成字典也很是容易:
iter_odd = (x for x in [1, 2, 3, 4, 5] if x % 2) print(type(iter_odd)) # <type 'generator'> square_dict = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
至于列表生成和map/filter应该优先用哪一种,这个问题很难回答,不过Python创始人Guido彷佛不喜欢map/filter/reduce,他曾在表示过一些从函数式编程里拿来的特性是个错误。
Python中字符串相关的处理都很是方便,来看例子:
a = 'Life is short, you need Python' a.lower() # 'life is short, you need Python' a.upper() # 'LIFE IS SHORT, YOU NEED PYTHON' a.count('i') # 2 a.find('e') # 从左向右查找'e',3 a.rfind('need') # 从右向左查找'need',19 a.replace('you', 'I') # 'Life is short, I need Python' tokens = a.split() # ['Life', 'is', 'short,', 'you', 'need', 'Python'] b = ' '.join(tokens) # 用指定分隔符按顺序把字符串列表组合成新字符串 c = a + '\n' # 加了换行符,注意+用法是字符串做为序列的用法 c.rstrip() # 右侧去除换行符 [x for x in a] # 遍历每一个字符并生成由全部字符按顺序构成的列表 'Python' in a # True
Python2.6中引入了format进行字符串格式化,相比在字符串中用%的相似C的方式,更增强大方便:
a = 'I’m like a {} chasing {}.' # 按顺序格式化字符串,'I’m like a dog chasing cars.' a.format('dog', 'cars') # 在大括号中指定参数所在位置 b = 'I prefer {1} {0} to {2} {0}' b.format('food', 'Chinese', 'American') # >表明右对齐,>前是要填充的字符,依次输出: # 000001 # 000019 # 000256 for i in [1, 19, 256]: print('The index is {:0>6d}'.format(i)) # <表明左对齐,依次输出: # *--------- # ****------ # *******--- for x in ['*', '****', '*******']: progress_bar = '{:-<10}'.format(x) print(progress_bar) for x in [0.0001, 1e17, 3e-18]: print('{:.6f}'.format(x)) # 按照小数点后6位的浮点数格式 print('{:.1e}'.format(x)) # 按照小数点后1位的科学记数法格式 print ('{:g}'.format(x)) # 系统自动选择最合适的格式 template = '{name} is {age} years old.' c = template.format(name='Tom', age=8)) # Tom is 8 years old. d = template.format(age=7, name='Jerry')# Jerry is 7 years old.
format在生成字符串和文档的时候很是有用,更多更详细的用法能够参考Python官网:
7.1. string - Common string operations - Python 2.7.13 documentation
在Python中,推荐用上下文管理器(with-as)来打开文件,IO资源的管理更加安全,并且不用老惦记着给文件执行close()函数。仍是举例子来讲明,考虑有个文件name_age.txt,里面存储着名字和年龄的关系,格式以下:
Tom,8
Jerry,7
Tyke,3
...
读取文件内容并所有显示:
with open('name_age.txt', 'r') as f: # 打开文件,读取模式 lines = f.readlines() # 一次读取全部行 for line in lines: # 按行格式化并显示信息 name, age = line.rstrip().split(',') print('{} is {} years old.'.format(name, age))
open()的第一个参数是文件名,第二个参数是模式。文件的模式通常有四种,读取(r),写入(w),追加(a)和读写(r+)。若是但愿按照二进制数据读取,则将文件模式和b一块儿使用(wb, r+b…)。
再考虑一个场景,要读取文件内容,并把年龄和名字的顺序交换存成新文件age_name.txt,这时能够同时打开两个文件:
with open('name_age.txt', 'r') as fread, open('age_name.txt', 'w') as fwrite: line = fread.readline() while line: name, age = line.rstrip().split(',') fwrite.write('{},{}\n'.format(age, name)) line = fread.readline()
有的时候咱们进行文件操做是但愿把对象进行序列化,那么能够考虑用pickle模块:
import pickle lines = [ "I'm like a dog chasing cars.", "I wouldn't know what to do if I caught one...", "I'd just do things." ] with open('lines.pkl', 'wb') as f: # 序列化并保存成文件 pickle.dump(lines, f) with open('lines.pkl', 'rb') as f: # 从文件读取并反序列化 lines_back = pickle.load(f) print(lines_back) # 和lines同样
注意到,序列化的时候就得使用b模式了。Python2中有个效率更高的pickle叫cPickle,用法和pickle同样,在Python3中就只有一个pickle。
相比起其余一些语言,在Python中咱们能够更大胆地使用异常,由于异常在Python中是很是常见的存在,好比下面这种简单的遍历:
a = ['Why', 'so', 'serious', '?'] for x in a: print(x)
当用for进行遍历时,会对要遍历的对象调用iter()。这须要给对象建立一个迭代器用来依次返回对象中的内容。为了能成功调用iter(),该对象要么得支持迭代协议(定义__iter__()),要么得支持序列协议(定义__getitem__())。当遍历结束时,__iter__()或者__getitem__()都须要抛出一个异常。__iter__()会抛出StopIteration,而__getitem__()会抛出IndexError,因而遍历就会中止。
在深度学习中,尤为是数据准备阶段,经常遇到IO操做。这时候遇到异常的可能性很高,采用异常处理能够保证数据处理的过程不被中断,并对有异常的状况进行记录或其余动做:
for filepath in filelist: # filelist中是文件路径的列表 try: with open(filepath, 'r') as f: # 执行数据处理的相关工做 ... print('{} is processed!'.format(filepath)) except IOError: print('{} with IOError!'.format(filepath)) # 异常的相应处理 ...
深度学习中对数据高效处理经常会须要并行,这时多进程就派上了用场。考虑这样一个场景,在数据准备阶段,有不少文件须要运行必定的预处理,正好有台多核服务器,咱们但愿把这些文件分红32份,并行处理:
from multiprocessing import Process#, freeze_support def process_data(filelist): for filepath in filelist: print('Processing {} ...'.format(filepath)) # 处理数据 ... if __name__ == '__main__': # 若是是在Windows下,还须要加上freeze_support() #freeze_support() # full_list包含了要处理的所有文件列表 ... n_total = len(full_list) # 一个远大于32的数 n_processes = 32 # 每段子列表的平均长度 length = float(n_total) / float(n_processes) # 计算下标,尽量均匀地划分输入文件列表 indices = [int(round(i*length)) for i in range(n_processes+1)] # 生成每一个进程要处理的子文件列表 sublists = [full_list[indices[i]:indices[i+1]] for i in range(n_processes)] # 生成进程 processes = [Process(target=process_data, args=(x,)) for x in sublists] # 并行处理 for p in processes: p.start() for p in processes: p.join()
其中if __name__ == '__main__'用来标明在import时不包含,可是做为文件执行时运行的语句块。为何不用多线程呢?简单说就是Python中线程的并发没法有效利用多核,若是有兴趣的读者能够从下面这个连接看起:
GlobalInterpreterLock - Python Wiki
深度学习中的数据可能是文件,因此数据处理阶段和文件相关的操做就很是重要。除了文件IO,Python中一些操做系统的相关功能也可以很是方便地帮助数据处理。想象一下咱们有一个文件夹叫作data,下边有3个子文件夹叫作cat,dog和bat,里面分别是猫,狗和蝙蝠的照片。为了训练一个三分类模型,咱们先要生成一个文件,里面每一行是文件的路径和对应的标签。定义cat是0,dog是1,bat是2,则能够经过以下脚本:
import os # 定义文件夹名称和标签的对应关系 label_map = { 'cat': 0, 'dog': 1, 'bat': 2 } with open('data.txt', 'w') as f: # 遍历全部文件,root为当前文件夹,dirs是全部子文件夹名,files是全部文件名 for root, dirs, files in os.walk('data'): for filename in files: filepath = os.sep.join([root, filename]) # 得到文件完整路径 dirname = root.split(os.sep)[-1] # 获取当前文件夹名称 label = label_map[dirname] # 获得标签 line = '{},{}\n'.format(filepath, label) f.write(line)
其中,os.sep是当前操做系统的路径分隔符,在Unix/Linux中是'/',Windows中是'\\'。有的时候咱们已经有了全部的文件在一个文件夹data下,但愿获取全部文件的名称,则能够用os.listdir():
filenames = os.listdir('data')
os也提供了诸如拷贝,移动和修改文件名等操做。同时由于大部分深度学习框架最多见的都是在Unix/Linux下使用,而且Unix/Linux的shell已经很是强大(比Windows好用太多),因此只须要用字符串格式化等方式生成shell命令的字符串,而后经过os.system()就能方便实现不少功能,有时比os,还有Python中另外一个操做系统相关模块shutil还要方便:
import os, shutil filepath0 = 'data/bat/IMG_000001.jpg' filepath1 = 'data/bat/IMG_000000.jpg' # 修改文件名 os.system('mv {} {}'.format(filepath0, filepath1)) #os.rename(filepath0, filepath1) # 建立文件夹 dirname = 'data_samples' os.system('mkdir -p {}'.format(dirname)) #if not os.path.exists(dirname): # os.mkdir(dirname) # 拷贝文件 os.system('cp {} {}'.format(filepath1, dirname)) #shutil.copy(filepath1, dirname)