{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "<p style=\"text-align:center\">\n", " <a href=\"https://nbviewer.jupyter.org/github/twMr7/Python-Machine-Learning/blob/master/03-Syntax_Overview_2.ipynb\">\n", " Open In Jupyter nbviewer\n", " <img style=\"float: center;\" src=\"https://nbviewer.jupyter.org/static/img/nav_logo.svg\" width=\"120\" />\n", " </a>\n", "</p>" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[](https://colab.research.google.com/github/twMr7/Python-Machine-Learning/blob/master/03-Syntax_Overview_2.ipynb)\n", "\n", "# 3. 語法概要(下) Syntax Overview 2\n", "\n", "本章節介紹 Python 程式語言常見的語法規則。\n", "\n", "+ [**3.1 陳述句(Statements)**](#statements)\n", "+ [**3.2 迴圈及流程控制(Loops and Control flow)**](#loops-control-flow)\n", "+ [**3.3 函式(Functions)**](#functions)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<a id=\"statements\"></a>\n", "\n", "## 3.1 陳述句 Statements\n", "\n", "+ 以換行結束一個陳述句。\n", "+ 需要跨越多行的陳述句,可以在換行之前用反斜線 **‘\\\\’** 字元結束。\n", "+ 需要跨越多行的陳述句,若述句中有成對出現的大、中、小括號,可以在第一個括號 **‘{’**、**‘[’**、**‘(’** 尚未用對應的第二個括號關閉前換行。\n", "+ 多個簡單述句可以寫在同一行,述句間使用分號 **‘;’** 字元分隔。" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# 兩個指派陳述句\n", "a = 'good'\n", "b = 'bad'" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# 這是省略了 tuple () 括號的單一陳述句\n", "a, b = 'good', 'bad'" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# 這是跨越兩行的單一陳述句\n", "a, b = 'good',\\\n", " 'bad'" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# 這也是跨越兩行的單一陳述句\n", "a, b = ('good2',\n", " 'bad2')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# 這是寫在同一行的兩個陳述句\n", "a = 'good'; b = 'bad'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 命名原則\n", "\n", "以下原則適用於變數、函式、類別、類別方法、類別屬性等命名。\n", "\n", "+ 名字一定只能用底線 **‘_’** 或字母開始,後面則可接底線、字母、或數字。\n", "+ 有區分字母大小寫,例如: speed 和 Speed 是兩個不同的名字。\n", "+ 名字不能使用程式語言本身的保留字,例如: class, True, break, ... 等。\n", "\n", "以下慣例不是強制要求的原則,但實際一般設計都是會遵循這樣的原則。\n", "\n", "+ 避免使用開始及結束都有兩個底線的名字,如: `__name__`,這通常是 Python 系統本身的定義在使用的。 \n", "+ 兩個底線開始的名字,如: `__x`,定義在類別中代表是該類別私有(private)的。\n", "+ 一個底線開始的名字,如: `_x`,不會被 `from module import *` 這樣的述句 import。\n", "+ 避免使用單一底線 `_` 這個名字,因為這是在互動式運算時,Python 用來儲存前一次運算結果的。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 擴增指派 Augmented Assignment\n", "\n", "2.2 節中列出的算數運算子,除了 NOT **‘~’** 是一元運算子(uniary operator)以外,其他的運算子都是二元運算子(binary operator)。 針對兩個運算元(operand)的二元運算,常見將運算結果直接指派給其中一個運算元的同一個變數,例如:\n", "```\n", "x = x + y\n", "```\n", "這樣的指派陳述可以使用擴增的述句來取代:\n", "```\n", "x += y\n", "```\n", "視不同的資料類型而定,這樣的擴增述句有時也隱含著就地變更的最佳化處理。 \n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 2 , y = 25\n" ] } ], "source": [ "x, y = 3, 5\n", "x -= 1; y **= x\n", "print('x =', x, ', y =', y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 延伸可迭代卸載 Extended Iterable Unpacking\n", "\n", "2.4 節中介紹過一般的卸載,任何序列或可迭代(iterable)物件的值可以直接指派給一個變數名字的序列,只要對應的元素數量是一樣的就可以。 基本的語法如:\n", "```\n", "a, b, c, d = [1, 2, 3, 4]\n", "```\n", "Python 提供了延伸的語法,在變數名字前加上前置星號 **‘\\*’** ,就允許等號左邊的變數數量可任意調整:\n", "\n", "| 延伸可迭代卸載 | 結果 |\n", "|----------------------------|---------------------------|\n", "| `a, *b = [1, 2, 3, 4]` | a = 1, b = [2, 3, 4] |\n", "| `a, *b, c = [1, 2, 3, 4]` | a = 1, b = [2, 3], c = 4 |\n", "| `*a, b = [1, 2, 3, 4]` | a = [1, 2, 3], b = 4 |\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(0, [1, 2, 3, 4, 5, 6, 7, 8, 9])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# range 是可迭代物件,一樣適用延伸語法的卸載\n", "x, *y = range(10)\n", "x, y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 刪除物件 del 述句\n", "\n", "`del` 述句可以用來刪除變數、物件、物件屬性、序列的元素、序列的片段等。" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "原來 L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", "刪除某元素後 L = [1, 3, 5, 7, 9]\n" ] } ], "source": [ "# 刪除序列片段元素\n", "L = list(range(10))\n", "print('原來 L =', L)\n", "del L[:10:2]\n", "print('刪除某元素後 L =', L)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'L' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-9-824d9fb6d651>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# 刪除變數(及物件)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdel\u001b[0m \u001b[0mL\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mL\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'L' is not defined" ] } ], "source": [ "# 刪除變數(及物件)\n", "del L\n", "print(L)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 斷言 assert 述句\n", "\n", "`assert` 主要用於除錯目的。 程式碼的組織,通常會依據功能切割成不同的小區塊。 每個區塊的運算或多或少會假設某些前提,這些前提可能是在其他區塊已經處理過的結果,可能是系統必需要提供的資源,而在滿足前提的狀況下才得以繼續完成運算。\n", "\n", "`assert(條件)` 就是用來檢查這樣必要滿足的前提條件;若滿足斷言,程式繼續執行;若不滿足斷言條件,Python 會發出 AssertionError 的錯誤,然後中斷執行。" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "# 斷言序列長度一定滿足某個數量\n", "L = list(range(10))\n", "assert(len(L) == 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 區塊 Blocks\n", "\n", "Python 程式的語法中,區塊的程式碼使用 ***space*** 或 ***tab*** 縮排的層次來區隔不同執行條件的陳述句。 以下列的 `if` 條件述句為例,所有在 `x > y` 條件成立才會執行的程式區塊,都同樣放在4個 spaces(預設等於1個tab)的第一層縮排裡。\n", "```\n", "if x > y:\n", " z = x - y\n", " x = x + y\n", "```\n", "這樣的區塊陳述式,都會有一個以冒號 **‘:’** 結尾的區塊標頭陳述式。 如上例中的 `if x > y:`。 若需要巢狀的區塊陳述式,可以透過增加縮排的層次來達成。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § 文件字串 Documentation string\n", "\n", "除了前置 **‘#’** 符號用來註解外,Python 另外支援連續三個單引號或雙引號的區塊性文字註解。這樣的區塊性文字註解,若置於模組檔案、類別、函式、的最前面,稱為 ***docstrings***(其實這才是這語法設計的本意),會被儲存於該物件的 `__doc__` 屬性中。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<a id=\"loops-control-flow\"></a>\n", "\n", "## 3.2 迴圈及流程控制 Loops and Control flow\n", "\n", "對於一般陳述句,Python 會一行一行往下執行,直到碰到改變流程的陳述句:\n", "\n", "+ `if-elif-else`: 條件陳述式,選擇性執行。\n", "+ `while-else`: 條件式迴圈,迭代執行。\n", "+ `for-else`: 序列式迴圈,迭代執行。\n", "+ `pass`: 佔位述句。\n", "+ `break`: 中斷。\n", "+ `continue`: 繼續。\n", "+ `try-except-finally`: 例外處理。\n", "+ `raise`: 觸發例外狀況。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § if-elif-else 條件陳述式\n", "\n", "`if-elif-else` 條件陳述式由一個 `if` 邏輯測試條件,中間可有可無的 `elif` 測試條件,以及最後也是可有可無的 `else` 條件所組成。 每個條件都有對應的陳述式區塊,在條件測試為 True 時會被執行。 一般的形式像:\n", "```\n", "if test1:\n", " statements1\n", "elif test2:\n", " statements2\n", "else:\n", " statements3\n", "```\n", "\n", "簡單的 if-else 指派陳述如:\n", "```\n", "if test:\n", " x = a\n", "else:\n", " x = b\n", "```\n", "可以簡化成一行**三元運算**的陳述句:\n", "```\n", "x = a if test else b\n", "```\n" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "neither good nor bad, x is excellent\n" ] } ], "source": [ "a, b = 'good', 'bad'\n", "\n", "# 試著改變 x = 'good', 或 x = 'bad' 看看條件測試的結果\n", "x = 'excellent'\n", "#x = 'good'\n", "#x = 'bad'\n", "\n", "if x == a:\n", " print('x is also good')\n", "elif x == b:\n", " print('x is bad')\n", "else:\n", " print('neither good nor bad, x is {}'.format(x))" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "good\n" ] } ], "source": [ "# 三元運算陳述句\n", "print(a if x != 'bad' else b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § while-else 條件式迴圈\n", "\n", "`while` 迴圈包含了一個標頭的邏輯條件測試式,一個或多個縮排的區塊陳述式;以及一個可有可無的 `else` 條件,當迴圈不是被 `break` 離開的時候會被執行。\n", "```\n", "while test:\n", " statements\n", "else:\n", " loop_not_break_statements\n", "```\n" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "excellent\n" ] } ], "source": [ "# 一個一個字元複製直到字串完全相等\n", "x = 'excellent'\n", "c = ''\n", "while c != x:\n", " c += x[len(c)]\n", "\n", "print(c)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "excellent\n", "excellen\n", "excelle\n", "excell\n", "excel\n", "exce\n", "exc\n", "ex\n", "e\n", "exit while loop without break\n", "c = 0\n" ] } ], "source": [ "# 註: Python 的空字串會被視為是其他字串的子字串\n", "while c in x:\n", " print(c)\n", " if len(c) > 1:\n", " c = c[:-1]\n", " elif len(c) == 1:\n", " # 試看看 c = '0' 和 c = '' 的結果有甚麼不同?\n", " c = '0'\n", " else:\n", " break\n", "else:\n", " print('exit while loop without break')\n", "\n", "print('c =', c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § for-else 序列式迴圈\n", "\n", "`for` 迴圈是一個通用的迭代器(iterator),可以用來走訪任何***序列容器的元素***或***可迭代(iterable)物件***。 `for` 迴圈區塊標頭從要走訪的 iterable_object 中循序指派內容物參考到 item,然後針對每個 item 重複執行區塊中的陳述句。\n", "```\n", "for item in iterable_object:\n", " statements\n", "else:\n", " statements\n", "```\n", "\n", "於 while-else 相同,`for` 後面接著的 `else` 條件也是可有可無,當迴圈不是被 `break` 離開的時候會被執行。" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "e\n", "x\n", "c\n", "e\n", "l\n", "l\n", "e\n", "n\n", "t\n" ] } ], "source": [ "for c in x:\n", " print(c)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "e\n", "x\n", "c\n", "e\n" ] } ], "source": [ "for c in x:\n", " # 試看看在 '0123456789' 字串中加入任意一個 'e', 'x', 'c', 'l', 'n', 't' 的字元,結果有甚麼不同?\n", " if c not in '0123456789l':\n", " print(c)\n", " else:\n", " break\n", "else:\n", " print('all elements in x are iterated without break')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § pass 佔位述句\n", "\n", "什麼事都沒作,通常只是為了暫時滿足語法規則,用來佔個位置的述句。 \n", "```\n", "while True:\n", " pass\n", "```" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "# 沒作甚麼事,單純占用 CPU 時間的迴圈\n", "for i in range(2**25):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § break 中斷\n", "\n", "跳離最近的 `for` 或 `while` 迴圈,以及與其相伴的 `else` 區塊(如果有的話)。" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n" ] } ], "source": [ "for i in range(10):\n", " if i > 5: break\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § continue 繼續\n", "\n", "在迴圈中出現時,忽略以下所有述句,回到迴圈標頭繼續下一輪迴圈。" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n", "6\n", "7\n", "8\n", "9\n" ] } ], "source": [ "n = list(range(10))\n", "for i in n:\n", " if i < 5: continue\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § try-except-finally 例外處理\n", "\n", "程式時若發生非預期的例外狀況,Python會中斷執行並發出錯誤訊息。 若要避免非預期的程式中斷執行,可以將可能會出現錯誤的程式碼包在 `try` 區塊中,然後指定 `except` 例外處理子句。 如果沒有錯誤發生,則 `else`(optional)區塊的陳述會被執行。 不管錯誤有沒有發生,`finally` 子句永遠會被執行,用來處理善後事宜。基本的句型在 `try` 之後至少要有一個 `except` 或 `finally`。\n", "```\n", "try:\n", " statements\n", "except type:\n", " statements\n", "```\n", ",或\n", "```\n", "try:\n", " statements\n", "finally:\n", " statements\n", "```\n", ",或完整述句\n", "```\n", "try:\n", " statements\n", "except:\n", " statements\n", "else:\n", " statements\n", "finally:\n", " statements\n", "```\n", "\n", "`except` 子句後若沒有指定例外的類型,則任何未被處理的例外都會在此處理。" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "executing finally clause\n", "2.0\n", "executing finally clause\n", "divided by zero!\n" ] } ], "source": [ "# 注意: 為了展示 try-except-finally 語法,Divide() 函式回傳型態不一致,但這不是好的函式寫法\n", "def Divide(x, y):\n", " try:\n", " result = x / y\n", " except ZeroDivisionError:\n", " return 'divided by zero!'\n", " else:\n", " return result\n", " finally:\n", " print('executing finally clause')\n", "\n", "print(Divide(6, 3))\n", "print(Divide(6, 0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § raise 觸發例外狀況\n", "\n", "使用 `raise` 述句主動觸發例外狀況。" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "some exception raised\n", "executing finally clause\n", "None\n" ] } ], "source": [ "# 改寫上面的範例,加入任意主動觸發例外,並新增處理任何尚未處理的例外的子句。\n", "def Divide(x, y):\n", " try:\n", " #result = x / y\n", " raise IndexError\n", " except ZeroDivisionError:\n", " return 'divided by zero!'\n", " except:\n", " print('some exception raised')\n", " else:\n", " return result\n", " finally:\n", " print('executing finally clause')\n", "\n", "print(Divide(6, 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<a id=\"functions\"></a>\n", "\n", "## 3.3 函式 Functions\n", "\n", "初學者最直覺也最常用的程式結構,就是一行一行程式碼循序往下寫直到結束。 這種寫法用來解決一般稍微複雜的問題時,就很容易遇到難以維護或可讀性不佳的問題。 工程上我們常會採用一種簡化問題的技巧: Divide and Conquer,這樣的手法在軟體程式設計的領域到處可見。 在程式中使用函式,是將大問題拆解成小問題的一個基本步驟。 所以經常可以看到一個大型一點的程式,內容的結構其實就是一堆小工具函式的集合。 Python 中用來定義函式的關鍵保留字如下:\n", "\n", "+ `def`: 函式定義。\n", "+ `return`: 函式運算結果返回。\n", "+ `lambda`: 匿名函式。\n", "+ `yield`: 定義生成函式的返回結果。\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § def 函式定義\n", "\n", "函式以 `def` 關鍵字及指定的函式名字起始,後面使用小括號接著函式運算需要的所有參數。 區塊陳述包含函式所需的運算,運算結果的值使用 `return` 述句返回,若未明確指定,預設返回值為 `None`。\n", "\n", "```\n", "def name(arg_name1,..., arg_nameN=value):\n", " statements\n", " return value\n", "```\n", "\n", "| 可用參數定義格式 | 格式說明 |\n", "|--------------------|-------------------------------------------|\n", "| `arg_name1` | 一般參數,以位置或名字匹配 |\n", "| `arg_nameN=value` | 同一般參數,明確指定預設值 |\n", "\n", "**函式呼叫**\n", "\n", "```\n", "name(arg_value1,... arg_nameN=value,... *iterable_arg, **dict_arg)\n", "```\n", "\n", "| 可用參數定義格式 | 格式說明 |\n", "|-------------------|------------------------------------------------|\n", "| `arg_value1` | 以位置匹配傳遞參數 |\n", "| `arg_nameN=value` | 以名字匹配傳遞參數 |\n", "| `*iterable_arg` | 以序列或可迭代物件傳遞,並根據位置順序卸載匹配 |\n", "| `**dict_arg` | 以 dict 傳遞,並根據 key 卸載匹配 |\n" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "1\n", "1\n", "9\n", "1\n" ] } ], "source": [ "# 定義一個函式 Add(): 印出參數相加的結果\n", "def Add(a, b, c):\n", " print(a + b + c)\n", "\n", "# 一般參數傳遞\n", "Add(2, 3, -4)\n", "\n", "# 位置及名字匹配\n", "Add(2, c=-4, b=3)\n", "\n", "# 序列卸載匹配\n", "larg = [2, 3, -4]\n", "Add(*larg)\n", "\n", "# 可迭代物件卸載匹配\n", "Add(*range(2,5))\n", "\n", "# dict 卸載匹配\n", "darg = {'a':2, 'b':3, 'c':-4}\n", "Add(**darg)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "# 定義一個函式 Multiply(): 返回兩個輸入參數相乘的結果\n", "def Multiply(a, b):\n", " return a * b\n", "\n", "print(Multiply(2, 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### § 參數傳遞的基本認識\n", "\n", "1. 參數的傳遞,是**物件的參考**複製給了函式的 local 變數名字; 意即函式***參數所指向物件與呼叫端是同一物件***。\n", "2. 在函式內,重新指派新的物件參考給參數變數名字,不會影響呼叫端。\n", "3. 如果參數物件是可就地變更的類別,在函式內異動物件內容,呼叫端也會跟着改變。\n", "\n", "在設計程式時,建議遵循以下原則:\n", "1. 在函式中,避免更改參數物件的元素內容。\n", "2. 如果有需要回傳運算結果,請利用 `return` 來回傳,讓呼叫端來決定是否覆蓋原物件內容。\n" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b = 5 id = 140708452513728\n", "a = 5 id = 140708452513728\n", "a = 9 id = 140708452513856\n", "b = 5 id = 140708452513728\n" ] } ], "source": [ "# 指派新物件不影響呼叫端\n", "def f(a):\n", " # 一開始 a 與呼叫端的 b 參考同一物件\n", " print('a =', a, 'id =', id(a))\n", " # 指派新的 int 物件\n", " a = 9\n", " # 現在 a 參考到新物件\n", " print('a =', a, 'id =', id(a))\n", "\n", "b = 5\n", "# before function call\n", "print('b =', b, 'id =', id(b))\n", "\n", "f(b)\n", "\n", "# after function called\n", "print('b =', b, 'id =', id(b))" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = [2, 3] id = 2438221060032\n", "y = [4, 5, 6] id = 2438221060288\n", "L1 = [2, 3] id = 2438221060032\n", "L1 = [7, 8, 9] id = 2438220972352\n", "L2 = [4, 5, 6] id = 2438221060288\n", "L2 = ['changed', 5, 6] id = 2438221060288\n", "x = [2, 3] id = 2438221060032\n", "y = ['changed', 5, 6] id = 2438221060288\n" ] } ], "source": [ "# 可就地變更的類別,兩邊內容是同一份\n", "def f_list_args(L1, L2):\n", " print('L1 =', L1, 'id =', id(L1))\n", " # 指派新的 list 物件\n", " L1 = list(range(7, 10))\n", " print('L1 =', L1, 'id =', id(L1))\n", " \n", " print('L2 =', L2, 'id =', id(L2))\n", " # 不建議在函式中直接更改參數物件的元素內容。\n", " L2[0] = 'changed'\n", " print('L2 =', L2, 'id =', id(L2))\n", "\n", "\n", "x = [2, 3]\n", "y = [4, 5, 6]\n", "\n", "# before function call\n", "print('x =', x, 'id =', id(x))\n", "print('y =', y, 'id =', id(y))\n", "\n", "f_list_args(x, y)\n", "\n", "# after function called\n", "print('x =', x, 'id =', id(x))\n", "print('y =', y, 'id =', id(y))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "vec = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] id = 2438221058112\n", "vec = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] id = 2438221059520\n" ] } ], "source": [ "# 有需要回傳運算結果就用 return,讓呼叫端自己決定覆蓋原物件內容。\n", "def min_max_scaler(vec_in):\n", " min_v = min(vec_in)\n", " max_v = max(vec_in)\n", " interval = max_v - min_v\n", " vec_out = [(float(v) - min_v) / interval for v in vec_in]\n", " return vec_out, interval\n", "\n", "vec = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]\n", "\n", "# before function call\n", "print('vec =', vec, 'id =', id(vec))\n", "\n", "vec, _ = min_max_scaler(vec)\n", "\n", "# after function called\n", "print('vec =', vec, 'id =', id(vec))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § lambda 匿名函式\n", "\n", "`lambda` 通常用來定義非常簡短的函式,以及需要 callback 函式的參數傳遞。 基本形式為:\n", "```\n", "lambda arg1, ..., argN: expression\n", "```\n", "`lambda` 匿名函式的內容是單一運算,而不是區塊陳述句,設計的原意本來就不是用來執行複雜的運算。" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n", "6\n" ] } ], "source": [ "# 匿名函式沒有名字,但匿名函式的物件還是可以把參考指定給變數\n", "func_add = lambda x, y: x + y\n", "func_multiply = lambda x, y: x * y\n", "\n", "print(func_add(2 ,3))\n", "print(func_multiply(2, 3))" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n", "6\n" ] } ], "source": [ "# lambda 常見用來傳遞函式物件,當成 callback function \n", "def calc(op_func, a, b):\n", " return op_func(a, b)\n", "\n", "print(calc(func_add, 2, 3))\n", "print(calc(func_multiply, 2, 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § Generator 生成函式\n", "\n", "一般函式用 `return` 返回一次性的運算結果,相同的輸入參數可以預期得到相同的運算結果。 生成函式返回的是一個迭代子(iterator),使用 `yield` 返回每次迭代運算的結果。 生成函式的呼叫要使用 `for` 迴圈或內建函式 `next()`。\n", "```\n", "def name(arg,..., arg=value):\n", " statements\n", " yield value\n", "```" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "# 小於某數的偶數數列生成函式\n", "def evenrange(n):\n", " for i in range((n + 1) // 2):\n", " yield i * 2\n" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0, 2, 4, 6, " ] } ], "source": [ "# 在 for 迴圈中使用\n", "for ei in evenrange(7):\n", " print(ei, end=', ')" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 , 2 , 4 , 6\n" ] }, { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[1;32m<ipython-input-31-88a00559f698>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;31m# 超過數列盡頭會發出 StopIteration 的例外狀況\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mevengen\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mStopIteration\u001b[0m: " ] } ], "source": [ "# 使用內建函式 next()\n", "evengen = evenrange(7)\n", "print(next(evengen), ', ', next(evengen), ', ', next(evengen), ', ', next(evengen))\n", "\n", "# 超過數列盡頭會發出 StopIteration 的例外狀況\n", "print(next(evengen))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### § Generator Expressions 生成運算表示\n", "\n", "生成運算表示使用小括號將一個緊接著 `for` 的運算表示句包起來,對於序列容器或可迭代物件 S 進行操作,返回一個迭代子(iterator)。\n", "\n", "| 生成運算表示 | 說明 |\n", "|--------------------------------- |---------------------------------------|\n", "| `(運算表示句 for x in S)` | 針對每個 S 的成員 x 做運算 |\n", "| `(運算表示句 for x in S if 條件)` | 針對每個***符合條件***的成員 x 做運算 |\n", "\n", "當生成運算表示用在函式呼叫的參數,而且只有一個參數時,括號可以省略。" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<generator object <genexpr> at 0x00000237B14BFC80>\n" ] }, { "data": { "text/plain": [ "[1, 3, 5, 7]" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 小於某數的奇數數數列生成運算表示\n", "n = 7\n", "oddgen = (i * 2 + 1 for i in range((n + 1) // 2))\n", "\n", "print(oddgen)\n", "list(oddgen)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 3, 5, 7]" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 生成運算表示直接使用在函式參數中\n", "list(i * 2 + 1 for i in range((n + 1) // 2))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 2 }