Python 3 Tutorial 第二堂(2)數值與字串型態


在知道怎麼撰寫、執行第一個 Python 程式之後,接下來就要瞭解一下 Python 這個程式語言,接下來會很快地瀏覽過 Python 語言的重要基本元素,之後四個小時再從更多實際的範例中瞭解 Python 語言。

那麼,哪些東西才是語言中重要而基本的元素呢?Pascal 之父 Niklaus E. Writh 曾說過:

Algorithms + Data Structures = Programs

演算法與資料結構就等於程式,而一門語言提供的資料型態(Data type)、運算子(Operator)、程式碼封裝方式等,會影響演算法與資料結構的實作方式,因此接下來對於 Python 語言講解的重點將選定在:

  • 內建型態(Built-in type)、變數(Variable)與運算子(Operator)
  • 函式(Function)、類別(Class)、模組(Module)與套件(Package)

內建型態

在 Python 中,萬物皆物件!不過,物件導向並非 Python 的主要典範(Paradigm),Python 之父 Guido van Rossum 曾言,自己並非物件導向之信徒,在《Masterminds of Programming》書中,Guido van Rossum 說到:

Python 支援程序式的(Procedural)程式設計以及(某些程度)物件導向。這兩者沒太大不同,然而 Python 的程序式風格仍強烈受到物件影響(因為基礎的資料型態都是物件)。Python 支援小部份函數式(Functional)程式設計 – 不過它不像任何真正的函數式語言。

無論如何,接下來要認識的內建型態都是物件,像是:

  • 數值型態(Numeric type) - int, long, float, bool, complex
  • 字串型態(String type)
  • 容器型態(Container type) - list, set, dict, tuple

這篇文章中,我們要先來認識數值型態與字串型態 …

數值型態

我們直接進入 Python 互動環境來瞭解這些型態吧!可以使用 type 函式來得知值的型態。首先是數值型態:

~$ python3.5
Python 3.5.0+ (default, Oct 11 2015, 09:05:38) 
[GCC 5.2.1 20151010] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> type(1)          # 1 是什麼型態?
<class 'int'>
>>> type(1111111111111111111111111111111111111111) # 很長的整數也是 int
<class 'int'> 
>>> type(3.14)       # 浮點數是 float 型態
<class 'float'>
>>> type(True)       # 布林值是 bool 型態
<class 'bool'>       
>>> type(3 + 4j)     # 支援複數的 complex 型態
<class 'complex'>
>>> 2 ** 100         # 2 的 100 次方
1267650600228229401496703205376
>>> 

上面的範例中使用了 ** 運算子進行次方運算,Python 中當然有加(+)、減(-)、乘(*)等運算子,至於除法則有 /// 兩個,後者的運算結果會只留下整數部份,在 Python 3.x 中,/ 一定是產生浮點數,而 // 的話整數與整數運算會產生整數,如果是整數與浮點數進行 // 會產生浮點數:

>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
>>> 10 / 3.0
3.3333333333333335
>>> 10 // 3.0
3.0
>>>

Python 2.x 的 / 行為與 Python 3.x 不同,例如,在 Python 2.7 中,整數與整數進行 / 運算會產生整數,原因可看看 Python Taiwan 社團的討論

>>> 10 / 3
3
>>> 10 // 3
3
>>> 10 / 3.0
3.3333333333333335
>>> 10 // 3.0
3.0
>>>

在浮點數精確度的表現上,也有必須注意的地方。例如:

>>> 1.0 - 0.8
0.19999999999999996
>>> print(1.0 - 0.8)
0.19999999999999996
>>>

多數 CPU 與浮點運算器多採用 IEEE754 浮點數運算(IEEE 754 floating-point arithmetic),某些浮點數本身就有誤差,這是每個程式人都應該知道的事,在 Python 3.x 中,忠實地呈現了這個事實,即使是透過 print() 函式也是忠實地呈現誤差。

不過,在 Python 2.x 時,你看到的卻會是:

>>> 1.0 - 0.8
0.19999999999999996
>>> print (1.0 - 0.8)
0.2
>>>

Python 2.x 互動環境在顯示值時,會採用制式的(Offical)字串表示,而 print 語句則採用了非正式的(Informal)字串表示;技術上來說,Python 2.x 互動環境會利用物件的 __repr__ 方法傳回的字串來顯示,print 語句會利用物件的 __str__ 方法傳回的字串來顯示,例如,在 Python 2.x 中:

>>> repr(1.0 - 0.8)
'0.19999999999999996'
>>> str(1.0 - 0.8)
'0.2'
>>>

雖然你也可以用 (1.0 - 0.8).__repr__()(1.0 - 0.8).__str__() 來取得字串,不過在 Python 中,底線開頭暗示著你不要直接呼叫或使用,因此上面的示範中,使用 reprstr 函式來取得字串。日後你繼續學習 Python 語言的過程中,你會知道,__repr____str__ 是可以自行定義的。

簡單來說,__repr__ 來傳回沒有岐義的(Unambiguous)字串表示,用 __str__ 來傳回具可讀性的(Readable)字串表示,在 Python 3.x 中,依然可以定義 __repr____str__,不過,對於浮點數來說,顯然地採取了更嚴格的態度,因此無論是 repr(1.0 - 0.8) 或是 str(1.0 - 0.8),在 Python 3.x 中,都是傳回 '0.19999999999999996' 的結果。

想要精確地表示浮點數,語言都會提供程式庫,Python 中使用 decimal 模組中的 Decimal 類別來進行處理。例如:

>>> import decimal
>>> a = decimal.Decimal('1.0')
>>> b = decimal.Decimal('0.8')
>>> a - b
Decimal('0.2')
>>> print(a - b)
0.2
>>> 

字串型態

如果要在 Python 中表示字串,可以使用 ''"" 包括文字,兩者在 Python 中具相同作用,都可產生 str 實例,可視情況互換。例如:

>>> "Just'in"
"Just'in"
>>> 'Just"in'
'Just"in'
>>> 'c:\workspace'
'c:\\workspace'
>>> "c:\workspace"
'c:\\workspace'
>>>

可以看到,在某些情況下,你不用特別略過(Escape) \ 字元,然而在底下這種情況下就需要了:

>>> 'c:\todo'
'c:\todo'
>>> print('c:\todo')
c:  odo
>>> print('c:\\todo')
c:\todo
>>> 

可以在字串前加上 r,表示接下來後面是原始字串(Raw string)表示,這樣 Python 就會忠實表示後續的字串,技術上來說,會自動為你處理需要略過的字元。例如:

>>> r'c:\todo'
'c:\\todo'
>>> print(r'c:\todo')
c:\todo
>>> 

Python 中的字串不可變(Immutable),你無法改變已建立的字串內容;想得知字串實際的字元長度,可以使用 len 函式;可以使用 for 來迭代字串;可以使用 in 來測試字串是否包括某子字串;可以使用 + 來串接字串;可以使用 * 來複製字串。例如:

>>> name = 'Justin'
>>> len(name)
6
>>> for ch in name:
...     print(ch)
... 
J
u
s
t
i
n
>>> 'Just' in name
True
>>> name + name
'JustinJustin'
>>> name * 3
'JustinJustinJustin'
>>> 

可以使用 [] 指定索引來取得字串中的某個字元,索引從 0 開始,可以是正數或負數,負數表示從尾端開始計數,例如 -1 就是最後一個字元, -2 就是倒數第二個字元,依此類推。例如:

>>> lang = 'Python'
>>> lang[0]
'P'
>>> lang[-1]
'n'
>>> 

[] 也可以用來切割字串,例如:

>>> lang[1:5] # 取得索引 1 至 5(包括 1 但不包括 5)的子字串
'ytho'
>>> lang[0:] # 省略結尾索引,表示取至尾端
'Python'
>>> lang[:6] # 省略起始索引,表示從 0 開始
'Python'
>>> 

[] 還可以指定間距(Gap),例如取索引 0 至 6,每 2 個間距的方式取子字串:

>>> lang[0:6:2]
'Pto'
>>>

'Python''P''y' 算一個間距,'y''t' 之間也是一個間距,依此類推 'Python'[0:6:2] 取得的就是 'Pto',將以上對 [] 的運算方式組合在一起,可以得到一個有趣的反轉字串方式 [::-1]

>>> lang[::-1]
'nohtyP'
>>>

如果要進行字串格式化,以下是舊式寫法:

>>> '%d %.2f %s' % (1, 99.3, 'Justin')
'1 99.30 Justin'
>>> '%(real)s is %(nick)s' % {'real' : 'Justin', 'nick' : 'caterpillar'}
'Justin is caterpillar'
>>> 

技術上來說,字串物件將 % 定義為格式化操作,可以接受 tupledict 型態,不過這種寫法可讀性不好,從 Python 2.6 之後,建議使用字串的 format 方法來取代 % 操作,這在 Python 3.x 之後也是如此:

>>> '{0} is {1}'.format('Justin', 'caterpillar')
'Justin is caterpillar'
>>> '{real} is {nick}'.format(real = 'Justin', nick = 'caterpillar')
'Justin is caterpillar'
>>> '{0} is {nick}'.format('Justin', nick = 'caterpillar')
'Justin is caterpillar'
>>> import sys
>>> 'My platform is {pc.platform}'.format(pc = sys)
'My platform is linux'
>>> 

容器型態及 ifforwhile 等流程語法,會在下篇文章中說明,在這之前,進一步來玩玩 Python 互動環境中,之後你也可以自行進行上面看到的範例練習。

練習 2:Python 互動模式與直譯器指令

開啟一個終端機,鍵入 python3.5 指令進入互動模式,接著鍵入以下指令,你會看到什麼?

  • 1 + 2
  • _                         # _ 代表最後一次執行結果
  • _ + 3
  • help()                # 進入文件查詢介面
  • len                     # 查詢 len 函式說明
  • keywords            # 查詢關鍵字有哪些
  • quit(或是 q) # 離開文件查詢介面
  • help(len)          # 直接查詢 len 函式說明
  • Ctrl + D              # 離開互動模式

離開互動模式後,直接鍵入以下指令會看到什麼?

python3.5 -h
python3.5 -c 'print "Hello! Python!"'
python3.5 -c 'help(len)'
python3.5 -c 'import this'

-h 引數會顯示使用說明,-c 會直譯後續給定的字串文字,因此 python -c 'help(len)' 就是顯示 len 函式的說明,import this 會顯示一段文件,代表 Python 的哲學,也是發展的規範:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!