1. 程式人生 > 其它 >封裝、私有,一文掌握Python關鍵程式碼

封裝、私有,一文掌握Python關鍵程式碼

首先,什麼是 Python?根據 Python 建立者 Guido van Rossum 所言,Python 是一種高階程式語言,其設計的核心理念是程式碼的易讀性,以及允許程式設計者通過若干行程式碼輕鬆表達想法創意。實際上,我選擇學習 Python 的首要原因是其程式設計的優美性,用它編碼和表達想法非常自然。

Python 是一個高層次的結合瞭解釋性、編譯性、互動性和麵向物件的指令碼語言。Python 的設計具有很強的可讀性,相比其他語言經常使用英文關鍵字,其他語言的一些標點符號,它具有比其他語言更有特色語法結構。

另一個原因是 Python 的編寫使用方式有多種,資料科學、網頁開發、機器學習皆可使用 Python。Quora、Pinterest 和 Spotify 都使用 Python 作為其後端開發語言。

01 基礎篇

變數

簡單來講,我們可以把變數看作是儲存一個值的詞。

在 Python 中定義一個變數併為其賦值非常簡單。想象一下你在變數「one」中儲存 1,即是:

one = 1

這是多麼簡單,我們只要把 1 賦值給變數「one」。

two = 2
some_number = 10000

並且你可以把任意值賦給任意變數。如上所見,把整數 2 賦值給變數「two」,把 10,000 賦值給變數「some_number」。除了整數,我們也可以賦值布林運算、字串、浮點數以及其他資料形式。

# booleans
true_boolean = True
false_boolean = False
# string
my_name = "Leandro Tk"
# float
book_price = 15.80

控制流:條件語句

「If」語句通過表示式評估一個語句是真或假。如果是真,則向下執行「If」條件中的子語句。比如:

if True:
print("Hello Python If")
if 2 > 1:
print("2 is greater than 1")

2 比 1 大,因此「print」程式碼被執行。如果「If」表示式是假的,則「else」下的子語句將被執行。

if 1 > 2:
print("1 is greater than 2")
else:
print("1 is not greater than 2")

你也可以使用一個「elif」語句以新增一個執行條件。

if 1 > 2:
print("1 is greater than 2")
elif 2 > 1:
print("1 is not greater than 2")
else:
print("1 is equal to 2")

迴圈/迭代器

在 Python 中,我們可有不同形式的迭代。我將討論兩個:while 與 for。

While 迴圈:當該語句為真,以下程式碼將被執行,並列印從 1 到 10 的數字。

num = 1
while num <= 10:
print(num)
num += 1

While 迴圈需要一個「迴圈條件」。如果它為真,則繼續迭代。在以上例項中,當 num 為 11,則迴圈條件為假,我們結束迴圈。

以下程式碼有助於更好地理解它:

loop_condition = True
while loop_condition:
print("Loop Condition keeps: %s" %(loop_condition))
loop_condition = False

迴圈條件為真,則繼續迭代,直到它為假。對於 For 迴圈:你可以把變數「num」應用需要迴圈的程式碼塊中,而「for」語句會為你迭代它。該程式碼的列印與 while 程式碼相同:從 1 到 10。

看,如此簡單。範圍從 1 直到第 11 個元素(10 是第 10 個元素)。此外,如果我們直接確定一個數,那麼 For 迴圈將從零開始一直迭代到該數字(不包括)。例如以下 For 迴圈將輸出 0 到 9:

for i in range(1, 11):
print(i)

02 列表:陣列資料結構

列表是一個數組或集合,它可用於儲存一系列值(比如那些你想要的整數)。因此讓我們用一下它:

my_integers = [1, 2, 3, 4, 5]

如上我們建立了一個數組並賦值到 my_integers 變數中。而我們可以通過索引取該陣列中的值,如下所示,陣列第一個元素的索引為 0,第二個元素的索引為 1,依次類推。

使用以下語句可能更好理解:

my_integers = [5, 7, 1, 3, 4]
print(my_integers[0]) # 5
print(my_integers[1]) # 7
print(my_integers[4]) # 4

同樣我們列表元素的型別也可以是字元型,如下我們建立了一個元素為字元的列表:

relatives_names = [
"Toshiaki",
"Juliana",
"Yuji",
"Bruno",
"Kaio"
]
print(relatives_names[4]) # Kaio

以上我們瞭解了列表的定義和索引使用方法,以下我們將瞭解如何新增一個元素到列表資料結構中。新增元素到列表最常見的方法是 append:

bookshelf = []
bookshelf.append("The Effective Engineer")
bookshelf.append("The 4 Hour Work Week")
print(bookshelf[0]) # The Effective Engineer
print(bookshelf[1]) # The 4 Hour Work Week

append 方法非常簡單,我們只需要對需要新增的元素應用該方法就能將其新增到列表的末尾。

03 字典:鍵-值資料結構

我們已經知道列表是通過整數索引來獲取某個元素,而若我們不希望使用整數作為索引,那麼就可以使用字典資料結構。通過這種資料結構,我們可以使用數值型、字元型或其它型別的索引。字典的每個鍵值 (key=>value) 對用冒號 (:) 分割,每個對之間用逗號 (,) 分割,整個字典包括在花括號 ({})中。如下,字典(Dictionary)是鍵(Key)與值(Value)的集合:

dictionary_example = {
"key1": "value1",
"key2": "value2",
"key3": "value3"
}

其中鍵是指向對應值的索引,我們需要使用鍵而訪問對應的元素值:

dictionary_tk = {
"name": "Leandro",
"nickname": "Tk",
"nationality": "Brazilian"
}
print("My name is %s" %(dictionary_tk["name"])) # My name is Leandro
print("But you can call me %s" %(dictionary_tk["nickname"])) # But you can call me Tk
print("And by the way I'm %s" %(dictionary_tk["nationality"])) # And by the way I'm Brazilian

以上建立了一個字典,其中定義了四個鍵與對應的值,print 函式內使用了字典的鍵以獲取對應的值。此外,字典的值可以使用任何型別的資料,如下我們添加了一個鍵為字元型,值為數值型的鍵-值對。

dictionary_tk = {
"name": "Leandro",
"nickname": "Tk",
"nationality": "Brazilian",
"age": 24
}
print("My name is %s" %(dictionary_tk["name"])) # My name is Leandro
print("But you can call me %s" %(dictionary_tk["nickname"])) # But you can call me Tk
print("And by the way I'm %i and %s" %(dictionary_tk["age"], dictionary_tk["nationality"])) # And by the way I'm Brazilian

下面我們需要了解如何新增元素到字典中,其實字典的本質就是指向特定值的關鍵字的集合。因此我們可以直接將某個值賦予到字典某個關鍵字(可以不存在)中而修改或新增鍵值對。

dictionary_tk = {
"name": "Leandro",
"nickname": "Tk",
"nationality": "Brazilian"
}
dictionary_tk['age'] = 24
print(dictionary_tk) # {'nationality': 'Brazilian', 'age': 24, 'nickname': 'Tk', 'name': 'Leandro'}

04 迭代:資料結構中的迴圈

列表迴圈同樣十分簡單,我們可以迴圈地修改或輸出某個列表。如下,我們常用 For 迴圈依次提取列表中的元素:

bookshelf = [
"The Effective Engineer",
"The 4 hours work week",
"Zero to One",
"Lean Startup",
"Hooked"
]
for book in bookshelf:
print(book)

對於雜湊資料結構,我們同樣可以使用字典中的鍵和 For 迴圈依次讀取鍵與對應的值:

dictionary = { "some_key": "some_value" }
for key in dictionary:
print("%s --> %s" %(key, dictionary[key]))
# some_key --> some_value

使用 iteritems 方法同樣可以實現相同的效果:

dictionary = { "some_key": "some_value" }
for key, value in dictionary.items():
print("%s --> %s" %(key, value))
# some_key --> some_value

我們命名了兩個引數 key 和 value,但我們同樣可以命名為其它的,如下我們使用 attribute 和 value 作為字典鍵值的引數,它同樣有效:

dictionary_tk = {
"name": "Leandro",
"nickname": "Tk",
"nationality": "Brazilian",
"age": 24
}
for attribute, value in dictionary_tk.items():
print("My %s is %s" %(attribute, value))
# My name is Leandro
# My nickname is Tk
# My nationality is Brazilian
# My age is 24

05 類與物件

物件(Object)表徵的是真實世界中的目標,如狗、貓和自行車等,一般物件有兩個特徵,即資料(Data)與行為(Behavior)。物件「車輛」有一些資料,如車輪的數量、車門的數量與作為容量等,它同樣還有一些行為,例如車輛可以加速、剎車、展示燃油使用量等。

在面向物件的程式設計中,我們將資料表示為屬性,將行為表示為方法。

類(Class)是建立獨立物件的藍圖。在現實世界中,我們經常發現很多相同型別的物件。例如車輛,同型號的車輛都有引擎、車輪、座位等元件,而每一輛車都是根據相同的設計圖構建且有相同的元件。

因此,物件是對客觀事物的抽象,類是對物件的抽象。物件是類的例項,類是物件的模板。

Python 是一種面向物件的程式語言,因此它也有類(Class)與物件(Object)這兩個概念。在瞭解 Python 面向物件程式設計的案例前,我們需要先熟悉面向物件程式設計的一些基本概念:

  • 類 (Class):用來描述具有相同的屬性和方法的物件的集合。它定義了該集合中每個物件所共有的屬性和方法。物件是類的例項。
  • 類變數:類變數在整個例項化的物件中是公用的。類變數定義在類中且在函式體之外。類變數通常不作為例項變數使用。
  • 資料成員:類變數或者例項變數用於處理類及其例項物件的相關的資料。
  • 方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。
  • 例項變數:定義在方法中的變數,只作用於當前例項的類。
  • 繼承:即一個派生類(derived class)繼承基類(base class)的欄位和方法。繼承也允許把一個派生類的物件作為一個基類物件對待。例如,一個「狗」類的物件派生自「動物」類,這是模擬"是一個(is-a)"關係(狗是一種動物)。
  • 例項化:建立一個類的例項,類的具體物件。
  • 方法:類中定義的函式。
  • 物件:通過類定義的資料結構例項。物件包括兩個資料成員(類變數和例項變數)和方法。

下面首先檢視通過宣告定義類的語句:

class Vehicle:
pass

目標是類的例項,我們可以使用類的名稱建立一個例項:

car = Vehicle()
print(car) # <__main__.Vehicle instance at 0x7fb1de6c2638>

如上,car 為 Vehicle 類的一個物件或例項。

若我們的 vehicle 類有四個屬性,即車輪數、儲能型別、座位容量和最大時速,那麼我們在建立 vehicle 類時可以設定這些屬性。下面,我們定義了初始化類時所接受的資料。self 代表類的例項,self 在定義類的方法時是必須有的,雖然在呼叫時不必傳入相應的引數。

class Vehicle:
def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
self.number_of_wheels = number_of_wheels
self.type_of_tank = type_of_tank
self.seating_capacity = seating_capacity
self.maximum_velocity = maximum_velocity

__init__() 方法是一種特殊的方法,被稱為類的建構函式或初始化方法,當建立 vehicle 類的例項時就會呼叫該方法來定義這些屬性。若我們希望能建立 Tesla Model S 這一個物件,根據其有四個車輪、電力驅動、四座容量和最大時速為 250km/hour 的特徵,我們能建立物件:

tesla_model_s = Vehicle(4, 'electric', 5, 250)

現在所有的屬性已經設定了,那麼我們該如何訪問這些屬性值?我們將傳送資訊到物件的過程稱為方法,即物件的行為:

class Vehicle:
def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
self.number_of_wheels = number_of_wheels
self.type_of_tank = type_of_tank
self.seating_capacity = seating_capacity
self.maximum_velocity = maximum_velocity
def number_of_wheels(self):
return self.number_of_wheels
def set_number_of_wheels(self, number):
self.number_of_wheels = number

以上語句實現了兩個方法,number_of_wheels 和 set_number_of_wheels。我們可以稱為 getter & setter,因為第一個方法獲取了屬性值,而第二個方法將為該屬性設定一個新的值。在類的內部,使用 def 關鍵字可以為類定義一個方法,與一般函式定義不同,類方法必須包含引數 self,且為第一個引數。

在 Python 中,我們能使用 @property (decorators) 定義 getter & setter:

class Vehicle:
def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
self.number_of_wheels = number_of_wheels
self.type_of_tank = type_of_tank
self.seating_capacity = seating_capacity
self.maximum_velocity = maximum_velocity
@property
def number_of_wheels(self):
return self.number_of_wheels
@number_of_wheels.setter
def number_of_wheels(self, number):
self.number_of_wheels = number

同樣我們能使用這些方法作為屬性:

tesla_model_s = Vehicle(4, 'electric', 5, 250)
print(tesla_model_s.number_of_wheels) # 4
tesla_model_s.number_of_wheels = 2 # setting number of wheels to 2
print(tesla_model_s.number_of_wheels) # 2

這和定義方法有一些不同,這些方法作為了一種屬性。如上當我們設定新的車輪數量,我們不需要將「2」作為引數設定,而是將 number_of_wheels 數值設定為 2。

我們還能使用方法做一些其他的操作,例如方法「make_noise」可以設定為:

class Vehicle:
def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
self.number_of_wheels = number_of_wheels
self.type_of_tank = type_of_tank
self.seating_capacity = seating_capacity
self.maximum_velocity = maximum_velocity
def make_noise(self):
print('VRUUUUUUUM')

當我們呼叫該方法時,它將返回字串「VRRRRUUUUM」。

tesla_model_s = Vehicle(4, 'electric', 5, 250)
tesla_model_s.make_noise() # VRUUUUUUUM

06 封裝:隱藏資訊

封裝是一種限制直接訪問目標屬性和方法的機制,但同時它又有利於對資料(物件的方法)進行操作。

封裝是一種將抽象性函式介面的實現細節部分包裝、隱藏起來的方法。同時,它也是一種防止外界呼叫端,去訪問物件內部實現細節的手段,這個手段是由程式語言本身來提供的。

物件所有的內部表徵對於外部來說都是隱藏的,只有物件能直接與內部資料互動。首先,我們需要理解公開(public)和私有(non-public)例項變數和方法。

公開例項變數

對於 Python 的類,我們可以使用 constructor 方法初始化公開例項變數:

class Person:
def __init__(self, first_name):
self.first_name = first_name

下面我們應用 first_name 的值作為公開例項變數的變元。

tk = Person('TK')
print(tk.first_name) # => TK

在類別內:

class Person:
first_name = 'TK'

現在我們不需要再對 first_name 賦值,所有賦值到 tk 的目標都將有類的屬性:

tk = Person()
print(tk.first_name) # => TK

現在我們已經學會如何使用公開例項變數和類屬性。除此之外,我們還能管理公開部分的變數值,即物件可以管理其變數的值:Get 和 Set 變數值。保留 Person 類,我們希望能給 first_name 變數賦另外一個值:

tk = Person('TK')
tk.first_name = 'Kaio'
print(tk.first_name) # => Kaio

如上我們將另外一個值(kaio)賦予了 first_name 例項變數,因為它又是一個公開變數,所以會更新變數值。

私有例項變數

和公開例項變數一樣,我們可以使用 constructor 方法或在類的內部宣告而定義一個私有例項變數。語法上的不同在於私有例項變數在變數名前面加一個下劃線:

class Person:
def __init__(self, first_name, email):
self.first_name = first_name
self._email = email

上述定義的 email 變數就是私有變數。

tk = Person('TK', '[email protected]')
print(tk._email) # [email protected]

我們可以訪問並且更新它,私有變數僅是一個約定,即他們需要被視為 API 非公開的部分。所以我們可以使用方法在類的定義中完成操作,例如使用兩種方法展示私有例項的值與更新例項的值:

class Person:
def __init__(self, first_name, email):
self.first_name = first_name
self._email = email
def update_email(self, new_email):
self._email = new_email
def email(self):
return self._email

現在我們可以使用方法更新或訪問私有變數。

tk = Person('TK', '[email protected]')
print(tk.email()) # => [email protected]
tk._email = '[email protected]'
print(tk.email()) # => [email protected]
tk.update_email('[email protected]')
print(tk.email()) # => [email protected]

我們先初始化 Person 類並賦值,然後通過定義的方法訪問並列印私有變數的值。如我們直接賦值給私有變數新的值,那麼打印出來還是原有的值,我們只能通過在類裡面定義的方法進行操作而更新私有變數。

公開方法

對於公開方法(public methods),我們同樣能在類以外的地方呼叫,以下定義了一個類與方法:

class Person:
def __init__(self, first_name, age):
self.first_name = first_name
self._age = age
def show_age(self):
return self._age

讓我們測試一下該方法:

tk = Person('TK', 25)
print(tk.show_age()) # => 25

私有方法

但是對於私有方法來說,並不能這樣操作。若我們定義相同的類,且使用下劃線定義 show_age 為私有方法:

class Person:
def __init__(self, first_name, age):
self.first_name = first_name
self._age = age
def _show_age(self):
return self._age

我們同樣能呼叫物件的私有方法:

tk = Person('TK', 25)
print(tk._show_age()) # => 25

我們也能訪問並更新它,私有方法應該要看作 API 的私有部分。下面的案例可以展示瞭如何使用它:

class Person:
def __init__(self, first_name, age):
self.first_name = first_name
self._age = age
def show_age(self):
return self._get_age()
def _get_age(self):
return self._age
tk = Person('TK', 25)
print(tk.show_age()) # => 25

如上我們聲明瞭私有方法_get_age 和公開方法 show_age。show_age 方法可以在類的外部呼叫,而_get_age 只能在類內部使用。

封裝小結

通過程式封裝,我們確保了物件的內部表徵對外是隱藏的。而面向物件的程式設計帶來的主要好處之一是程式碼的重用,實現這種重用的方法之一是通過繼承機制。繼承完全可以理解成類之間的型別和子型別關係。

若我們有一輛車,且知道車輪數、座位容量和最大時速,那麼一輛電動車類就繼承常規汽車類中的相同屬性。

class Car:
def __init__(self, number_of_wheels, seating_capacity, maximum_velocity):
self.number_of_wheels = number_of_wheels
self.seating_capacity = seating_capacity
self.maximum_velocity = maximum_velocity

更新類中的一個物件:

my_car = Car(4, 5, 250)
print(my_car.number_of_wheels)
print(my_car.seating_capacity)
print(my_car.maximum_velocity)

初始化物件後,Python 可以將父類(parent class)作為引數應用到子類(child class)中。因此電動車類可以從汽車類繼承物件。

class ElectricCar(Car):
def __init__(self, number_of_wheels, seating_capacity, maximum_velocity):
Car.__init__(self, number_of_wheels, seating_capacity, maximum_velocity)

我們不需要實現其他方法,因為電動汽車類已經從汽車類繼承了物件:

my_electric_car = ElectricCar(4, 5, 250)
print(my_electric_car.number_of_wheels) # => 4
print(my_electric_car.seating_capacity) # => 5
print(my_electric_car.maximum_velocity) # => 250