# Python Programming - Lecture 01 資料型別與變數宣告 ###### tags: `python-programming` ## 1.1 資料 生活中充滿了資料,舉凡各式統計數據、報章雜誌、Youtube 上的影音、社群網站的貼文等等,這些東西都是記載了重要資訊的**資料 (Data)**。在網際網路蓬勃發展的現代,人們每天創造的資料量高達數個 EB (GB 的平方)。要在這麼大量的資料裏面歸納萃取出有用的**資訊 (Information)**,正是資訊工程的一大任務。你或許會聽過, Python 很適合用來進行網頁爬蟲、資料探勘、機器學習等等,這些其實都是在試圖從大量的資料中抽出有用的資訊。 我們舉一個簡單的例子,這是我今天一天的開銷: * 早餐 35 元 * 午餐 80 元 * 手搖杯 30 元 * 晚餐 100 元 * 加油 120 元 這個列表可以視為一筆資料,這筆資料內有五個細項,分別代表每一筆開銷的用途和金額。現在,我好奇今天我總共花了多少錢,那麼我就可以把他加起來 ```python >>> 35 + 80 + 30 + 100 + 120 365 ``` 365 這個總額,就可以說是我從這筆資料中得出的資訊,是透過統計的方式得到的。人們可以透過許多方式從資料中抽取資訊,若這個方式是可以被電腦自動化的,那我們就可以藉助程式語言的力量來幫我們減少抽取資訊所需的時間和人力。 ## 1.2 資料型別 現實中的資料種類看似琳琅滿目,但實際上深入追究,他們都可以由最基本的兩種資料組合而成。還記得你小學一年級的時候,唯二重要的科目是哪兩個科目嗎?沒錯,就是國語和數學。這兩個科目之所以被安排在小學一年級就得開始教,正是因為人類的智慧都是奠基在語言和數學上的。你必須先會寫字,才有辦法紀錄事物或想法;你必須要先學會算術,才有辦法發展進階的系統性科學。**文字和數字**這兩種資料,幾乎是所有的程式語言中,必定會最先學到的兩種**資料型別 (Data type)**。~~(但不是因為我們先上國語和數學拉這我唬爛ㄉ)~~ 在 Python 中,所有資料都必定屬於某一個資料型別。我們可以使用 `type()` 函式來幫助我們觀察不同資料的資料型別: ```python >>> type(1) <class 'int'> >>> type(1.0) <class 'float'> >>> type('1') <class 'str'> ``` 在以上執行結果中,我們可以觀察到,明明同樣都是 "1" ,稍微不同寫法就會導致他們以不同的資料型別來儲存。 * 如果寫成 `1` , Python 會覺得這個東西是**整數 (integer)**。 * 如果寫成 `1.0` , Python 會覺得這個東西是**浮點數 (floating point)**,因為數字當中帶有一個小數點。 * 如果寫成 `'1'` , Python 會覺得這個東西是**字串 (string)**,因為左右兩邊有引號。 使用正確的資料型別來儲存資料是很重要的。舉個例子:有些時候我們會認為一串數字被當成字串看待比較合理,像是電話號碼 "0912345678" ,雖然整個都是數字,但開頭的 0 其實是不能省略的,此時如果誤用整數來儲存,就會失去它電話號碼的意義了。除了賦予資料正確的意義,不同的資料型別也有不同的內建功能,這些功能我們會在 Lec 02 中提及。 以下我們先簡單認識 Python 中常用到的內建資料型別,完整的內建型別列表可以在[官方文件](https://docs.python.org/3/library/stdtypes.html)當中找到。 ### 1.2.1 數值型別 (Numeric Types) 常用的 Python 內建數值型別有**整數和浮點數**,但其實 Python 也支援複數等高階的數值型別,有需要用到的時候可以再查詢文件。以下僅針對整數與浮點數進行說明。 **整數型別(`int`)** 是最基本的數值型別,可以用來表示所有的整數。Python 的整數預設是十進制,但也可以透過在數字前添加前綴 (prefix) 來表示十六進制、八進制或二進制整數。(關於進制轉換請參考[附錄](https://hackmd.io/@kaeteyaruyo/python-programming-appendix)) ```python >>> 10 # No prefix for decimal 10 >>> 0x10 # 0x for hexadecimal 16 >>> 0o10 # 0o for octal 8 >>> 0b10 # 0b for binary 2 ``` **浮點數型別(`float`)** 用來表示帶有小數點的數字。舉凡商品售價、溫度溼度、身高體重等等,許多數值資料都是用小數來表達的。Python 的浮點數是[雙精度浮點數](https://zh.wikipedia.org/zh-tw/%E9%9B%99%E7%B2%BE%E5%BA%A6%E6%B5%AE%E9%BB%9E%E6%95%B8),可以用來表示非常非常大的數字,或是非常非常接近 0 的小數字,但過大或過小的數字會以科學記號表示就是了。此外,無限大 (`inf`) 和未定義數 (`nan`) 這兩個特別的數字也是屬於浮點數的類型。 ```python >>> type(1.23) <class 'float'> >>> 0.0000000000000000000000000001 1e-28 >>> type(0.0000000000000000000000000001) <class 'float'> >>> 1e+24 1e+24 >>> type(1e+24) <class 'float'> >>> 1e+30000 inf >>> type(1e+30000) <class 'float'> ``` 浮點數和整數是可以互相轉換的。在 Python 中,內建型別都會有一個跟他名字一樣的轉換函式,可以將其他型別的資料轉換成該型別。 * 將整數轉換成浮點數,可以使用 `float()` 函式。 ```python >>> float(1) 1.0 >>> float(1234567) 1234567.0 >>> float(100000000000000000000000000000000) 1e+32 ``` * 將浮點數轉換成整數,可以使用 `int()` 函式。 這麼做相當於是進行**無條件捨去**。 ```python >>> int(1.0) 1 >>> int(99.999) 99 >>> int(1e16) 10000000000000000 ``` 雖然浮點數包含整數,理論上來說整數可以用浮點數來表示,但是通常我們會認為連續的數值資料才用浮點數儲存,可數的或是離散的資料,應當要用整數儲存。 :::info :memo: **Python 的大整數** 資工系的考卷最喜歡問一個問題: C 語言裏面,整數最大可以到多大呢?資工系有成功畢業的孩子們應該要可以正確回答,一個無號長整數 (`unsigned long`) 頂多佔 64 bits 的記憶體,因此最大只能到 $2^{64} - 1 = 18446744073709551615$ 而已。 然而 Python 不來這一套。誰管你一個整數佔多少記憶體,難道我大 Python 如此高階的語言還要屈就於一個數字只能佔固定數量的記憶體嗎?有時在進行科學運算的過程中,真的會需要算到很大很大的整數(具體來說就是比 18446744073709551615 還要大),因此 Python 有內建的大整數功能,能夠對付任何長度的整數。 "Google" 這個品牌名稱,是從同音的 "googol" 變化而來的。 "googol" 的意思是「10 的 100 次方」,這個數字在大部分的程式語言中都是無法表示的,但在 Python 中,要算出 1 googol 是很簡單的事: ```python >>> 10 ** 100 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` 你也可以試試看對這些大整數進行加減乘除,看看會不會得到正確的結果~ ::: ### 1.2.2 字串型別 (String Type) Python 內建用來表達文字的型別就只有一種,那就是**字串型別 (`str`)**。所有的符號只要被引號包起來,不管裡面裝的是文字、數字還是程式碼,通通都會被當成是一串普通的字。 ```python >>> "abc" 'abc' >>> type("abc") <class 'str'> >>> "123" '123' >>> type("123") <class 'str'> >>> "123 + 456" '123 + 456' >>> type("123 + 456") <class 'str'> ``` 在 Python 中,可以使用的引號有**單引號 `''` 和雙引號 `""`**,這兩種引號是沒有功能上的區別的,你習慣打哪一種就打哪一種。但必須注意開頭是哪一個引號,尾巴就必須要用那個引號關起來,不可以開頭是單引號,尾巴卻是雙引號。 ```python >>> "abc" 'abc' >>> 'abc' 'abc' >>> 'abc" File "<stdin>", line 1 'abc" ^ SyntaxError: EOL while scanning string literal ``` 之前有提過,程式語言是用英文和標點符號寫出來的,因此理論上,程式裡的所有東西都應該要可以變成字串。我們可以使用 `str()` 函式把其他資料型別的東西轉成字串版本。 ```python >>> str(123) '123' >>> str(123.456) '123.456' >>> str(10**100) '10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' >>> str(1e32) '1e+32' >>> str(True) 'True' >>> str(False) 'False' ``` 注意到 `1e32` 的例子,有沒有發現,其實輸出出來的字串和預期中的有微小的差別?這並不是程式出 Bug 了自己幫你補上一個 `+` ,而是浮點數被字串化的過程本來就是這樣設計的。沒錯,`str()` 並不是你在小括號裡打什麼,他就原封不動的把那串字變成字串。在小括號裡的東西必定會屬於某一種資料型別,如果該資料型別有實作把自己變成字串的功能的話, `str()` 就會去呼叫這個功能然後輸出結果。因此,一個資料型別最後會以什麼形式被輸出成字串,其實是可以另外設計的。詳細的作法我們會在往後的課程中提到。 如果字串裏面的字是合法的 Python 數值,在這樣的特殊情況下,字串可以被轉換成數值型別。 ```python >>> int('123') 123 >>> float('1.23') 1.23 >>> float('1000') 1000.0 >>> float('1e-5') 1e-05 >>> float('inf') inf ``` 字串這個型別對我們輸出程式結果來說是非常重要的,人類畢竟還是比較懂人類語言而不是程式語言,我們通常會希望計算的結果可以在有一些說明文字輔助的情況下被顯示出來,這時我們就會需要有一個可以承載人類文字的資料型別,這就是字串的功能。 ### 1.2.3 布林型別 (Boolean Types) **布林型別 (`bool`)** 是邏輯運算的時候會使用到的一種資料型別。在下一章,我們將會學到一種叫作**條件判斷**的運算,布林型別就是這種運算的輸出結果。布林型別只有兩種值,那就是**真 `True`** 和**假 `False`**,畢竟當我們在判斷一件事的真假時,結果就只會有這兩種而已,不會有又真又假的這種結果。舉個最簡單的例子: ```python >>> 10 > 3 True >>> 2 < 1 False ``` 10 有沒有大於 3 呢?這個問題的答案,要嘛是有,要嘛是沒有,不會有又沒有。有學過小學數學的我們應該都知道 10 的確大於 3,所以 `10 > 3` 這個條件判斷的結果就是「真 (`True`)」。同樣道理, 2 沒有小於 1 ,所以 `2 < 1` 這個條件判斷的結果就是「假 (`False`)」。 條件判斷在流程控制當中是必備的步驟,我們將會在第三章學到如何進行流程控制,但在這裡可以先簡單提一下流程控制的概念。簡單來說,流程控制就是「如果怎樣怎樣,那就做什麼」這樣的行為。「如果」後面的那句話,就是影響我們要做或是不要做某件事的判斷依據,因此我們把這句話叫作條件判斷。條件判斷的結果如果是真,那我們就會執行某些動作;如果是假,我們就會去執行另外的步驟。這就是布林型別資料的功能。 值得注意的是,布林型別也是可以和其他型別互相轉換的,我們可以使用 `bool()` 函式來進行轉換。其他型別要轉換成布林型別時,會因為內容物的差異而得到不同的結果。以整數來說,只有整數 0 在轉換成布林值時會是 `False` ,其餘的整數值都會得到 `True`: ```python >>> bool(0) False >>> bool(1) True >>> bool(10) True >>> bool(-5) True ``` 像 0 這種轉換成布林值時會變成 `False` 的數值,我們叫作 **Falsy value**。 常見型別的 Falsy value 有: * 整數: `0` * 浮點數: `0.0` * 複數: `0 + 0j` * 字串: `''` (空字串) * 陣列: `[]` (空陣列) * `None` (參見 [1.2.5](#125-None-型別-None-Type)) 在上述各型別當中,除了列出來的這些值在轉換成布林值時會得到 `False` 之外,剩下其餘的值都會得到 `True` 的結果。Falsy value 在流程控制的條件當中,可以直接被當作 `False` 使用。詳細的使用方式我們會在第三章當中詳述。 ### 1.2.4 各式資料結構 上述資料型別都是不可能再被分割成更小單位的基本資料型別,但有時我們可能會需要一次處理一筆以上的資料,這時就會需要用到各式各樣的**資料結構 (Data structure)** 來儲存我們的資料。 Python 內建了各種好用的資料結構,最常被使用到的有**列表 (`list`), 數組 (`tuple`) 和字典 (`dict`)**。這些資料結構本身其實也是一種資料型別: ```python >>> type([]) <class 'list'> >>> type(()) <class 'tuple'> >>> type({}) <class 'dict'> ``` 關於資料結構的細節,我們會在第五、第六章詳細說明。 ### 1.2.5 None 型別 (None Type) None 型別是一種很特別的資料型別。在 Python 中,只有 `None` 這個東西屬於 None 型別。 ```python >>> type(None) <class 'NoneType'> ``` 在某些時候,我們的程式會需要表達「什麼都沒有」這樣的意思,在這樣的情況下,不管使用什麼其他有意義的資料型別感覺都會怪怪的,我們就會用到 None 型別。舉個例子來說:假設今天你的程式想要讀取一個存在於檔案系統中的文字檔,結果檔名不小心打錯了,導致程式找不到那個檔案,那麼這時開檔的結果要回傳什麼呢?要回傳一個 0 嗎?還是要回傳空字串?還是要回傳一個空的列表呢?不管回傳什麼,好像感覺都怪怪的。這時我們就可以回傳一個 `None` ,表示我「什麼都沒有讀到」,這樣反而才是最合理的結果。 ## 1.3 變數宣告 在程式語言中,**變數 (variable)** 是用來存放資料的東西。我個人很喜歡這麼描述變數的功能:「**給資料取一個名字**」。由於我們是在學習 Python ,不是在學習 C 語言,所以我們可以先暫且略過複雜又深奧的記憶體配置細節,你只要簡單的知道:你可以給你的資料取一個名字,從此之後用這個名字呼叫他,這個名字就叫作「變數」。 ```python >>> x = 10 >>> x 10 >>> type(x) <class 'int'> ``` 以上這三句話已經明確地示範了變數的用途: * `x = 10`:這句話叫作**變數宣告 (variable declaration)**,在這裡我們宣告了一個叫做 `x` 的變數。簡單來說就是「我有一筆資料 `10` ,現在我要叫他 `x`」的意思。句子中的 `=` 並不是數學上的「等於」的意思,而是「**賦值 (assign)**」的意思。**賦值這個動作,可以想成是把等號右邊的東西,放進貼了等號左邊名字的箱子裏面。** 因此,你不可以隨意交換等號左右兩邊的東西,這會導致完全相反的意思。 ```python >>> 10 = x File "<stdin>", line 1 SyntaxError: cannot assign to literal ``` 像這樣,如果我嘗試要把 `x` 放進叫作 `10` 的箱子裏面, Python 直譯器就會哇哇叫了,因為 `10` 是一種數值,不可以被當作箱子的名字。 * `x`:試著把 `x` 的內容物倒出來看看,會發現裏面裝的東西就是 `10`,跟我們一開始放進去的一樣。從此之後,你在任何地方叫 `x` , `x` 都會是 `10` 了。 * `type(x)`:試著把 `x` 的資料型別印出來看看,會發現是整數。因為 `x` 裏面裝的是整數,**變數的資料型別都是由裡頭裝的東西決定的**,所以 `x` 才會是整數型別。 給自己的資料取一個好名字是很重要的,這會幫助我們在閱讀程式碼時可以更容易理解程式的意義。舉一個實際例子: ```python a = 0.1 b = 520 c = int(b * (1 + a)) print(c) ``` 在這個例子中,上方的程式碼使用了 `a`, `b`, `c` 這種沒有意義的變數名稱,你很難一眼看出這是在算什麼,儘管知道運算結果會是 `572` ,但你也無法了解這個數字是什麼意思。但如果我把變數名稱換成下面這樣: ```python tax = 0.1 price_before_tax = 520 price = int(price_before_tax * (1 + tax)) print(price) ``` 把程式碼閱讀過去,應該可以很輕易的知道這段程式碼是在計算商品含稅價,`0.1` 是消費稅率的意思, `520` 是商品未稅價的意思,計算出來的 `572` 原來是商品含稅價的意思。這兩支整個程式的程式邏輯本質上是沒有差異的,所有的賦值與運算的動作通通都一樣,僅僅是稱呼這些資料的方式不一樣而已。但這樣的差異卻會對程式碼的**可讀性 (readability)** 造成很大的影響。因此為自己的資料好好取名字,真的是一件很重要的事。 在你宣告一個變數之後,並不是從此之後這個名字就永遠只能指稱這一筆資料了。 Python 允許**重複賦值**的動作,也就是一個變數可以被多次賦值,像這樣: ```python >>> x = 10 >>> x 10 >>> x = 100.0 >>> x 100.0 >>> x = '123' >>> x '123' ``` 你或許會注意到,這三次的賦值放進去的是不同型別的東西,這在 Python 這種**動態型別 (Dynamic type)** 的語言當中是被允許的(反之,在靜態型別 (static type) 的語言當中就不行)。變數的資料型別是在被賦值的當下決定的,每一次的賦值都會重新決定一次該變數的型別。 現在我們已經有程式的最基本元素了,接著就可以開始運用這些資料來進行運算。 :::info :memo: **變數的合法命名** 剛剛我們有提到一句「 "10" 不可以被當作變數名稱」,顯然 Python 的變數是不可以亂取名字的。Python 變數命名必須遵守某些規則 (rule),否則程式就會無法執行。以下是這些規則當中最重要的幾個: * 變數名稱必須用英文字母或是下底線 (`_`) 當第一個字 * 變數名稱不可以用數字開頭 * 變數名稱不可以是關鍵字 "10" 這個變數名稱違反了第二個規則,所以才會不能當作變數名稱。關於更詳細的合法的 Python 變數命名規則,可以參考[官網的文件](https://docs.python.org/3/reference/lexical_analysis.html#identifiers)。 除了這些必須遵守的規則之外,我們程式設計師自己會有一套命名的習慣 (convention) ,這些規則雖然不遵守也沒關係,但是遵守了大家會更容易看懂你的程式碼。有興趣的話可以參考這份 [Naming Convention Guideline](https://peps.python.org/pep-0008/#naming-conventions),這裡就不多贅述了。 ::: ---- [<< Lec 00 - 執行環境](https://hackmd.io/@kaeteyaruyo/python-programming-00) | [目錄](https://hackmd.io/@kaeteyaruyo/python-programming-index) | [Lec 02 - 運算式 >>](https://hackmd.io/@kaeteyaruyo/python-programming-02)