天天看點

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

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

當我在2011年和2012年寫作本書的第一版時,可用的學習Python資料分析的資源很少。這部分上是一個雞和蛋的問題:我們現在使用的庫,比如pandas、scikit-learn和statsmodels,那時相對來說并不成熟。2017年,資料科學、資料分析和機器學習的資源已經很多,原來通用的科學計算拓展到了計算機科學家、實體學家和其它研究領域的從業人員。學習Python和成為軟體工程師的優秀書籍也有了。

因為這本書是專注于Python資料處理的,對于一些Python的資料結構和庫的特性難免不足。是以,本章和第3章的内容隻夠你能學習本書後面的内容。

在我來看,沒有必要為了資料分析而去精通Python。我鼓勵你使用IPython shell和Jupyter試驗示例代碼,并學習不同類型、函數和方法的文檔。雖然我已盡力讓本書内容循序漸進,但讀者偶爾仍會碰到沒有之前介紹過的内容。

本書大部分内容關注的是基于表格的分析和處理大規模資料集的資料準備工具。為了使用這些工具,必須首先将混亂的資料規整為整潔的表格(或結構化)形式。幸好,Python是一個理想的語言,可以快速整理資料。Python使用得越熟練,越容易準備新資料集以進行分析。

最好在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

指令打開:

$ 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

檔案,它的内容是:

你可以用下面的指令運作它(

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作為本地的計算環境,但它也可以部署到伺服器上遠端通路。這裡不做介紹,如果需要的話,鼓勵讀者自行到網上學習。
利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

要建立一個notebook,點選按鈕New,選擇“Python3”或“conda[預設項]”。如果是第一次,點選空格,輸入一行Python代碼。然後按Shift-Enter執行。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

當儲存notebook時(File目錄下的Save and Checkpoint),會建立一個字尾名為

.ipynb

的檔案。這是一個自包含檔案格式,包含目前筆記本中的所有内容(包括所有已評估的代碼輸出)。可以被其它Jupyter使用者加載和編輯。要加載存在的notebook,把它放到啟動notebook程序的相同目錄内。你可以用本書的示例代碼練習,見圖2-3。

雖然Jupyter notebook和IPython shell使用起來不同,本章中幾乎所有的指令和工具都可以通用。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

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。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

後面會仔細地學習函數。

自省

在變量前後使用問号?,可以顯示對象的資訊:

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展示了一部分,如移動光标。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks
利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

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指令。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

內建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
           
利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

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的語句不需要用分号結尾。但是,分号卻可以用來給同在一行的語句切分:

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

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

函數和對象方法調用

你可以用圓括号調用函數,傳遞零個或幾個參數,或者将傳回值給一個變量:

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

幾乎Python中的每個對象都有附加的函數,稱作方法,可以用來通路對象的内容。可以用下面的語句調用:

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

後面會有更多介紹。

變量和參數傳遞

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

假設将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]
           
利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

了解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

函數和相關的

hasattr

setattr

函數,使用這些函數可以高效編寫原生的、可重複使用的代碼。

鴨子類型

經常地,你可能不關心對象的類型,隻關心對象是否有某些方法或用途。這通常被稱為“鴨子類型”,來自“走起來像鴨子、叫起來像鴨子,那麼它就是鴨子”的說法。例如,你可以通過驗證一個對象是否遵循疊代協定,判斷它是可疊代的。對于許多對象,這意味着它有一個

__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
           

is

is not

常用來判斷一個變量是否為

None

,因為隻有一個

None

的執行個體:

In [41]: a = None

In [42]: a is None
Out[42]: True
           
利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

可變與不可變對象

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

子產品提供的。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

數值類型

Python的主要數值類型是

int

float

int

可以存儲任意大的數:

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。

将兩個字元串合并,會産生一個新的字元串:

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

方法,可以替換格式化的參數為字元串,産生一個新的字元串:

在這個字元串中,

  • {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

子產品提供了

datetime

date

time

類型。

datetime

類型結合了

date

time

,是最常使用的:

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

執行個體,你可以用

date

time

提取出各自的對象:

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列出了所有的格式化指令。

利用python進行資料分析(第二版)_第二章第2章 Python文法基礎,IPython和Jupyter Notebooks

當你聚類或對時間序列進行分組,替換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-expr

false-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的分支會被執行。

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