(數據分析)第02章 Python語法基礎,IPython和Jupyter Notebooks.md

第2章 Python語法基礎,IPython和Jupyter Notebooks

當我在2011年和2012年寫做本書的初版時,可用的學習Python數據分析的資源不多。這部分上是一個雞和蛋的問題:咱們如今使用的庫,好比pandas、scikit-learn和statsmodels,那時相對來講並不成熟。2017年,數據科學、數據分析和機器學習的資源已經不少,原來通用的科學計算拓展到了計算機科學家、物理學家和其它研究領域的工做人員。學習Python和成爲軟件工程師的優秀書籍也有了。html

由於這本書是專一於Python數據處理的,對於一些Python的數據結構和庫的特性不免不足。所以,本章和第3章的內容只夠你能學習本書後面的內容。python

在我來看,沒有必要爲了數據分析而去精通Python。我鼓勵你使用IPython shell和Jupyter試驗示例代碼,並學習不一樣類型、函數和方法的文檔。雖然我已盡力讓本書內容按部就班,但讀者偶爾仍會碰到沒有以前介紹過的內容。linux

本書大部份內容關注的是基於表格的分析和處理大規模數據集的數據準備工具。爲了使用這些工具,必須首先將混亂的數據規整爲整潔的表格(或結構化)形式。幸虧,Python是一個理想的語言,能夠快速整理數據。Python使用得越熟練,越容易準備新數據集以進行分析。git

最好在IPython和Jupyter中親自嘗試本書中使用的工具。當你學會了如何啓動Ipython和Jupyter,我建議你跟隨示例代碼進行練習。與任何鍵盤驅動的操做環境同樣,記住常見的命令也是學習曲線的一部分。程序員

筆記:本章沒有介紹Python的某些概念,如類和麪向對象編程,你可能會發現它們在Python數據分析中頗有用。 爲了增強Python知識,我建議你學習官方Python教程,https://docs.python.org/3/,或是通用的Python教程書籍,好比:算法

  • Python Cookbook,第3版,David Beazley和Brian K. Jones著(O’Reilly)
  • 流暢的Python,Luciano Ramalho著 (O’Reilly)
  • 高效的Python,Brett Slatkin著 (Pearson)

2.1 Python解釋器

Python是解釋性語言。Python解釋器同一時間只能運行一個程序的一條語句。標準的交互Python解釋器能夠在命令行中經過鍵入python命令打開:shell

$ python
Python 3.6.0 | packaged by conda-forge | (default, Jan 13 2017, 23:17:12)
[GCC 4.8.2 20140120 (Red Hat 4.8.2-15)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 5
>>> print(a)
5

>>>提示輸入代碼。要退出Python解釋器返回終端,能夠輸入exit()或按Ctrl-D。編程

運行Python程序只需調用Python的同時,使用一個.py文件做爲它的第一個參數。假設建立了一個hello_world.py文件,它的內容是:api

print('Hello world')

你能夠用下面的命令運行它(hello_world.py文件必須位於終端的工做目錄):數組

$ python hello_world.py
Hello world

一些Python程序員老是這樣執行Python代碼的,從事數據分析和科學計算的人卻會使用IPython,一個強化的Python解釋器,或Jupyter notebooks,一個網頁代碼筆記本,它原先是IPython的一個子項目。在本章中,我介紹瞭如何使用IPython和Jupyter,在附錄A中有更深刻的介紹。當你使用%run命令,IPython會一樣執行指定文件中的代碼,結束以後,還能夠與結果交互:

$ ipython
Python 3.6.0 | packaged by conda-forge | (default, Jan 13 2017, 23:17:12)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: %run hello_world.py
Hello world

In [2]:

IPython默認採用序號的格式In [2]:,與標準的>>>提示符不一樣。

2.2 IPython基礎

在本節中,咱們會教你打開運行IPython shell和jupyter notebook,並介紹一些基本概念。

運行IPython Shell

你能夠用ipython在命令行打開IPython Shell,就像打開普通的Python解釋器:

$ ipython
Python 3.6.0 | packaged by conda-forge | (default, Jan 13 2017, 23:17:12)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: a = 5
In [2]: a
Out[2]: 5

你能夠經過輸入代碼並按Return(或Enter),運行任意Python語句。當你只輸入一個變量,它會顯示錶明的對象:

In [5]: import numpy as np

In [6]: data = {i : np.random.randn() for i in range(7)}

In [7]: data
Out[7]: 
{0: -0.20470765948471295,
 1: 0.47894333805754824,
 2: -0.5194387150567381,
 3: -0.55573030434749,
 4: 1.9657805725027142,
 5: 1.3934058329729904,
6: 0.09290787674371767}

前兩行是Python代碼語句;第二條語句建立一個名爲data的變量,它引用一個新建立的Python字典。最後一行打印data的值。

許多Python對象被格式化爲更易讀的形式,或稱做pretty-printed,它與普通的print不一樣。若是在標準Python解釋器中打印上述data變量,則可讀性要下降:

>>> from numpy.random import randn
>>> data = {i : randn() for i in range(7)}
>>> print(data)
{0: -1.5948255432744511, 1: 0.10569006472787983, 2: 1.972367135977295,
3: 0.15455217573074576, 4: -0.24058577449429575, 5: -1.2904897053651216,
6: 0.3308507317325902}

IPython還支持執行任意代碼塊(經過一個華麗的複製-粘貼方法)和整段Python腳本的功能。你也可使用Jupyter notebook運行大代碼塊,接下來就會看到。

運行Jupyter Notebook

notebook是Jupyter項目的重要組件之一,它是一個代碼、文本(有標記或無標記)、數據可視化或其它輸出的交互式文檔。Jupyter Notebook須要與內核互動,內核是Jupyter與其它編程語言的交互編程協議。Python的Jupyter內核是使用IPython。要啓動Jupyter,在命令行中輸入jupyter notebook:

$ jupyter notebook
[I 15:20:52.739 NotebookApp] Serving notebooks from local directory:
/home/wesm/code/pydata-book
[I 15:20:52.739 NotebookApp] 0 active kernels
[I 15:20:52.739 NotebookApp] The Jupyter Notebook is running at:
http://localhost:8888/
[I 15:20:52.740 NotebookApp] Use Control-C to stop this server and shut down
all kernels (twice to skip confirmation).
Created new window in existing browser session.

在多數平臺上,Jupyter會自動打開默認的瀏覽器(除非指定了--no-browser)。或者,能夠在啓動notebook以後,手動打開網頁http://localhost:8888/。圖2-1展現了Google Chrome中的notebook。

筆記:許多人使用Jupyter做爲本地的計算環境,但它也能夠部署到服務器上遠程訪問。這裏不作介紹,若是須要的話,鼓勵讀者自行到網上學習。

 

圖2-1 Jupyter notebook啓動頁面
圖2-1 Jupyter notebook啓動頁面

 

要新建一個notebook,點擊按鈕New,選擇「Python3」或「conda[默認項]」。若是是第一次,點擊空格,輸入一行Python代碼。而後按Shift-Enter執行。

 

圖2-2 Jupyter新notebook頁面
圖2-2 Jupyter新notebook頁面

 

當保存notebook時(File目錄下的Save and Checkpoint),會建立一個後綴名爲.ipynb的文件。這是一個自包含文件格式,包含當前筆記本中的全部內容(包括全部已評估的代碼輸出)。能夠被其它Jupyter用戶加載和編輯。要加載存在的notebook,把它放到啓動notebook進程的相同目錄內。你能夠用本書的示例代碼練習,見圖2-3。

雖然Jupyter notebook和IPython shell使用起來不一樣,本章中幾乎全部的命令和工具均可以通用。

 

圖2-3 Jupyter查看一個存在的notebook的頁面
圖2-3 Jupyter查看一個存在的notebook的頁面

 

Tab補全

從外觀上,IPython shell和標準的Python解釋器只是看起來不一樣。IPython shell的進步之一是具有其它IDE和交互計算分析環境都有的tab補全功能。在shell中輸入表達式,按下Tab,會搜索已輸入變量(對象、函數等等)的命名空間:

In [1]: an_apple = 27

In [2]: an_example = 42

In [3]: an<Tab>
an_apple    and         an_example  any

在這個例子中,IPython呈現出了以前兩個定義的變量和Python的關鍵字和內建的函數any。固然,你也能夠補全任何對象的方法和屬性:

In [3]: b = [1, 2, 3]

In [4]: b.<Tab>
b.append  b.count   b.insert  b.reverse
b.clear   b.extend  b.pop     b.sort
b.copy    b.index   b.remove

一樣也適用於模塊:

In [1]: import datetime

In [2]: datetime.<Tab>
datetime.date          datetime.MAXYEAR       datetime.timedelta
datetime.datetime      datetime.MINYEAR       datetime.timezone
datetime.datetime_CAPI datetime.time          datetime.tzinfo

在Jupyter notebook和新版的IPython(5.0及以上),自動補全功能是下拉框的形式。

筆記:注意,默認狀況下,IPython會隱藏下劃線開頭的方法和屬性,好比魔術方法和內部的「私有」方法和屬性,以免混亂的顯示(和讓新手迷惑!)這些也能夠tab補全,可是你必須首先鍵入一個下劃線才能看到它們。若是你喜歡老是在tab補全中看到這樣的方法,你能夠IPython配置中進行設置。能夠在IPython文檔中查找方法。

除了補全命名、對象和模塊屬性,Tab還能夠補全其它的。當輸入看似文件路徑時(即便是Python字符串),按下Tab也能夠補全電腦上對應的文件信息:

In [7]: datasets/movielens/<Tab>
datasets/movielens/movies.dat    datasets/movielens/README
datasets/movielens/ratings.dat   datasets/movielens/users.dat

In [7]: path = 'datasets/movielens/<Tab>
datasets/movielens/movies.dat    datasets/movielens/README
datasets/movielens/ratings.dat   datasets/movielens/users.dat

結合%run,tab補全能夠節省許多鍵盤操做。

另外,tab補全能夠補全函數的關鍵詞參數(包括等於號=)。見圖2-4。

 

圖2-4 Jupyter notebook中自動補全函數關鍵詞
圖2-4 Jupyter notebook中自動補全函數關鍵詞

 

後面會仔細地學習函數。

自省

在變量先後使用問號?,能夠顯示對象的信息:

In [8]: b = [1, 2, 3]

In [9]: b?
Type:       list
String Form:[1, 2, 3]
Length:     3
Docstring:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items In [10]: print? Docstring: print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline. flush: whether to forcibly flush the stream. Type: builtin_function_or_method 

這能夠做爲對象的自省。若是對象是一個函數或實例方法,定義過的文檔字符串,也會顯示出信息。假設咱們寫了一個以下的函數:

def add_numbers(a, b):
    """ Add two numbers together Returns ------- the_sum : type of arguments """
    return a + b

而後使用?符號,就能夠顯示以下的文檔字符串:

In [11]: add_numbers?
Signature: add_numbers(a, b)
Docstring:
Add two numbers together

Returns
-------
the_sum : type of arguments
File:      <ipython-input-9-6a548a216e27>
Type:      function

使用??會顯示函數的源碼:

In [12]: add_numbers??
Signature: add_numbers(a, b)
Source:
def add_numbers(a, b):
    """ Add two numbers together Returns ------- the_sum : type of arguments """
    return a + b
File:      <ipython-input-9-6a548a216e27>
Type:      function

?還有一個用途,就是像Unix或Windows命令行同樣搜索IPython的命名空間。字符與通配符結合能夠匹配全部的名字。例如,咱們能夠得到全部包含load的頂級NumPy命名空間:

In [13]: np.*load*?
np.__loader__
np.load
np.loads
np.loadtxt
np.pkgload

%run命令

你能夠用%run命令運行全部的Python程序。假設有一個文件ipython_script_test.py

def f(x, y, z):
    return (x + y) / z

a = 5
b = 6
c = 7.5

result = f(a, b, c)

能夠以下運行:

In [14]: %run ipython_script_test.py

這段腳本運行在空的命名空間(沒有import和其它定義的變量),所以結果和普通的運行方式python script.py相同。文件中全部定義的變量(import、函數和全局變量,除非拋出異常),均可以在IPython shell中隨後訪問:

In [15]: c
Out [15]: 7.5

In [16]: result
Out[16]: 1.4666666666666666

若是一個Python腳本須要命令行參數(在sys.argv中查找),能夠在文件路徑以後傳遞,就像在命令行上運行同樣。

筆記:若是想讓一個腳本訪問IPython已經定義過的變量,可使用%run -i

在Jupyter notebook中,你也可使用%load,它將腳本導入到一個代碼格中:

>>> %load ipython_script_test.py

    def f(x, y, z):
        return (x + y) / z
    a = 5
    b = 6
    c = 7.5

    result = f(a, b, c)

中斷運行的代碼

代碼運行時按Ctrl-C,不管是%run或長時間運行命令,都會致使KeyboardInterrupt。這會致使幾乎全部Python程序當即中止,除非一些特殊狀況。

警告:當Python代碼調用了一些編譯的擴展模塊,按Ctrl-C不必定將執行的程序當即中止。在這種狀況下,你必須等待,直到控制返回Python解釋器,或者在更糟糕的狀況下強制終止Python進程。

從剪貼板執行程序

若是使用Jupyter notebook,你能夠將代碼複製粘貼到任意代碼格執行。在IPython shell中也能夠從剪貼板執行。假設在其它應用中複製了以下代碼:

x = 5
y = 7
if x > 5:
    x += 1

    y = 8

最簡單的方法是使用%paste%cpaste函數。%paste能夠直接運行剪貼板中的代碼:

In [17]: %paste
x = 5
y = 7
if x > 5:
    x += 1

    y = 8
## -- End pasted text --

%cpaste功能相似,但會給出一條提示:

In [18]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:x = 5
:y = 7
:if x > 5:
:    x += 1
:
:    y = 8
:--

使用%cpaste,你能夠粘貼任意多的代碼再運行。你可能想在運行前,先看看代碼。若是粘貼了錯誤的代碼,能夠用Ctrl-C中斷。

鍵盤快捷鍵

IPython有許多鍵盤快捷鍵進行導航提示(相似Emacs文本編輯器或UNIX bash Shell)和交互shell的歷史命令。表2-1總結了常見的快捷鍵。圖2-5展現了一部分,如移動光標。

 

圖2-5 IPython shell中一些快捷鍵的說明
圖2-5 IPython shell中一些快捷鍵的說明

 

 

表2-1 IPython的標準快捷鍵
表2-1 IPython的標準快捷鍵

 

Jupyter notebooks有另一套龐大的快捷鍵。由於它的快捷鍵比IPython的變化快,建議你參閱Jupyter notebook的幫助文檔。

魔術命令

IPython中特殊的命令(Python中沒有)被稱做「魔術」命令。這些命令可使普通任務更便捷,更容易控制IPython系統。魔術命令是在指令前添加百分號%前綴。例如,能夠用%timeit(這個命令後面會詳談)測量任何Python語句,例如矩陣乘法,的執行時間:

In [20]: a = np.random.randn(100, 100)

In [20]: %timeit np.dot(a, a)
10000 loops, best of 3: 20.9 µs per loop

魔術命令能夠被看作IPython中運行的命令行。許多魔術命令有「命令行」選項,能夠經過?查看:

In [21]: %debug?
Docstring:
::

  %debug [--breakpoint FILE:LINE] [statement [statement ...]]

Activate the interactive debugger.

This magic command support two ways of activating debugger.
One is to activate debugger before executing code.  This way, you
can set a break point, to step through the code from the point.
You can use this mode by giving statements to execute and optionally
a breakpoint.

The other one is to activate debugger in post-mortem mode.  You can
activate this mode simply running %debug without any argument.
If an exception has just occurred, this lets you inspect its stack
frames interactively.  Note that this will always work only on the last
traceback that occurred, so you must call this quickly after an
exception that you wish to inspect has fired, because if another one
occurs, it clobbers the previous one.

If you want IPython to automatically do this on every exception, see
the %pdb magic for more details.

positional arguments:
  statement             Code to run in debugger. You can omit this in cell
                        magic mode.

optional arguments:
  --breakpoint <FILE:LINE>, -b <FILE:LINE>
                        Set break point at LINE in FILE.

魔術函數默承認以不用百分號,只要沒有變量和函數名相同。這個特色被稱爲「自動魔術」,能夠用%automagic打開或關閉。

一些魔術函數與Python函數很像,它的結果能夠賦值給一個變量:

In [22]: %pwd
Out[22]: '/home/wesm/code/pydata-book

In [23]: foo = %pwd

In [24]: foo
Out[24]: '/home/wesm/code/pydata-book'

IPython的文檔能夠在shell中打開,我建議你用%quickref%magic學習下全部特殊命令。表2-2列出了一些能夠提升生產率的交互計算和Python開發的IPython指令。

 

表2-2 一些經常使用的IPython魔術命令
表2-2 一些經常使用的IPython魔術命令

 

集成Matplotlib

IPython在分析計算領域可以流行的緣由之一是它很是好的集成了數據可視化和其它用戶界面庫,好比matplotlib。不用擔憂之前沒用過matplotlib,本書後面會詳細介紹。%matplotlib魔術函數配置了IPython shell和Jupyter notebook中的matplotlib。這點很重要,其它建立的圖不會出現(notebook)或獲取session的控制,直到結束(shell)。

在IPython shell中,運行%matplotlib能夠進行設置,能夠建立多個繪圖窗口,而不會干擾控制檯session:

In [26]: %matplotlib
Using matplotlib backend: Qt4Agg

在JUpyter中,命令有所不一樣(圖2-6):

In [26]: %matplotlib inline

 

圖2-6 Jupyter行內matplotlib做圖
圖2-6 Jupyter行內matplotlib做圖

 

2.3 Python語法基礎

在本節中,我將概述基本的Python概念和語言機制。在下一章,我將詳細介紹Python的數據結構、函數和其它內建工具。

語言的語義

Python的語言設計強調的是可讀性、簡潔和清晰。有些人稱Python爲「可執行的僞代碼」。

使用縮進,而不是括號

Python使用空白字符(tab和空格)來組織代碼,而不是像其它語言,好比R、C++、JAVA和Perl那樣使用括號。看一個排序算法的for循環:

for x in array:
    if x < pivot:
        less.append(x)
    else:
        greater.append(x)

冒號標誌着縮進代碼塊的開始,冒號以後的全部代碼的縮進量必須相同,直到代碼塊結束。無論是否喜歡這種形式,使用空白符是Python程序員開發的一部分,在我看來,這可讓python的代碼可讀性大大優於其它語言。雖然期初看起來很奇怪,通過一段時間,你就能適應了。

筆記:我強烈建議你使用四個空格做爲默認的縮進,可使用tab代替四個空格。許多文本編輯器的設置是使用製表位替代空格。某些人使用tabs或不一樣數目的空格數,常見的是使用兩個空格。大多數狀況下,四個空格是大多數人採用的方法,所以建議你也這樣作。

你應該已經看到,Python的語句不須要用分號結尾。可是,分號卻能夠用來給同在一行的語句切分:

a = 5; b = 6; c = 7

Python不建議將多條語句放到一行,這會下降代碼的可讀性。

萬物皆對象

Python語言的一個重要特性就是它的對象模型的一致性。每一個數字、字符串、數據結構、函數、類、模塊等等,都是在Python解釋器的自有「盒子」內,它被認爲是Python對象。每一個對象都有類型(例如,字符串或函數)和內部數據。在實際中,這可讓語言很是靈活,由於函數也能夠被當作對象使用。

註釋

任何前面帶有井號#的文本都會被Python解釋器忽略。這一般被用來添加註釋。有時,你會想排除一段代碼,但並不刪除。簡便的方法就是將其註釋掉:

results = []
for line in file_handle:
    # keep the empty lines for now
    # if len(line) == 0:
    # continue
    results.append(line.replace('foo', 'bar'))

也能夠在執行過的代碼後面添加註釋。一些人習慣在代碼以前添加註釋,前者這種方法有時也是有用的:

print("Reached this line")  # Simple status report

函數和對象方法調用

你能夠用圓括號調用函數,傳遞零個或幾個參數,或者將返回值給一個變量:

result = f(x, y, z)
g()

幾乎Python中的每一個對象都有附加的函數,稱做方法,能夠用來訪問對象的內容。能夠用下面的語句調用:

obj.some_method(x, y, z)

函數可使用位置和關鍵詞參數:

result = f(a, b, c, d=5, e='foo')

後面會有更多介紹。

變量和參數傳遞

當在Python中建立變量(或名字),你就在等號右邊建立了一個對這個變量的引用。考慮一個整數列表:

In [8]: a = [1, 2, 3]

假設將a賦值給一個新變量b:

In [9]: b = a

在有些方法中,這個賦值會將數據[1, 2, 3]也複製。在Python中,a和b其實是同一個對象,即原有列表[1, 2, 3](見圖2-7)。你能夠在a中添加一個元素,而後檢查b:

In [10]: a.append(4)

In [11]: b
Out[11]: [1, 2, 3, 4]

 

圖2-7 對同一對象的雙重引用
圖2-7 對同一對象的雙重引用

 

理解Python的引用的含義,數據是什麼時候、如何、爲什麼複製的,是很是重要的。尤爲是當你用Python處理大的數據集時。

筆記:賦值也被稱做綁定,咱們是把一個名字綁定給一個對象。變量名有時可能被稱爲綁定變量。

當你將對象做爲參數傳遞給函數時,新的局域變量建立了對原始對象的引用,而不是複製。若是在函數裏綁定一個新對象到一個變量,這個變更不會反映到上一層。所以能夠改變可變參數的內容。假設有如下函數:

def append_element(some_list, element):
    some_list.append(element)

而後有:

In [27]: data = [1, 2, 3]

In [28]: append_element(data, 4)

In [29]: data
Out[29]: [1, 2, 3, 4]

動態引用,強類型

與許多編譯語言(如JAVA和C++)對比,Python中的對象引用不包含附屬的類型。下面的代碼是沒有問題的:

In [12]: a = 5

In [13]: type(a)
Out[13]: int

In [14]: a = 'foo'

In [15]: type(a)
Out[15]: str

變量是在特殊命名空間中的對象的名字,類型信息保存在對象自身中。一些人可能會說Python不是「類型化語言」。這是不正確的,看下面的例子:

In [16]: '5' + 5
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-f9dbf5f0b234> in <module>()
----> 1 '5' + 5
TypeError: must be str, not int

在某些語言中,例如Visual Basic,字符串‘5’可能被默許轉換(或投射)爲整數,所以會產生10。但在其它語言中,例如JavaScript,整數5會被投射成字符串,結果是聯結字符串‘55’。在這個方面,Python被認爲是強類型化語言,意味着每一個對象都有明確的類型(或類),默許轉換隻會發生在特定的狀況下,例如:

In [17]: a = 4.5

In [18]: b = 2

# String formatting, to be visited later
In [19]: print('a is {0}, b is {1}'.format(type(a), type(b)))
a is <class 'float'>, b is <class 'int'>

In [20]: a / b
Out[20]: 2.25

知道對象的類型很重要,最好能讓函數能夠處理多種類型的輸入。你能夠用isinstance函數檢查對象是某個類型的實例:

In [21]: a = 5

In [22]: isinstance(a, int)
Out[22]: True

isinstance能夠用類型元組,檢查對象的類型是否在元組中:

In [23]: a = 5; b = 4.5

In [24]: isinstance(a, (int, float))
Out[24]: True

In [25]: isinstance(b, (int, float))
Out[25]: True

屬性和方法

Python的對象一般都有屬性(其它存儲在對象內部的Python對象)和方法(對象的附屬函數能夠訪問對象的內部數據)。能夠用obj.attribute_name訪問屬性和方法:

In [1]: a = 'foo'

In [2]: a.<Press Tab>
a.capitalize  a.format      a.isupper     a.rindex      a.strip
a.center      a.index       a.join        a.rjust       a.swapcase
a.count       a.isalnum     a.ljust       a.rpartition  a.title
a.decode      a.isalpha     a.lower       a.rsplit      a.translate
a.encode      a.isdigit     a.lstrip      a.rstrip      a.upper
a.endswith    a.islower     a.partition   a.split       a.zfill
a.expandtabs  a.isspace     a.replace     a.splitlines
a.find        a.istitle     a.rfind       a.startswith

也能夠用getattr函數,經過名字訪問屬性和方法:

In [27]: getattr(a, 'split')
Out[27]: <function str.split>

在其它語言中,訪問對象的名字一般稱做「反射」。本書不會大量使用getattr函數和相關的hasattrsetattr函數,使用這些函數能夠高效編寫原生的、可重複使用的代碼。

鴨子類型

常常地,你可能不關心對象的類型,只關心對象是否有某些方法或用途。這一般被稱爲「鴨子類型」,來自「走起來像鴨子、叫起來像鴨子,那麼它就是鴨子」的說法。例如,你能夠經過驗證一個對象是否遵循迭代協議,判斷它是可迭代的。對於許多對象,這意味着它有一個__iter__魔術方法,其它更好的判斷方法是使用iter函數:

def isiterable(obj):
    try:
        iter(obj)
        return True
    except TypeError: # not iterable
        return False

這個函數會返回字符串以及大多數Python集合類型爲True

In [29]: isiterable('a string')
Out[29]: True

In [30]: isiterable([1, 2, 3])
Out[30]: True

In [31]: isiterable(5)
Out[31]: False

我老是用這個功能編寫能夠接受多種輸入類型的函數。常見的例子是編寫一個函數能夠接受任意類型的序列(list、tuple、ndarray)或是迭代器。你可先檢驗對象是不是列表(或是NUmPy數組),若是不是的話,將其轉變成列表:

if not isinstance(x, list) and isiterable(x):
    x = list(x)

引入

在Python中,模塊就是一個有.py擴展名、包含Python代碼的文件。假設有如下模塊:

# some_module.py
PI = 3.14159

def f(x):
    return x + 2

def g(a, b):
    return a + b

若是想從同目錄下的另外一個文件訪問some_module.py中定義的變量和函數,能夠:

import some_module
result = some_module.f(5)
pi = some_module.PI

或者:

from some_module import f, g, PI
result = g(5, PI)

使用as關鍵詞,你能夠給引入起不一樣的變量名:

import some_module as sm
from some_module import PI as pi, g as gf

r1 = sm.f(pi)
r2 = gf(6, pi)

二元運算符和比較運算符

大多數二元數學運算和比較都不難想到:

In [32]: 5 - 7
Out[32]: -2

In [33]: 12 + 21.5
Out[33]: 33.5

In [34]: 5 <= 2
Out[34]: False

表2-3列出了全部的二元運算符。

要判斷兩個引用是否指向同一個對象,可使用is方法。is not能夠判斷兩個對象是不一樣的:

In [35]: a = [1, 2, 3]

In [36]: b = a

In [37]: c = list(a)

In [38]: a is b
Out[38]: True

In [39]: a is not c
Out[39]: True

由於list老是建立一個新的Python列表(即複製),咱們能夠判定c是不一樣於a的。使用is比較與==運算符不一樣,以下:

In [40]: a == c
Out[40]: True

isis not經常使用來判斷一個變量是否爲None,由於只有一個None的實例:

In [41]: a = None

In [42]: a is None
Out[42]: True

 

表2-3 二元運算符
表2-3 二元運算符

 

可變與不可變對象

Python中的大多數對象,好比列表、字典、NumPy數組,和用戶定義的類型(類),都是可變的。意味着這些對象或包含的值能夠被修改:

In [43]: a_list = ['foo', 2, [4, 5]]

In [44]: a_list[2] = (3, 4)

In [45]: a_list
Out[45]: ['foo', 2, (3, 4)]

其它的,例如字符串和元組,是不可變的:

In [46]: a_tuple = (3, 5, (4, 5))

In [47]: a_tuple[1] = 'four'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-47-b7966a9ae0f1> in <module>()
----> 1 a_tuple[1] = 'four'
TypeError: 'tuple' object does not support item assignment

記住,能夠修改一個對象並不意味就要修改它。這被稱爲反作用。例如,當寫一個函數,任何反作用都要在文檔或註釋中寫明。若是可能的話,我推薦避免反作用,採用不可變的方式,即便要用到可變對象。

標量類型

Python的標準庫中有一些內建的類型,用於處理數值數據、字符串、布爾值,和日期時間。這些單值類型被稱爲標量類型,本書中稱其爲標量。表2-4列出了主要的標量。日期和時間處理會另外討論,由於它們是標準庫的datetime模塊提供的。

 

表2-4 Python的標量
表2-4 Python的標量

 

數值類型

Python的主要數值類型是intfloatint能夠存儲任意大的數

In [48]: ival = 17239871

In [49]: ival ** 6
Out[49]: 26254519291092456596965462913230729701102721

浮點數使用Python的float類型。每一個數都是雙精度(64位)的值。也能夠用科學計數法表示:

In [50]: fval = 7.243

In [51]: fval2 = 6.78e-5

不能獲得整數的除法會獲得浮點數:

In [52]: 3 / 2
Out[52]: 1.5

要得到C-風格的整除(去掉小數部分),可使用底除運算符//:

In [53]: 3 // 2
Out[53]: 1

字符串

許多人是由於Python強大而靈活的字符串處理而使用Python的。你能夠用單引號或雙引號來寫字符串:

a = 'one way of writing a string'
b = "another way"

對於有換行符的字符串,可使用三引號,'''或"""都行:

c = """ This is a longer string that spans multiple lines """

字符串c實際包含四行文本,"""後面和lines後面的換行符。能夠用count方法計算c中的新的行:

In [55]: c.count('\n')
Out[55]: 3

Python的字符串是不可變的,不能修改字符串:

In [56]: a = 'this is a string'

In [57]: a[10] = 'f'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-57-5ca625d1e504> in <module>()
----> 1 a[10] = 'f'
TypeError: 'str' object does not support item assignment

In [58]: b = a.replace('string', 'longer string')

In [59]: b
Out[59]: 'this is a longer string'

通過以上的操做,變量a並無被修改:

In [60]: a
Out[60]: 'this is a string'

許多Python對象使用str函數能夠被轉化爲字符串:

In [61]: a = 5.6

In [62]: s = str(a)

In [63]: print(s)
5.6

字符串是一個序列的Unicode字符,所以能夠像其它序列,好比列表和元組(下一章會詳細介紹二者)同樣處理

In [64]: s = 'python'

In [65]: list(s)
Out[65]: ['p', 'y', 't', 'h', 'o', 'n']

In [66]: s[:3]
Out[66]: 'pyt'

語法s[:3]被稱做切片,適用於許多Python序列。後面會更詳細的介紹,本書中用到不少切片。

反斜槓是轉義字符,意思是它備用來表示特殊字符,好比換行符\n或Unicode字符。要寫一個包含反斜槓的字符串,須要進行轉義:

In [67]: s = '12\\34'

In [68]: print(s)
12\34

若是字符串中包含許多反斜槓,但沒有特殊字符,這樣作就很麻煩。幸虧,能夠在字符串前面加一個r,代表字符就是它自身:

In [69]: s = r'this\has\no\special\characters'

In [70]: s
Out[70]: 'this\\has\\no\\special\\characters'

r表示raw。
strong text
將兩個字符串合併,會產生一個新的字符串:

In [71]: a = 'this is the first half '

In [72]: b = 'and this is the second half'

In [73]: a + b
Out[73]: 'this is the first half and this is the second half'

字符串的模板化或格式化,是另外一個重要的主題。Python 3拓展了此類的方法,這裏只介紹一些。字符串對象有format方法,能夠替換格式化的參數爲字符串,產生一個新的字符串:

In [74]: template = '{0:.2f} {1:s} are worth US${2:d}'

在這個字符串中,

  • {0:.2f}表示格式化第一個參數爲帶有兩位小數的浮點數。
  • {1:s}表示格式化第二個參數爲字符串。
  • {2:d}表示格式化第三個參數爲一個整數。

要替換參數爲這些格式化的參數,咱們傳遞format方法一個序列:

In [75]: template.format(4.5560, 'Argentine Pesos', 1)
Out[75]: '4.56 Argentine Pesos are worth US$1'

字符串格式化是一個很深的主題,有多種方法和大量的選項,能夠控制字符串中的值是如何格式化的。推薦參閱Python官方文檔。

這裏歸納介紹字符串處理,第8章的數據分析會詳細介紹。

字節和Unicode

在Python 3及以上版本中,Unicode是一級的字符串類型,這樣能夠更一致的處理ASCII和Non-ASCII文本。在老的Python版本中,字符串都是字節,不使用Unicode編碼。假如知道字符編碼,能夠將其轉化爲Unicode。看一個例子:

In [76]: val = "español"

In [77]: val
Out[77]: 'español'

能夠用encode將這個Unicode字符串編碼爲UTF-8:

In [78]: val_utf8 = val.encode('utf-8')

In [79]: val_utf8
Out[79]: b'espa\xc3\xb1ol'

In [80]: type(val_utf8)
Out[80]: bytes

若是你知道一個字節對象的Unicode編碼,用decode方法能夠解碼:

In [81]: val_utf8.decode('utf-8')
Out[81]: 'español'

雖然UTF-8編碼已經變成主流,但由於歷史的緣由,你仍然可能碰到其它編碼的數據:

In [82]: val.encode('latin1')
Out[82]: b'espa\xf1ol'

In [83]: val.encode('utf-16')
Out[83]: b'\xff\xfee\x00s\x00p\x00a\x00\xf1\x00o\x00l\x00'

In [84]: val.encode('utf-16le')
Out[84]: b'e\x00s\x00p\x00a\x00\xf1\x00o\x00l\x00'

工做中碰到的文件不少都是字節對象,盲目地將全部數據編碼爲Unicode是不可取的。

雖然用的很少,你能夠在字節文本的前面加上一個b:

In [85]: bytes_val = b'this is bytes'

In [86]: bytes_val
Out[86]: b'this is bytes'

In [87]: decoded = bytes_val.decode('utf8')

In [88]: decoded  # this is str (Unicode) now
Out[88]: 'this is bytes'

布爾值

Python中的布爾值有兩個,True和False。比較和其它條件表達式能夠用True和False判斷。布爾值能夠與and和or結合使用:

In [89]: True and True
Out[89]: True

In [90]: False or True
Out[90]: True

類型轉換

str、bool、int和float也是函數,能夠用來轉換類型:

In [91]: s = '3.14159'

In [92]: fval = float(s)

In [93]: type(fval)
Out[93]: float

In [94]: int(fval)
Out[94]: 3

In [95]: bool(fval)
Out[95]: True

In [96]: bool(0)
Out[96]: False

None

None是Python的空值類型。若是一個函數沒有明確的返回值,就會默認返回None:

In [97]: a = None

In [98]: a is None
Out[98]: True

In [99]: b = 5

In [100]: b is not None
Out[100]: True

None也經常做爲函數的默認參數:

def add_and_maybe_multiply(a, b, c=None):
    result = a + b

    if c is not None:
        result = result * c

    return result

另外,None不只是一個保留字,仍是惟一的NoneType的實例:

In [101]: type(None)
Out[101]: NoneType

日期和時間

Python內建的datetime模塊提供了datetimedatetime類型。datetime類型結合了datetime,是最常使用的:

In [102]: from datetime import datetime, date, time

In [103]: dt = datetime(2011, 10, 29, 20, 30, 21)

In [104]: dt.day
Out[104]: 29

In [105]: dt.minute
Out[105]: 30

根據datetime實例,你能夠用datetime提取出各自的對象:

In [106]: dt.date()
Out[106]: datetime.date(2011, 10, 29)

In [107]: dt.time()
Out[107]: datetime.time(20, 30, 21)

strftime方法能夠將datetime格式化爲字符串:

In [108]: dt.strftime('%m/%d/%Y %H:%M')
Out[108]: '10/29/2011 20:30'

strptime能夠將字符串轉換成datetime對象:

In [109]: datetime.strptime('20091031', '%Y%m%d')
Out[109]: datetime.datetime(2009, 10, 31, 0, 0)

表2-5列出了全部的格式化命令。

 

表2-5 Datetime格式化指令(與ISO C89兼容)
表2-5 Datetime格式化指令(與ISO C89兼容)

 

當你聚類或對時間序列進行分組,替換datetimes的time字段有時會頗有用。例如,用0替換分和秒:

In [110]: dt.replace(minute=0, second=0)
Out[110]: datetime.datetime(2011, 10, 29, 20, 0)

由於datetime.datetime是不可變類型,上面的方法會產生新的對象。

兩個datetime對象的差會產生一個datetime.timedelta類型:

In [111]: dt2 = datetime(2011, 11, 15, 22, 30)

In [112]: delta = dt2 - dt

In [113]: delta
Out[113]: datetime.timedelta(17, 7179)

In [114]: type(delta)
Out[114]: datetime.timedelta

結果timedelta(17, 7179)指明瞭timedelta將17天、7179秒的編碼方式。

timedelta添加到datetime,會產生一個新的偏移datetime

In [115]: dt
Out[115]: datetime.datetime(2011, 10, 29, 20, 30, 21)

In [116]: dt + delta
Out[116]: datetime.datetime(2011, 11, 15, 22, 30)

控制流

Python有若干內建的關鍵字進行條件邏輯、循環和其它控制流操做。

if、elif和else

if是最廣爲人知的控制流語句。它檢查一個條件,若是爲True,就執行後面的語句:

if x < 0:
    print('It's negative') 

if後面能夠跟一個或多個elif,全部條件都是False時,還能夠添加一個else

if x < 0:
    print('It's negative') elif x == 0: print('Equal to zero') elif 0 < x < 5: print('Positive but smaller than 5') else: print('Positive and larger than or equal to 5') 

若是某個條件爲True,後面的elif就不會被執行。當使用and和or時,複合條件語句是從左到右執行:

In [117]: a = 5; b = 7

In [118]: c = 8; d = 4

In [119]: if a < b or c > d:
   .....:     print('Made it')
Made it

在這個例子中,c > d不會被執行,由於第一個比較是True:

也能夠把比較式串在一塊兒:

In [120]: 4 > 3 > 2 > 1
Out[120]: True

for循環

for循環是在一個集合(列表或元組)中進行迭代,或者就是一個迭代器。for循環的標準語法是:

for value in collection:
    # do something with value

你能夠用continue使for循環提早,跳過剩下的部分。看下面這個例子,將一個列表中的整數相加,跳過None:

sequence = [1, 2, None, 4, None, 5]
total = 0
for value in sequence:
    if value is None:
        continue
    total += value

能夠用break跳出for循環。下面的代碼將各元素相加,直到遇到5:

sequence = [1, 2, 0, 4, 6, 5, 2, 1]
total_until_5 = 0
for value in sequence:
    if value == 5:
        break
    total_until_5 += value

break只中斷for循環的最內層,其他的for循環仍會運行:

In [121]: for i in range(4):
   .....:     for j in range(4):
   .....:         if j > i:
   .....:             break
   .....:         print((i, j))
   .....:
(0, 0)
(1, 0)
(1, 1)
(2, 0)
(2, 1)
(2, 2)
(3, 0)
(3, 1)
(3, 2)
(3, 3)

若是集合或迭代器中的元素序列(元組或列表),能夠用for循環將其方便地拆分紅變量:

for a, b, c in iterator:
    # do something

While循環

while循環指定了條件和代碼,當條件爲False或用break退出循環,代碼纔會退出:

x = 256
total = 0
while x > 0:
    if total > 500:
        break
    total += x
    x = x // 2

pass

pass是Python中的非操做語句。代碼塊不須要任何動做時可使用(做爲未執行代碼的佔位符);由於Python須要使用空白字符劃定代碼塊,因此須要pass:

if x < 0:
    print('negative!')
elif x == 0:
    # TODO: put something smart here
    pass
else:
    print('positive!')

range

range函數返回一個迭代器,它產生一個均勻分佈的整數序列:

In [122]: range(10)
Out[122]: range(0, 10)

In [123]: list(range(10))
Out[123]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

range的三個參數是(起點,終點,步進)

In [124]: list(range(0, 20, 2))
Out[124]: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

In [125]: list(range(5, 0, -1))
Out[125]: [5, 4, 3, 2, 1]

能夠看到,range產生的整數不包括終點。range的常見用法是用序號迭代序列:

seq = [1, 2, 3, 4]
for i in range(len(seq)):
    val = seq[i]

可使用list來存儲range在其餘數據結構中生成的全部整數,默認的迭代器形式一般是你想要的。下面的代碼對0到99999中3或5的倍數求和:

sum = 0
for i in range(100000):
    # % is the modulo operator
    if i % 3 == 0 or i % 5 == 0:
        sum += i

雖然range能夠產生任意大的數,但任意時刻耗用的內存卻很小。

三元表達式

Python中的三元表達式能夠將if-else語句放到一行裏。語法以下:

value = true-expr if condition else false-expr

true-exprfalse-expr能夠是任何Python代碼。它和下面的代碼效果相同:

if condition:
    value = true-expr
else:
    value = false-expr

下面是一個更具體的例子:

In [126]: x = 5

In [127]: 'Non-negative' if x >= 0 else 'Negative'
Out[127]: 'Non-negative'

和if-else同樣,只有一個表達式會被執行。所以,三元表達式中的if和else能夠包含大量的計算,但只有True的分支會被執行。所以,三元表達式中的if和else能夠包含大量的計算,但只有True的分支會被執行。

雖然使用三元表達式能夠壓縮代碼,但會下降代碼可讀性。

相關文章
相關標籤/搜索