商业摄影网站,网站租空间多少钱一年,零基础自学python,游戏如何在网上推广Python快速教程 - 基础篇
5.1 Python简介 本章将介绍Python的最基本语法#xff0c;以及一些和深度学习还有计算机视觉最相关的基本使用。
5.1.1 Python简史 Python是一门解释型的高级编程语言#xff0c;特点是简单明确。Python作者是荷兰人Guido van Rossum#xff0c…Python快速教程 - 基础篇
5.1 Python简介 本章将介绍Python的最基本语法以及一些和深度学习还有计算机视觉最相关的基本使用。
5.1.1 Python简史 Python是一门解释型的高级编程语言特点是简单明确。Python作者是荷兰人Guido van Rossum1982年他获得数学和计算机硕士学位后在荷兰数学与计算科学研究所Centrum Wiskunde Informatica, CWI谋了份差事。在CWI期间Guido参与到了一门叫做ABC的语言开发工作中。ABC是一门教学语言所以拥有简单可读性好语法更接近自然语言等特点。在那个C语言一统天下的年代ABC就是一股简单的清流毕竟是门教学语言最后没有流行起来不过这段经历影响了Guido。1989年的圣诞假期闲得蛋疼的Guido决定设计一门简单易用的新语言要介于C和Shell之间同时吸取ABC语法中的优点。Guido用自己喜欢的一部喜剧电视剧来命名这门语言《Monty Pythons 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哲学的做法叫做很PythonPythonic具体参见 PEP 20 -- The Zen of Python 或者在Python中执行 import this Python拥有很好的扩充性可以非常轻松地用其他语言编写模块供调用用Python编写的模块也可以通过各种方式轻松被其他语言调用。所以一种常见的Python使用方式是底层复杂且对效率要求高的模块用C/C等语言实现顶层调用的API用Python封装这样可以通过简单的语法实现顶层逻辑故而Python又被称为“胶水语言”。这种特性的好处是无需花费很多时间在编程实现上更多的时间可以专注于思考问题的逻辑。尤其是对做算法和深度学习的从业人员这种方式是非常理想的所以如今的深度学习框架中除了MATLAB或是Deeplearning4j这种摆明了给Java用的其他框架基本上要么官方接口就是Python要么支持Python接口。 5.1.2 安装和使用Python Python有两个大版本考虑到用户群数量和库的各种框架的兼容性本文以Python22.7为准语法尽量考虑和Python3的兼容。 Unix/Linux下的Python基本都是系统自带的一般默认为Python2使用时在终端直接键入python就能进入Python解释器界面。 安装更多的python库一般有两种方法第一是用系统的软件包管理以Ubuntu 16.04 LTS为例比如想要安装numpy库后面会介绍这个库软件包的名字就是python-numpy所以在终端中输入 sudo apt install python-numpy Python自己也带了包管理器叫做pip使用如下 pip install numpy 安装和深度学习相关的框架时一般来说推荐使用系统自带的包管理出现版本错误的可能性低一些。另外也可以使用一些提前配置好很多第三方库的Python包这些包通常已经包含了深度学习框架中绝大多数的依赖库比如最常用的是Anaconda。 5.2 Python基本语法 对于一个特定的问题应该只用最好的一种方法来解决。
5.2.1 基本数据类型和运算
1基本数据类型 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。2变量和引用 Python中基本变量的赋值一般建立的是个引用比如下面的语句 a 1 b a c 1 a赋值为1后ba执行时并不会将a的值复制一遍然后赋给b而是简单地为a所指的值也就是1建立了一个引用相当于a和b都是指向包含1这个值的这块内存的指针。所以c1执行的也是个引用建立这三个变量其实是三个引用指向同一个值。这个逻辑虽然简单不过也还是常常容易弄混这没关系Python内置了id函数可以返回一个对象的地址用id函数可以让我们知道每个变量指向的是不是同一个值 id(a) # 35556792L id(b) # 35556792L id(c) # 35556792L 注释中表示的仍是执行后的结果。如果这时候我们接下面两个语句 b 2 # b的引用到新的一个变量上 id(b) # 35556768L 可以看到b引用到了另一个变量上。3运算符 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字符串用双引号和单引号都可以区别主要是单引号字符串中如果出现单引号字符则需要用转义符双引号也是一样所以在单引号字符串中使用双引号或者双引号字符串中使用单引号就会比较方便。另外三个双引号或者三个单引号围起来的也是字符串因为换行方便更多用于文档。2Python2中两个数值相除会根据数值类型判断是否整数除法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 -- -(10001) 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来实现。4模块导入 因为提到了对象名覆盖和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导入的名字里是否和现有对象名已经有冲突很可能会不知不觉覆盖了现有的对象。 5.2.2 容器
1列表 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, reverseTrue) print(c) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]2元组 元组和列表有很多相似的地方最大的区别在于不可变还有如果初始化只包含一个元素的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)3集合 集合是一种很有用的数学操作比如列表去重或是理清两组数据之间的关系集合的操作符和位操作符有交集注意不要弄混 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])4字典 字典是一种非常常见的“键-值”(key-value)映射结构键无重复一个键不能对应多个值不过多个键可以指向一个值。还是通过例子来了解构建一个名字-年龄的字典并执行一些常见操作 a {Tom: 8, Jerry: 7} print(a[Tom]) # 8 b dict(Tom8, Jerry7) # 一种字符串作为键更方便的初始化方式 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(), keyitemgetter(1)) print(c) # [(Tuffy, 2), (Tyke, 3), (Jerry, 7), (Tom, 8), (Spike, 10)] d sorted(a.iteritems(), keyitemgetter(1)) print(d) # [(Tuffy, 2), (Tyke, 3), (Jerry, 7), (Tom, 8), (Spike, 10)] e sorted(a) print(e) # 只对键排序[Jerry, Spike, Tom, Tuffy, Tyke] items()可以把字典中的键值对转化成一个列表其中每个元素是一个tupletuple的第一个元素是键第二个元素是值。变量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()直接没了。 5.2.3 分支和循环 从这节开始代码就未必适合在Python终端中输入了选个顺手的编辑器或者IDE。作者良心推荐PyCharm虽然慢但好用社区版免费。
1for循环 上面提到的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的话就不会陌生C11中也开始支持这种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, dont, 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, dont, 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!) # 和谐社会 这样不需要一个标记是否和谐的状态变量语句简洁了很多。2if和分支结构 上一个例子中已经出现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。
3if表达式中的小技巧 通过链式比较让语句简洁 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(Its 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或者False4while循环 while的就是循环和if的综合体是一种单纯的基于条件的循环本身没有遍历的意思这是和for_each的本质差别这种区别比起C/C中要明确得多用法如下 i 0 while i 100: # 笑100遍 print(ha) while True: # 一直笑 print(ha) 5.2.4 函数、生成器和类 还是从几个例子看起 def say_hello(): print(Hello!) def greetings(xGood morning!): print(x) say_hello() # Hello! greetings() # Good morning! greetings(Whats up!) # Whats up! a greetings() # 返回值是None def create_a_list(x, y2, z3): # 默认参数项必须放后面 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(x3, y4, z5) # 依次打印(x, 3), (y, 4), (z, 5) traverse_kargs(fighter1Fedor, fighter2Randleman) 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, a6, bbar) 其实和很多语言差不多括号里面定义参数参数可以有默认值且默认值不能在无默认值参数之前。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, keyget_val_at_pos_1) sorted_pairs1 sorted(heros, keylambda 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 191生成器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消息2类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(Whats 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() # Whats up! print(a._name) # 可以正常访问 print(a.cal_l2norm()) # 输出11*1111*11242 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(Hows going!) b B(12, 12, Flaubert) b.introduce() # Flaubert b.greeting() # Hows going! print(b._name()) # Flaubert print(b._A__l2norm()) # “私有”方法必须通过_A__l2norm访问
5.2.5 map, reduce和filter 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]) # ((12)3)410 filter顾名思义根据条件对可遍历结构进行筛选 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)) # 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他曾在表示过一些从函数式编程里拿来的特性是个错误。 5.2.7 字符串 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) # 从左向右查找e3 a.rfind(need) # 从右向左查找need19 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 {:06d}.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(nameTom, age8)) # Tom is 8 years old. d template.format(age7, nameJerry)# Jerry is 7 years old. format在生成字符串和文档的时候非常有用。 5.2.8 文件操作和pickle 在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, rb…。 再考虑一个场景要读取文件内容并把年龄和名字的顺序交换存成新文件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 [ Im like a dog chasing cars., I wouldnt know what to do if I caught one..., Id 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。 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_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_processes1)] # 生成每个进程要处理的子文件列表 sublists [full_list[indices[i]:indices[i1]] for i in range(n_processes)] # 生成进程 processes [Process(targetprocess_data, args(x,)) for x in sublists] # 并行处理 for p in processes: p.start() for p in processes: p.join() 其中if __name__ __main__用来标明在import时不包含但是作为文件执行时运行的语句块。为什么不用多线程呢简单说就是Python中线程的并发无法有效利用多核。 5.2.11 os模块 深度学习中的数据多是文件所以数据处理阶段和文件相关的操作就非常重要。除了文件IOPython中一些操作系统的相关功能也能够非常方便地帮助数据处理。想象一下我们有一个文件夹叫做data下边有3个子文件夹叫做catdog和bat里面分别是猫狗和蝙蝠的照片。为了训练一个三分类模型我们先要生成一个文件里面每一行是文件的路径和对应的标签。定义cat是0dog是1bat是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)