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。面向對象編程是編程語言技術邁出的一大步,面向對象的出現讓我們的代碼能夠更好的描述我們的世界。現在的主流編程語言已經紛紛開始支持面向對象,所以掌握面向對象編程是成為一個好的程序員的基本。