2023年7月10日发(作者:)
⼈⽣苦短,我⽤Python(Python快速教程-基础篇)Life is short, you need Python⼈⽣苦短,我⽤Python-- Bruce Eckel5.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),具体参见:或者在Python中执⾏:>> import thisPython拥有很好的扩充性,可以⾮常轻松地⽤其他语⾔编写模块供调⽤,⽤Python编写的模块也可以通过各种⽅式轻松被其他语⾔调⽤。所以⼀种常见的Python使⽤⽅式是,底层复杂且对效率要求⾼的模块⽤C/C++等语⾔实现,顶层调⽤的API⽤Python封装,这样可以通过简单的语法实现顶层逻辑,故⽽Python⼜被称为“胶⽔语⾔”。这种特性的好处是,⽆需花费很多时间在编程实现上,更多的时间可以专注于思考问题的逻辑。尤其是对做算法和深度学习的从业⼈员,这种⽅式是⾮常理想的,所以如今的深度学习框架中,除了MATLAB,或是Deeplearning4j这种摆明了给Java⽤的,其他框架基本上要么官⽅接⼝就是Python,要么⽀持Python接⼝。5.1.2 安装和使⽤PythonPython有两个⼤版本,考虑到⽤户群数量和库的各种框架的兼容性,本⽂以Python2(2.7)为准,语法尽量考虑和Python3的兼容。Unix/Linux下的Python基本都是系统⾃带的,⼀般默认为Python2,使⽤时在终端直接键⼊python就能进⼊Python解释器界⾯:在解释器下就已经可以进⾏最基本的编程了,⽐如:写程序的话还是需要保存成⽂件再执⾏,⽐如我们写下⾯语句,并且保存为:print("Hello world!")然后在终端⾥执⾏:安装更多的python库⼀般有两种⽅法,第⼀是⽤系统的软件包管理,以Ubuntu 16.04 LTS为例,⽐如想要安装numpy库(后⾯会介绍这个库),软件包的名字就是python-numpy,所以在终端中输⼊:>> sudo apt install python-numpyPython⾃⼰也带了包管理器,叫做pip,使⽤如下:>> pip install numpy安装和深度学习相关的框架时,⼀般来说推荐使⽤系统⾃带的包管理,出现版本错误的可能性低⼀些。另外也可以使⽤⼀些提前配置好很多第三⽅库的Python包,这些包通常已经包含了深度学习框架中绝⼤多数的依赖库,⽐如最常⽤的是Anaconda:Windows下的Python安装简单⼀些,从官⽅⽹站下载相应的安装程序就可以了,当然也有更⽅便的已经包含了很全的第三⽅库的选择,WinPython:并且是绿⾊的,直接执⾏就可以⽤了。5.2 Python基本语法There should be one-- and preferably only one --obvious way to do it.对于⼀个特定的问题,应该只⽤最好的⼀种⽅法来解决。-- Tim Peters5.2.1 基本数据类型和运算基本数据类型Python中最基本的数据类型包括整型,浮点数,布尔值和字符串。类型是不需要声明的,⽐如:a = 1 #
整数b = 1.2 #
浮点数c = True #
布尔类型d = "False" #
字符串e = None # NoneType其中#是⾏内注释的意思。最后⼀个None是NoneType,注意不是0,在Python中利⽤type函数可以查看⼀个变量的类型:type(a) #
c = 3a + b # 2 + 2.3 = 4.3c – a # 3 - 2 = 1a / b #
整数除以浮点数,运算以浮点数为准,2 / 2.3 = 0.8695652173913044a / c # Python2中,整数除法,向下取整 2 / 3 = 0a ** c # a的c次⽅,结果为8a += 1 # Python中没有i++的⽤法,⾃增⽤+=c -= 3 # c变成0了d = 'Hello'
d + ' world!' #
相当于字符串拼接,结果为'Hello world!'d += ' "world"!'#
相当于把字符串接在当前字符串尾,d变为'Hello "world"!'e = r'nt'
print(e) # 'nt'需要提⼀下的⼏点:1)字符串⽤双引号和单引号都可以,区别主要是单引号字符串中如果出现单引号字符则需要⽤转义符,双引号也是⼀样,所以在单引号字符串中使⽤双引号,或者双引号字符串中使⽤单引号就会⽐较⽅便。另外三个双引号或者三个单引号围起来的也是字符串,因为换⾏⽅便,更多⽤于⽂档。2)Python2中两个数值相除会根据数值类型判断是否整数除法,Python3中则都按照浮点数。想要在Python2种也执⾏Python3中的除法只要执⾏下⾯语句:from __future__ import division #
使⽤Python3中的除法1 / 2 # 0.53)字符串前加r表⽰字符串内容严格按照输⼊的样⼦,好处是不⽤转义符了,⾮常⽅便。Python中的布尔值和逻辑的运算⾮常直接,下⾯是例⼦:a = Trueb = Falsea and b # Falsea or b # Truenot a # False基本上就是英语,操作符优先级之类的和其他语⾔类似。Python中也有位操作:~8 #
按位翻转,1000 --> -(1000+1)8 >> 3 #
右移3位,1000 --> 00011 << 3 #
左移3位,0001 --> 10005 & 2 #
按位与,101 & 010 = 0005 | 2 #
按位或,101 | 010 = 1114 ^ 1 #
按位异或,100 ^ 001 = 101==, !=和is判断是否相等或者不等的语法和C也⼀样,另外在Python中也常常见到is操作符,这两者的区别在于==和!=⽐较引⽤指向的内存中的内容,⽽is判断两个变量是否指向⼀个地址,看下⾯的代码例⼦:a = 1b = 1.0c = 1a == b # True,值相等a is b # False,指向的不是⼀个对象,这个语句等效于 id(a) == id(b)a is c # True,指向的都是整型值1所以⼀定要分清要⽐较的对象应该⽤那种⽅式,对于⼀些特殊的情况,⽐如None,本着Pythonic的原则,最好⽤is None。注意关键字Python中,万物皆对象。不过这并不是这⾥要探讨的话题,想说的是⼀定要注意关键字,因为所有东西都是对象,所以⼀个简简单单的赋值操作就可以把系统内置的函数给变成⼀个普通变量,来看下边例⼦:id(type) # 506070640Ltype = 1 # type成了指向1的变量id(type) # 35556792Lid = 2 # id成了指向2的变量from __future__ import print_functionprint = 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 mathprint(())#
从math中导⼊cos函数和pi变量from math import cos, piprint(cos(pi))#
如果是个模块,在导⼊的时候可以起个别名,避免名字冲突或是⽅便懒得打字的⼈使⽤import math as mprint(())#
从math中导⼊所有东西from math import *print(cos(pi))⼀般来说最后⼀种⽅式不是很推荐,因为不知道import导⼊的名字⾥是否和现有对象名已经有冲突,很可能会不知不觉覆盖了现有的对象。5.2.2 容器列表
Python中的容器是异常好⽤且异常有⽤的结构。这节主要介绍列表(list),元组(tuple),字典(dict)和集合(set)。这些结构和其他语⾔中的类似结构并⽆本质不同,来看例⼦了解下使⽤:a = [1, 2, 3, 4]b = [1]c = [1]d = be = [1, "Hello world!", c, False]print(id(b), id(c)) # (194100040L, 194100552L)print(id(b), id(d)) # (194100040L, 194100040L)print(b == c) # Truef = 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()初始化,输⼊参数需要是⼀个可以遍历的结构,其中每⼀个元素会作为列表的⼀项。“*”操作符对于列表⽽⾔是复制,最后⼀个语句⽤这种办法⽣成了分段的列表。列表的基本操作有访问,增加,删除,和拼接:() #
把最后⼀个值4从列表中移除并作为pop的返回值(5) #
末尾插⼊值,[1, 2, 3, 5](2) #
找到第⼀个2所在的位置,也就是1a[2] #
取下标,也就是位置在2的值,也就是第三个值3a += [4, 3, 2] #
拼接,[1, 2, 3, 5, 4, 3, 2](1, 0) #
在下标为1处插⼊元素0,[1, 0, 2, 3, 5, 4, 3, 2](2) #
移除第⼀个2,[1, 0, 3, 5, 4, 3, 2]e() #
倒序,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()()print(a) #
列表内排序,a变为[0, 1, 1, 2, 2, 3, 3]因为列表是有顺序的,所以和顺序相关的操作是列表中最常见的,⾸先我们来打乱⼀个列表的顺序,然后再对这个列表排序:import randoma = range(10) # ⽣成⼀个列表,从0开始+1递增到9print(a) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]e(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) # 6e = 3, 4, 5print(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']) # 8b = dict(Tom=8, Jerry=7) #
⼀种字符串作为键更⽅便的初始化⽅式print(b['Tom']) # 8if 'Jerry' in a: #
判断'Jerry'是否在keys⾥⾯ print(a['Jerry']) # 7print(('Spike')) # None,通过get获得值,即使键不存在也不会报异常a['Spike'] = 10a['Tyke'] = ({'Tuffy': 2, 'Mammy Two Shoes': 42})print(()) # dict_values([8, 2, 3, 7, 10, 42])print(('Mammy Two Shoes')) #
移除'Mammy Two Shoes'的键值对,并返回42print(()) # dict_keys(['Tom', 'Tuffy', 'Tyke', 'Jerry', 'Spike'])
注意到初始化字典和集合很像,的确如此,集合就像是没有值只有键的字典。既然有了⼈名到年龄的映射,也许你⽴马想到是否可以给字典排序?在Python3.6之前,这个问题是错误的,字典是⼀种映射关系,没有顺序。当然了,如果要把(键, 值)的这种对进⾏排序,是没有问题的,前提是先把字典转化成可排序的结构,items()或者iteritems()可以做到这件事,接上段代码继续:b = ()print(b) # [('Tuffy', 2), ('Spike', 10), ('Tom', 8), ('Tyke', 3), ('Jerry', 7)]from operator import itemgetterc = sorted((), key=itemgetter(1))print(c) # [('Tuffy', 2), ('Tyke', 3), ('Jerry', 7), ('Tom', 8), ('Spike', 10)]d = sorted(ems(), 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 OrderedDicta = {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()直接没了。5.2.3 分⽀和循环从这节开始,代码就未必适合在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到9for 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 breakelse: # 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 = Trueif 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或者Falsewhile循环while的就是循环和if的综合体,是⼀种单纯的基于条件的循环,本⾝没有遍历的意思,这是和for_each的本质差别,这种区别⽐起C/C++中要明确得多,⽤法如下:i = 0while i < 100: # 笑100遍 print("ha")while True: # ⼀直笑 print("ha")5.2.4 函数、⽣成器和类还是从⼏个例⼦看起: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() #
返回值是Nonedef 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, 3traverse_args('A', 'B', 'C', 'D') #
依次打印A, B, C, Ddef traverse_kargs(**kwargs): for k, v in (): 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] += 1def move_down(x): #
定义向下的操作 x[1] -= 1def move_left(x): #
定义向左的操作 x[0] -= 1def 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**ysome_ops(2, 3) # 2 + 3 + 2*3 + 2^3 = 19⽣成器(Generator)⽣成器是迭代器的⼀种,形式上看和函数很像,只是把return换成了yield,在每次调⽤的时候,都会执⾏到yield并返回值,同时将当前状态保存,等待下次执⾏到yield再继续:#
从10倒数到0def countdown(x): while x >= 0: yield x x -= 1for i in countdown(10): print(i)#
打印⼩于100的斐波那契数def fibonacci(n): a = 0 b = 1 while b < n: yield b a, b = b, a + bfor x in fibonacci(100): print(x)⽣成器和所有可迭代结构⼀样,可以通过next()函数返回下⼀个值,如果迭代结束了则抛出StopIteration异常:a = fibonacci(3)print(next(a)) # 1print(next(a)) # 1print(next(a)) # 2print(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)) # 1print(next(a)) # 1print(next(a)) # 2print(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"uce() # "Leonardo"ng() # "What's up!"print(a._name) #
可以正常访问print(_l2norm()) #
输出11*11+11*11=242print(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')uce() # ng() # How's going!print(b._name()) # Flaubertprint(b._A__l2norm()) # “私有”⽅法,必须通过_A__l2norm访问5.2.5 map, reduce和filtermap可以⽤于对可遍历结构的每个元素执⾏同样的操作,批量操作: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=10filter顾名思义,根据条件对可遍历结构进⾏筛选:filter(lambda x: x % 2, [1, 2, 3, 4, 5]) #
筛选奇数,[1, 3, 5]需要注意的是,对于filter和map,在Python2中返回结果是列表,Python3中是⽣成器。5.2.6 列表⽣成(list comprehension)列表⽣成是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)) #
从左向右查找'e',('need') #
从右向左查找'need',e('you', 'I') # 'Life is short, I need Python'tokens = () # ['Life', 'is', 'short,', 'you', 'need', 'Python']b = ' '.join(tokens) #
⽤指定分隔符按顺序把字符串列表组合成新字符串c = a + 'n' #
加了换⾏符,注意+⽤法是字符串作为序列的⽤法() #
右侧去除换⾏符[x for x in a] #
遍历每个字符并⽣成由所有字符按顺序构成的列表'Python' in a # TruePython2.6中引⼊了format进⾏字符串格式化,相⽐在字符串中⽤%的类似C的⽅式,更加强⼤⽅便:a = 'I’m like a {} chasing {}.'#
按顺序格式化字符串,'I’m like a dog chasing cars.'('dog', 'cars')#
在⼤括号中指定参数所在位置b = 'I prefer {1} {0} to {2} {0}'('food', 'Chinese', 'American')# >代表右对齐,>前是要填充的字符,依次输出:# 000001# 000019# 000256for 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 = (name='Tom', age=8)) # Tom is 8 years old.d = (age=7, name='Jerry')# Jerry is 7 years 在⽣成字符串和⽂档的时候⾮常有⽤,更多更详细的⽤法可以参考Python官⽹:
5.2.8 ⽂件操作和pickle在Python中,推荐⽤上下⽂管理器(with-as)来打开⽂件,IO资源的管理更加安全,⽽且不⽤⽼惦记着给⽂件执⾏close()函数。还是举例⼦来说明,考虑有个⽂件name_,⾥⾯存储着名字和年龄的关系,格式如下:Tom,8Jerry,读取⽂件内容并全部显⽰:with open('name_', 'r') as f: #
打开⽂件,读取模式 lines = nes() #
⼀次读取所有⾏ for line in lines: #
按⾏格式化并显⽰信息 name, age = ().split(',') print('{} is {} years old.'.format(name, age))open()的第⼀个参数是⽂件名,第⼆个参数是模式。⽂件的模式⼀般有四种,读取(r),写⼊(w),追加(a)和读写(r+)。如果希望按照⼆进制数据读取,则将⽂件模式和b⼀起使⽤(wb, r+b…)。再考虑⼀个场景,要读取⽂件内容,并把年龄和名字的顺序交换存成新⽂件age_,这时可以同时打开两个⽂件:with open('name_', 'r') as fread, open('age_', 'w') as fwrite: line = ne() while line: name, age = ().split(',') ('{},{}n'.format(age, name)) line = ne()有的时候我们进⾏⽂件操作是希望把对象进⾏序列化,那么可以考虑⽤pickle模块:import picklelines = [ "I'm like a dog chasing cars.", "I wouldn't know what to do if I ", "I'd just do things."]with open('', 'wb') as f: #
序列化并保存成⽂件 (lines, f)with open('', 'rb') as f: #
从⽂件读取并反序列化 lines_back = (f)print(lines_back) #
和lines⼀样注意到,序列化的时候就得使⽤b模式了。Python2中有个效率更⾼的pickle叫cPickle,⽤法和pickle⼀样,在Python3中就只有⼀个pickle。5.2.9 异常相⽐起其他⼀些语⾔,在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)) #
异常的相应处理 ...5.2.10 多进程(multiprocessing)深度学习中对数据⾼效处理常常会需要并⾏,这时多进程就派上了⽤场。考虑这样⼀个场景,在数据准备阶段,有很多⽂件需要运⾏⼀定的预处理,正好有台多核服务器,我们希望把这些⽂件分成32份,并⾏处理:from multiprocessing import Process#, freeze_supportdef 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: () for p in processes: ()其中if __name__ == '__main__'⽤来标明在import时不包含,但是作为⽂件执⾏时运⾏的语句块。为什么不⽤多线程呢?简单说就是Python中线程的并发⽆法有效利⽤多核,如果有兴趣的读者可以从下⾯这个链接看起:
5.2.11 os模块深度学习中的数据多是⽂件,所以数据处理阶段和⽂件相关的操作就⾮常重要。除了⽂件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('', 'w') as f: #
遍历所有⽂件,root为当前⽂件夹,dirs是所有⼦⽂件夹名,files是所有⽂件名 for root, dirs, files in ('data'): for filename in files: filepath = ([root, filename]) #
获得⽂件完整路径 dirname = ()[-1] #
获取当前⽂件夹名称 label = label_map[dirname] #
得到标签 line = '{},{}n'.format(filepath, label) (line)其中,是当前操作系统的路径分隔符,在Unix/Linux中是'/',Windows中是''。有的时候我们已经有了所有的⽂件在⼀个⽂件夹data下,希望获取所有⽂件的名称,则可以⽤r():filenames = r('data')os也提供了诸如拷贝,移动和修改⽂件名等操作。同时因为⼤部分深度学习框架最常见的都是在Unix/Linux下使⽤,并且Unix/Linux的shell已经⾮常强⼤(⽐Windows好⽤太多),所以只需要⽤字符串格式化等⽅式⽣成shell命令的字符串,然后通过()就能⽅便实现很多功能,有时⽐os,还有Python中另⼀个操作系统相关模块shutil还要⽅便:import os, shutilfilepath0 = 'data/bat/IMG_'filepath1 = 'data/bat/IMG_'#
修改⽂件名('mv {} {}'.format(filepath0, filepath1))#(filepath0, filepath1)#
创建⽂件夹dirname = 'data_samples'('mkdir -p {}'.format(dirname))#if not (dirname):# (dirname)#
拷贝⽂件('cp {} {}'.format(filepath1, dirname))#(filepath1, dirname)
发布者:admin,转转请注明出处:http://www.yc00.com/news/1688931114a184763.html
评论列表(0条)