關於Python的包裝機制

Code Packing Mechanism in Python

Posted by imprld01 on Thursday, August 17, 2017

目錄


簡介

來源www.python.org - Python, GPL, Wikipedia

要寫Python很容易,只要安裝好環境之後,在terminal啟動python,直接打上程式碼就能得到執行結果,例如輸入2+5,Python就會顯示7。 不過,如果想要使用Python撰寫有規模的程式,應該還要學習Python的函式、類別、模組與套件的寫法,才能設計出容易維護的程式碼。

本篇預設讀者已經有些程式基礎,所以有關於Python程式語法與一般程式基礎的部分會較少談到。 會寫這篇主要是紀錄一下如何將一般C/C++的程式設計架構引入Python中, 畢竟以Python作為首次接觸的程式語言來說,程式設計架構其實是較為不重要的, 屬於直譯式語言的Python,與其他編譯式語言不同,能夠輕易跳脫編譯式語言嚴謹的框架, 但是也因此如此,Python的程式碼寫起來可能會更為冗長,這也與寫程式的習慣與喜好有關。

組織性是程式撰寫十分重要的原則之一,一份雜亂無章的程式碼,與一份組織良好的程式碼相比, 一份組織良好程式碼的易讀性、易用性與重用性會更高一些, 程式碼應該讓提供閱讀的人(包括自己與未來的自己)一目了然,甚至能減少一些撰寫軟體設計文件的功夫。

構築程式的思考重點

  1. 軟體架構設計:物件導向設計(Object-Oriented)與設計模式(Design Pattern)
  2. 程式組織方式:封裝(Encapsulate)、分離(Separate)與重構(Refactoring)

Python的封裝機制

本篇介紹Python程式碼的封裝機制包括:

  1. 套件(Package)
  2. 模組(Module)
  3. 類別(Class)
  4. 函式(Function)

函式(Function)

函式能使程式碼較為簡潔、具可讀性,並能達到重用的目的。

有過程式經驗的人會發現函式的重要性,在撰寫過程經常會出現相同邏輯的程式碼, 而之間的差異只是變數數值不相同,這時候就會使用函式達到重用性, 意思是只要寫一次程式碼(函式),如果之後又需要使用到相同邏輯的程式碼時, 只要引入該函數,呼叫先前的程式碼(函式)並且輸入所需要的引數即可。例如:

avg1 = (x + y) / 2
avg2 = (5 + 9) / 2

從上面的範例可以明顯看到兩段相似邏輯的程式碼,這兩段程式碼可能散落在不同的程式中, 或者在同個程式中出現許多次,這時候我們就可以考慮將該程式碼封裝成一個函式。 這是個簡單的例子,可能讀者感受不到函式的重要性,但我們可以進一步擴展情境, 如果該程式邏輯變得更加複雜、長度變得更長,可以想像得到,相似且龐大的程式碼將混雜在程式碼中, 這會使程式碼難以維護、降低可讀性。以下我們將該程式碼封裝:

def calAvg(input1, input2):
    return (input1 + input2) / 2

接著,我們就可以將相似的程式碼替換為函式,如下所示:

	avg1 = calAvg(x, y)
	avg2 = calAvg(5, 9)

這次我們看看另一個範例,這次我們試著將函式變得稍微複雜:

	def myAvg(*inputs):
		sum = 0
		for input in inputs:
			sum += input
		return sum / len(inputs)
	avg1 = myAvg(10, 20, 30)
	avg2 = myAvg(30, 20, 60, 10, 30)

透過上例,讀者應該可以明顯感受到函式的作用! 函式可以幫助我們獲得較簡潔的程式碼,函式的使用還有釐清邏輯的功用, 我們可以透過函式的名稱清楚的理解該段程式碼的目的, 否則我們需要細讀程式碼才能了解其目的, 所以善用函式封裝程式碼會是將來撰寫大型程式的好幫手。

最後,順便介紹方便的匿名函式機制。 事實上,函式名稱本身也是個變數,變數裡儲存的是該函式的記憶體位址, 所以可以如以下的程式碼般將calAvg的數值存入avgFunc1變數中, 而匿名函式的語法lambda則是如字面上所說可以創造一個沒有名稱的函式。 讀者可能會疑惑這個匿名函式究竟能運用於何處?從上面的例子來看, 匿名函式較不具有一般函式的重用性,它並無法被重複使用在其他的程式上, 唯一可以使它稍微具有重用性的情況只有如下面的例子所示, 將匿名函式的記憶體位址存在一個有名稱的變數中,再來使用該函式, 但這並不能顯示匿名函式存在的原因。

	avgFunc1 = calAvg
	avgFunc2 = lambda input1, input2: (input1 + input2) / 2
	avg3 = avgFunc1(5, 9)
	avg4 = avgFunc2(5, 9)
	avg5 = avgFunc2(8, 12)

從上面的例子,我們可以發現匿名函式似乎並不是那麼有用。 其實,它本來就沒有被設計於實現重用性,而是用來滿足快速的、暫時性的函式需求。 可以試想當前程式固定需要一個函式,而該函式基本上不會再於其他程式中被使用, 此時就可以採用匿名函式。讀者可能會有疑問:「既然如此,何須再以函式包裝呢」。 沒錯,這裡唯一能給出的理由就是「使用函式包裝可以增加可讀性」, 但是為何要使用匿名函式呢?

以下例子可以給讀者一個解答:

	theList = [5, 2, 1, 7, 9]
	print(list(map(lambda x: x ** 2, theList)))

map是Python提供的函式,該函式的輸入包含一個函式,用來作為map的篩選機制, 這樣的函式設計可以增加map函式的靈活性, 每次使用map函式時都可以動態輸入固定的篩選規則。 不過需要注意的是,濫用匿名函式會降低程式碼的可讀性,使用時應該釐清時機。

類別(Class)

類別能讓開發者容易使用相關的程式碼,使程式碼更具可讀性與重用性。

沒錯,類別也出現在Python裡,這讓Python變得更為靈活, 開發時講求物件導向設計的開發者也能夠使用Python盡興開發。 物件導向程式設計並不是本篇的重點,所以這部份的概念不會提到太多, 有興趣的讀者可以找找其他相關文章。使用函式進行封裝是個好主意, 不過龐大數量的函式對於開發者而言,在使用上也是一大挑戰。 你可以想想如何在圖書館中找到多本你真正需要的書? 除非你是真正熟悉需要的那些書名,不然只能慢慢搜尋, 函式就像是圖書館裡的書籍,如果沒有分門別類的管理, 臨時需要的時候或許直接自己實作要比在成千上萬的函式海裡搜尋要快(?!), 所以如果能將函式依照相關度進行分類管理與包裝, 這將能大大提升程式碼的易用性、重用性與可讀性。

下例是將有關數學運算的函式放置於同一個類別。

	class MathLib:
		
        def sum2(self, a, b):
			return a + b
        
		def sub2(self, a, b):
			return a - b

我們再來看另一個例子,例中是將音樂盒狀態包裝成一個類別, 相關的變數與函式皆被放在類別中, 我們只要新增該類別的(一個或多個)物件就能夠輕鬆管理(一個或多個)音樂盒的狀態,這是類別的優點。 在物件導向程式設計原則中有許多類別的封裝建議, 這裡提供的類別範例主要是用來說明Python中類別的實作方式,並不一定符合恰當的物件導向設計原則。

	class MusicBoxStatus:
        
		def __init__(self, random):
			self.random = random
			self.number = 0
        
		def add(self):
			self.number += 1
        
		def setPlayMode(self, random):
			self.random = random
        
		def getNumber(self):
			return self.number
        
		def __str__(self):
			return str(self.number) + ' songs inside the musicbox.'

模組(Module)

區分函式與類別的機制。

介紹完函式與類別,讀者應該已經能撰寫出不錯的程式碼了, 但是這麼多的函式與類別如何有效的管理呢? 這時就可以再往上一層,使用模組的概念來進行封裝。 事實上,各位讀者已經在不知不覺中建立模組了, 因為只要建立一個Python檔案(.py)就是建立了一個模組, 依照Python檔案的檔名視為模組的名稱, 例如:新增一個myModule.py的檔案後,同時也是在建立一個名叫myModule的模組。 模組的概念能有效的管理函式與類別,提供開發者將相關的類別與函式放在同個模組中, 就如同當初將相關的函式與變數放在同個類別一樣, 同時模組也能將開發者自己的函式與類別與其他程式開發者撰寫的同版型(prototype)函式與同名稱類別區隔開, 打個比方,就好比用班級來區分同名的學生,如5班的小張與7班的小張。 將Python與Java作比較,Java並沒有模組的概念,Java只採用套件而沒有嚴格引入模組的概念, 因為實際上等等要介紹的套件同樣也具有相同的作用。 因此Java一個檔案(.class)只能放置一個類別,而Python一個檔案(.py)則能放置多個類別與函式。

套件(Package)

區分函式、類別與模組的機制。

情況是這樣的,現在手邊有許多Python檔案(.py)或者說是有許多模組,我們還可以再往外建立一層套件。建立套件的方法十分簡單,只要建立一個資料夾將所有想要歸類為相同套件的模組放在裡面即可,這與建立模組相似,資料夾的名稱就是套件的名。不過事實上,單純的資料夾不可能隨便讓Python辨識為套件,所以還要額外在資料夾內放入__init__.py的空白檔案,當然這個檔案內也可以撰寫一些套件初始化的程式,但如果沒有特別需要,只要將該空白檔案放置好即可。與模組相同,套件能將開發者自己的函式、類別及模組與其他程式開發者撰寫的同版型(prototype)函式與同名稱類別、模組區隔開。套件再往上一層會是什麼?如果沒有在更上一層,要如何將自己的套件與其他開發者的套件區隔開呢?這是套件與模組不同的地方,套件的名稱可以作多層級的定義!一般開發者會將識別自身的名稱作為套件的名稱,例如Google公司開發的數學運算套件名稱可以設作com.google.math,以此為原則,微軟公司開發的數學運算套件名稱則設作com.microsoft.math。

import、import as、from import、del

引入寫好的函式、類別、模組與套件到程式裡。

最後,我們要來介紹一下如何引入已經寫好的函式、類別、模組與套件。 沒錯,已經寫好的函式、類別、模組與套件是需要被引入後才能使用的! 基礎語法是import package.module,只要遵循這樣的原則再作沿伸就十分容易了。

這裡我們可以從四個面向去思考:

  1. 可以引入特定套件嗎?
  2. 可以引入特定模組嗎?
  3. 可以引入特定類別嗎?
  4. 可以引入特定函式嗎?

答案是肯定的:

	import package.module
	from package.module import class/function	

引入的程式庫我們可以賦予一個新的變數名稱,方便後續程式呼叫。

	import package.module as rename
	from package.module import class/function as rename

參考資料

  1. Python Tutorial 第二堂(3)函式、模組、類別與套件 from CodeData

comments powered by Disqus