22 Python 程式設計正規化科普
程式設計正規化是計算機程式設計的基本風格或典範模式。如果說每個程式設計者都在創造虛擬世界,那麼程式設計正規化就是程式設計師置身其中採用的世界觀和方法論。
常見的程式設計正規化包括:
- 面向過程程式設計
- 面向物件程式設計
程式設計範型提供了程式設計師對程式執行的看法:在面向過程程式設計中,程式設計師認為程式是一系列相互呼叫的過程或者函式;在面向物件程式設計中,程式設計師認為程式是一系列相互作用的物件;而在函數語言程式設計中一個程式會被看作是一個無狀態的函式計算的序列。
不同的程式語言也會提倡不同的程式設計正規化,一些語言是專門為某個特定的程式設計正規化設計的。例如,C 支援面向過程程式設計,Java 支援面向物件程式設計。Python 程式語言支援多種程式設計正規化,應該在不同的應用場景下,選擇合適的程式設計正規化。
1. 面向過程程式設計
1.1 概述
面向過程程式設計是一種以過程為中心的程式設計思想,程式由一系列相互呼叫的過程組成。面向過程程式設計的主要思想是關注計算機執行的步驟,即一步一步告訴計算機先做什麼再做什麼。
面向過程程式設計特別適合解決線性(或者說按部就班)的演算法問題。在這類演算法問題中,解決問題的途徑由多個步驟構成,使用函式描述每個步驟,因此使用函式對問題建模非常合適。
面向過程程式設計強調 “自頂向下” 和 “精益求精” 的設計方式。解決一個複雜的問題的方法是將問題劃分為多個子問題,將子問題再繼續分解直到問題足夠簡單到可以在一個小步驟範圍內解決。
面向過程程式設計不足之處就是它不適合某些種類問題的解決,例如圖形化程式設計,在圖形化程式設計中,客觀世界由具體的物件(視窗、標籤、按鈕等)組成,無法自然的將函式與圖形物件一一對應,因此面向過程程式設計不適合用於圖形化程式設計的領域。
1.2 例子
本節採用面向過程程式設計的方式完成這樣的任務:將文字檔案中小寫字母轉換為大寫字母。將任務劃分為 3 個步驟:
- 讀取文字檔案的內容
- 對讀取的文字進行轉換,將小寫字母轉換為大寫字母
- 把轉換後的內容儲存到檔案中
任務被劃分為 3 個簡單的子任務,然後使用函式實現每個子任務,採用面向過程程式設計的方式解決這樣的問題非常自然。
設計與實現的步驟如下:
1. 設計主任務和子任務:
任務名稱 | 函式名 | 任務功能描述 |
---|---|---|
主任務 | main | 執行子任務 |
讀取任務 | read_file | 讀取文字檔案的內容 |
轉換任務 | transform | 將小寫字母轉換為大寫字母 |
儲存任務 | save_file | 把轉換後的內容儲存到檔案中 |
- 實現主任務
def read_file(path):
pass
def transform(input):
pass
def save_file(path, content):
pass
def main():
input = read_file("test.txt")
output = transform(input)
save_file("test.txt", output)
main()
執行案例 點選 "執行案例" 可檢視線上執行效果 - 在第 1 行,定義函式 read_file(path),它讀取 path 指定的檔案,返回檔案的內容
- 在第 4 行,定義函式 transform(input),它將輸入 input 中的小寫字母轉換為大寫字母
- 在第 7 行,定義函式 save_file(path, content),它將字串 content 儲存到 path 指定的檔案
- 在第 10 行,定義函式 main
- 在第 11 行,讀取檔案 test.txt 的內容,將內容儲存到變數 input 中
- 在第 12 行,對輸入 input 進行轉換,將轉換後的內容儲存到變數 output 中
- 在第 13 行,將字串 output 儲存到檔案 test.txt
3. 實現讀取任務
def read_file(path):
lines = ''
file = open(path)
for line in file:
lines += line
file.close()
return lines
- 在第 2 行,將檔案內容儲存到變數 lines 中,設定它的初值為空字串
- 在第 3 行,開啟檔案
- 在第 4 行,逐行遍歷檔案
- 在第 5 行,將每行的內容累加到變數 lines 中
- 在第 6 行,關閉檔案
4. 實現轉換任務
def transform(input, output):
output = input.upper()
return output
- 在第 2 行,呼叫字串的 upper 方法返回大寫的字串
- 在第 3 行,返回 output
5. 實現儲存任務
def save_file(path, content):
file = open(path, "w")
file.write(content)
file.close()
- 在第 2 行,以可寫方式開啟檔案
- 在第 3 行,呼叫檔案的 write 方法儲存檔案
- 在第 4 行,關閉檔案
2. 面向物件程式設計
2.1 概述
面向物件程式設計是一種以物件為中心的程式設計思想,程式由一系列相互作用的物件組成。面向物件程式設計中,程式包含各種獨立而又互相呼叫的物件,而在面向過程程式設計中,將程式看作一系列函式的集合。
面向物件程式設計方法是儘可能模擬人類的思維方式,使得軟體的開發方法與過程儘可能接近人類認識世界、解決現實問題的方法和過程,也即使得描述問題的問題空間與問題的解決方案空間在結構上儘可能一致,把客觀世界中的實體抽象為問題域中的物件。
例如圖形化程式設計,在圖形化程式設計中,客觀世界由具體的物件(視窗、標籤、按鈕等)組成,可以自然的將物件與圖形物件一一對應,因此面向物件程式設計適合用於圖形化程式設計的領域。
2.2 基本概念
面向物件程式設計包含通過類、例項、屬性和方法等核心概念:
- 類,類是相似物件的集合,類包含和描述了“具有共同特性和共同行為”的一組物件。
- 例項,例項則指的是類的例項,例項包含屬性和方法。
- 屬性,屬性是指物件的特性。例如,存在一個物件 person,物件 person 的屬性包括姓名和年齡。
- 方法,方法是指物件的行為。例如,存在一個物件 person,物件 person 的包括一個方法 show,通過呼叫方法 show 可以輸出物件 person 的相關資訊。
下面的程式碼演示了以上 4 個基本概念:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
print('My name is %s, I am %d years old' % (self.name, self.age))
tom = Person('tom', 10)
jerry = Person('jerry', 12)
tom.show()
jerry.show()
- 在第 1 行,定義了類 Person
- 在第 2 行,定義了類 Person 的方法 init
- 方法 init 設定類 Person 的屬性 name 和 age
- 在第 6 行,定義了類 Person 的方法 show,
- 方法 show 輸出類 Person 的屬性 name 和 age
- 在第 9 行,通過類 Person 建立一個例項 tom
- 例項 tom 的屬性 name 是 ‘tom’,age 是 10
- 在第 10 行,通過類 Person 建立一個例項 jerry
- 例項 jerry 的屬性 name 是 ‘jerry’,age 是 12
- 在第 11 行,呼叫類 tom 的方法 show
- 在第 12 行,呼叫類 jerry 的方法 show
3.3 基本特性
3.3.1 封裝
封裝是將資料和程式碼捆綁到一起,物件的某些屬性和方法可以是私有的,不能被外界訪問,以此實現對資料和程式碼不同級別的訪問許可權。防止了程式相互依賴性而帶來的變動影響,有效實現了兩個目標:對資料和行為的包裝和資訊隱藏。
在下面的程式中,類 Span 描述了間距,間距有 3 個屬性:
- 開始位置
- 結束位置
- 長度,長度等於開始位置到結束位置之間的距離
使用封裝通過方法訪問這些屬性,而不是直接訪問這些屬性,程式碼如下:
class Span:
def __init__(self, start, end):
self._start = start
self._end = end
def get_start(self):
return self._start
def get_end(self):
return self._end
def get_length(self):
return self._end - self._start
span = Span(10, 100)
print('start = %d' % span.get_start())
print('end = %d' % span.get_end())
print('length = %d' % span.get_length())
- 在第 2 行,定義了建構函式,使用 start 和 end 構造間距
- 在第 3 行,_start 是私有屬性,描述了間距的開始位置,通過引數 start 設定
- 在第 4 行,_end 是私有屬性,,描述了間距的結束位置,通過引數 end 設定
- 在第 6 行,定義了成員函式 get_start,外界通過 get_start 獲取開始位置
- 在第 9 行,定義了成員函式 get_end,外界通過 get_end 獲取結束位置
- 在第 12 行,定義了成員函式 get_length,外界通過 get_length 獲取間距的長度
程式執行輸出如下:
start = 10
end = 100
length = 90
3.3.2 繼承
繼承是一種層次模型,這種層次模型能夠被重用。層次結構的上層具有通用性,但是下層結構則具有特殊性。在繼承的過程中,子類則可以從父類繼承一些方法和屬性。子類除了可以繼承以外同時還能夠進行修改或者新增。
下面的例子描述父類 Person 和子類 Teacher 之間的繼承關係,子類 Teacher 從父類 Person 繼承了一些方法和屬性,父類 Person 程式碼如下:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print('My name is', self.name)
print('My age is', self.age)
- 在第 2 行,在建構函式 __init__ 中,設定屬性 name 和 age
- 在第 6 行,在成員函式 introduce 中,輸出屬性 name 和 age
子類 Teacher 程式碼如下:
class Teacher(Person):
def __init__(self, name, age, salary):
Person.__init__(self, name, age)
self.salary = salary
def showSalary(self):
print('My salary is', self.salary)
teacher = Teacher('tom', 30, 5000)
teacher.introduce()
teacher.showSalary()
- 在第 2 行,在建構函式 __init__ 中,設定屬性 name、age 和 salary
- 在第 3 行,呼叫父類的 __init__ 方法,設定屬性 name 和 age
- 在第 4 行,設定子類 Tearch 特有的屬性 salary
- 在第 6 行,在成員函式 showSalary 中,輸出屬性 salary
- 在第 9 行,例項化一個物件 teacher
- 在第 10 行,呼叫 teacher 的 introduce 方法,該方法是子類繼承的方法
- 在第 11 行,呼叫 teacher 的 showSalary 方法,該方法是子類特有的方法
程式執行輸出如下:
My name is tom
My age is 30
My salary is 5000
3.3.3 多型
在面嚮物件語言中,介面的多種不同的實現方式即為多型。多型機制使具有不同內部結構的物件可以共享相同的外部介面,它是面向物件程式設計(OOP)的一個重要特徵。
下面通過一個具體的例子演示多型的意義:有多種型別的形體,例如:正方形、三角形等。但是,不論形體的型別是什麼,每一種形體都有周長的概念。顯然,計算每種形體的周長的公式是不一樣的。面對抽象的形體時,希望能夠獲取形體的周長。
首先,定義父類 Shape,程式碼如下:
class Shape:
def get_circle(self):
pass
- 在第 1 行,定義了父類 Shape,子類 Square 和 Triangle 繼承於 Shape
- 在第 2 行,定義了成員方法 get_circle,get_circle 計算形體的周長
- 在第 3 行,這裡是一個空方法,僅僅定義了介面,在子類中定義具體的實現
定義子類 Square,程式碼如下:
class Square(Shape):
def __init__(self, side):
self.side = side
def get_circle(self):
return self.side * 4
- 在第 1 行,定義了子類 Square,繼承於父類 Shape
- 在第 2 行,定義建構函式 __init__,引數 side 表示正方形的邊長
- 在第 5 行,定義成員方法 get_circle,計算正方形的周長
定義子類 Triangle,程式碼如下:
class Triangle(Shape):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def get_circle(self):
return self.a + self.b + self.c
- 在第 1 行,定義了子類 Triangle,繼承於父類 Shape
- 在第 2 行,定義建構函式 __init__,引數 a、b、c 表示三角形的邊長
- 在第 7 行,定義成員方法 get_circle,計算三角形的周長
在父類 Shape 中定義了介面 get_circle ,子類 Square 和子類 Triangle 分別實現了介面 get_circle。只要物件的型別是 Shape,不論具體的型別是 Square 還是 Triangle,都可以呼叫介面 get_circle,程式碼如下:
square = Square(10)
triangle = Triangle(3, 4, 5)
shapes = [square, triangle]
for shape in shapes:
print(shape.get_circle())
- 在第 1 行,構造一個正方形 square,邊長是 10
- 在第 2 行,構造一個三角形 triangle,邊長是 3、4、5
- 在第 3 行,構造一個列表 shapes,將 square 和 triangle 加入到列表 shapes 中
- 在第 4 行,遍歷列表 shapes
- 在第 5 行,呼叫 shape 的 get_circle 方法,獲取形體的周長
程式執行輸出如下:
40
12
4. 小結
比較經典的面向過程程式語言有 C。面向物件程式設計是程式語言技術邁出的一大步,面向物件的出現讓我們的程式碼能夠更好的描述我們的世界。現在的主流程式語言已經紛紛開始支援面向物件,所以掌握面向物件程式設計是成為一個好的程式設計師的基本。