給深度學習入門者的Python快速教程 - 基礎篇

 

實在搞不定博客園的排版,排版更佳的版本在:

https://zhuanlan.zhihu.com/p/24162430 html

 

 

Life is short, you need Pythonpython

人生苦短,我用Pythongit

-- Bruce Eckelgithub

5.1 Python簡介

本章將介紹Python的最基本語法,以及一些和深度學習還有計算機視覺最相關的基本使用。算法

5.1.1 Python簡史

Python是一門解釋型的高級編程語言,特色是簡單明確。Python做者是荷蘭人Guido van Rossum1982年他得到數學和計算機碩士學位後,在荷蘭數學與計算科學研究所(Centrum Wiskunde & Informatica, CWI)謀了份差事。在CWI期間,Guido參與到了一門叫作ABC的語言開發工做中。ABC是一門教學語言,因此擁有簡單,可讀性好,語法更接近天然語言等特色。在那個C語言一統天下的年代,ABC就是一股簡單的清流。不過畢竟是門教學語言,最後沒有流行起來,不過這段經歷影響了Guido1989年的聖誕假期,閒得蛋疼的Guido決定設計一門簡單易用的新語言,要介於CShell之間,同時吸收ABC語法中的優勢。Guido用本身喜歡的一部喜劇電視劇來命名這門語言:《Monty Python's Flying Circus》。shell

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),具體參見:編程

https://www.python.org/dev/peps/pep-0020/安全

或者在Python中執行:服務器

>> import this多線程

Python擁有很好的擴充性,能夠很是輕鬆地用其餘語言編寫模塊供調用,用Python編寫的模塊也能夠經過各類方式輕鬆被其餘語言調用。因此一種常見的Python使用方式是,底層複雜且對效率要求高的模塊用C/C++等語言實現,頂層調用的APIPython封裝,這樣能夠經過簡單的語法實現頂層邏輯,故而Python又被稱爲「膠水語言」。這種特性的好處是,無需花費不少時間在編程實現上,更多的時間能夠專一于思考問題的邏輯。尤爲是對作算法和深度學習的從業人員,這種方式是很是理想的,因此現在的深度學習框架中,除了MATLAB,或是Deeplearning4j這種擺明了給Java用的,其餘框架基本上要麼官方接口就是Python,要麼支持Python接口。

5.1.2 安裝和使用Python

Python有兩個大版本,考慮到用戶羣數量和庫的各類框架的兼容性,本書以Python22.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

https://www.continuum.io/downloads

Windows下的Python安裝簡單一些,從官方網站下載相應的安裝程序就能夠了,固然也有更方便的已經包含了很全的第三方庫的選擇,WinPython

http://winpython.github.io/

而且是綠色的,直接執行就能夠用了。

5.2 Python基本語法

There should be one-- and preferably only one --obvious way to do it.

對於一個特定的問題,應該只用最好的一種方法來解決。

-- Tim Peters

5.2.1 基本數據類型和運算

基本數據類型

Python中最基本的數據類型包括整型,浮點數,布爾值和字符串。類型是不須要聲明的,好比:

a = 1       # 整數

b = 1.2     # 浮點數

c = True    # 布爾類型

d = "False" # 字符串

e = None    # NoneType

其中#是行內註釋的意思。最後一個NoneNoneType,注意不是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創建了一個引用,至關於ab都是指向包含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          # ac次方,結果爲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 --> -(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 ==# True,值相等

a is# False,指向的不是一個對象,這個語句等效於 id(a) == id(b)

a is# True,指向的都是整型值1

因此必定要分清要比較的對象應該用那種方式,對於一些特殊的狀況,好比None,本着Pythonic的原則,最好用is None / is not 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導入的名字裏是否和現有對象名已經有衝突,極可能會不知不覺覆蓋了現有的對象。

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")            # 利用list函數從任何可遍歷結構初始化

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 = [1, 2, 3, 4]

a.pop()             # 把最後一個值4從列表中移除並做爲pop的返回值

4 in            # 判斷4是否在a中,False

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()         # 倒序,返回值爲0a變爲[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],此類方法叫作slicing

c = a[2:-2]         # 下標也能夠很方便地倒着數, -1對應最後一個元素,[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)             # 列表內排序,返回值爲Nonea變爲[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]

其中range函數是一個能夠生成等差數列的函數,好比代碼中生成了0-910個元素的列表。

 

元組

元組和列表有不少類似的地方,最大的區別在於不可變,還有若是初始化只包含一個元素的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

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()能夠把字典中的鍵值對轉化成一個列表,其中每一個元素是一個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)])

這樣初始化時的順序就保留了,除了有序的特性之外,用法上和字典沒有區別。20169月,Guido宣佈在Python3.6中,字典將默認有序,這樣就不用糾結了。另外須要注意的一點是字典是經過哈希表實現的,因此鍵必須是可哈希的, list不能被哈希,因此也不能做爲字典的鍵,而tuple就能夠。

由於上上段代碼中用到了iteritems(),因此這裏順帶提一下迭代器(iterator),迭代器至關於一個函數,每次調用都返回下一個元素,從遍歷的角度來看就和列表沒有區別了。iteritems()就是一個迭代器,因此效果同樣,區別是迭代器佔用更少內存,由於不須要一上來就生成整個列表。通常來講,若是隻須要遍歷一次,用迭代器是更好的選擇,如果要屢次頻繁從一個可遍歷結構中取值,且內存夠,則直接生成整個列表會更好。固然,用迭代器生成一個完整列表並不麻煩,因此有個趨勢是把迭代器做爲默認的可遍歷方式,好比前面咱們使用過用來生成等差數列列表的range(),在Python2中對應的迭代器形式是xrange()。在Python3中,range()就再也不產生一個列表了,而是做爲迭代器,xrange()直接沒了。

5.2.3 分支和循環

從這節開始,代碼就未必適合在Python終端中輸入了,選個順手的編輯器或者IDE。做者良心推薦PyCharm,雖然慢,但好用,社區版免費:

https://www.jetbrains.com/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)

 

# 依次輸出09

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語句了,因此這部分先講講ifPython的條件控制主要是三個關鍵字: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']:    # 相較於 if x == 'piano' or x == 'violin':

    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")

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()                        # 返回值是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, 2, 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中的itemgetteritemgetter(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以上能夠容許yieldreturn同時使用,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中的類的概念和其餘語言相比沒什麼不一樣,比較特殊的是protectedprivatePython中是沒有明確限制的,一個慣例是用單下劃線開頭的表示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訪問

5.2.5 map, reducefilter

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]

須要注意的是,對於filtermap,在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]

若是要生成迭代器只須要把方括號換成括號,生成字典也很是容易:

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')              # 從左向右查找'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官網:

https://docs.python.org/2/library/string.html#format-specification-mini-language

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, 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中有個效率更高的picklecPickle,用法和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+1)*length)) for i in range(-1, n_processes)]

   

    # 生成每一個進程要處理的子文件列表

    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中線程的併發沒法有效利用多核,若是有興趣的讀者能夠從下面這個連接看起:

https://wiki.python.org/moin/GlobalInterpreterLock

5.2.11 os模塊

深度學習中的數據可能是文件,因此數據處理階段和文件相關的操做就很是重要。除了文件IOPython中一些操做系統的相關功能也可以很是方便地幫助數據處理。想象一下咱們有一個文件夾叫作data,下邊有3個子文件夾叫作catdogbat,裏面分別是貓,狗和蝙蝠的照片。爲了訓練一個三分類模型,咱們先要生成一個文件,裏面每一行是文件的路徑和對應的標籤。定義cat0dog1bat2,則能夠經過以下腳本:

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/Linuxshell已經很是強大(比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)

相關文章
相關標籤/搜索