Python Class 介紹
Pythonic way 針對物件與類別上的 terminology,與 Java 有些差異。
因為 Python 是直譯是語音,所有的腳本(Script) 都會被 interpreter 直譯。
所以,在 Python 的世界中 class script 被直譯後也會被稱為 object (class object)。
由 class object 因調用而產生出的物件稱為 instance object。
在 Python Environment 中似乎也沒有 Java heap 的結構。
Python Class 結構
*.py
Python Script --> Class Object --> Instance Object
\
\> Instance Object
建構子
Python 中 Class 建構子為 __init__ 函數
另外,因為 Python 的函數(包含建構子)都支援 varArgs, kwArgs, Python 建構子本身即具有多載特性。
因此,Python 沒有如 Java 般以 this 呼叫 overloaded 建構子的語法。
class Profile:
def __init__(self, name, gender, *args, **kwargs):
self.name = name
self.gender = gender
self.infoList = args
self.infoMap = kwargs
# Fundamental constructor
p2 = Profile('Totem', 'Male')
# Chimera Style
p = Profile('Winnie', 'Female',
'Shopping', 'Reading', # varArgs
height='160', addr='TW', tel='0955-555-555') #kwargs
print(p.name) # Winnie
print(p.infoList) # ('Shopping', 'Reading')
print(p.infoMap) # {'height': '160', 'addr': 'TW', 'tel': '0955-555-555'}
命名慣例:
package name: lowercase
class name: CamelCase
member name: lower_case with under_scores
exception name: CamelCase
constant name: UPPERCASE
Java 中的 this => Python 中的 self
建構子為 __init__ 函數
biologe.animal.py
class Animal:
alive = True
def __init__(self, gender: bool, voice: str):
self.voice = voice
self.gender = gender
self.age = None
def get_voice(self):
return self.voice
def get_gender(self):
if self.gender:
return "Male"
else:
return "Female"
def get_display(self):
return f'sex:{self.gender}, voice={self.voice}, age={self.age} '
run.py
from biology.animal import Animal
dog = Animal(True, voice="bark")
print(dog.getVoice())
print(dog.getGender())
方法與屬性
Python 允許 instance object 在 class 定義區塊外 自行添加新的屬性與函數。
而相關新增成員僅生效於 instance object 身上。
允許 class object 在 class 定義區塊外 自行添加新的屬性與函數。
但相關新增成員會生效在除了 class instance 外也會在 instance object 身上生效。
同時新增同名變數,各自擁有一個私有變數。
這情境也可說成 instance object 遮蔽了 class object 成員
大部分書籍會討論到這項功能。 沒錯,語法上來說這是一個功能,但很明顯的他會造成管理與維護上的成本。
所以應該避開。 可以從 class object 身上來防呆。
如下:
Pythonic Way 變數命名規則:
基本上不該建立同名變數
變數命名規則: 例如 class object 追加的成員以 single underscore 為前綴,宣告是 class level。
常數設計方式: 也許可以用常數或雙底線告知,這是 class level 用的成員。
- 這段落僅是個人記憶與理解用,並未深層研究 Python Interpreter 實際運方式作方式。
- 由 Python class 建立 instance object 過程比較類似 POM 或 XML 的繼承。
- 可以想成是,composite 結構。 Instance Object 吃了一份 Class Object 結構在身上。
- 所以 Object Instance 擁有 Class Object 的 reference。
- Python 允許物件 instacne 建立後,再追加成員變數。
- 由此推導,
- 當 Class Object 事後追加成員時,Instance Object 因為擁有 Class reference,所以也能讀取追加成員。
- 反之當 Instance Object 事後追加成員時,Class Object 因無法得知 Instance reference,所以無從得知。
- 但當 Class / Instance Object 都追加同名成員時,因為 Shadow effect 造成的影響。Instance 換改用私有成員。
Variable 追加
from biology.animal import Animal
dog = Animal(True, voice="bark!")
cat = Animal(False, voice="mew~")
print(dog.get_voice())
print(dog.get_gender())
# 遮蔽發生, 避免使用
dog.alive = False # alive 遮蔽,變成 dog scope 的成員.
print(Animal.alive) # True, 自作孽該死, class 變數名應該給予警示。
print(dog.alive) # False
dog.eat = 'bone' # 正常追加的 instance variable
Animal._action = 'Run'
print(dog._action) # Run
# IDE 也會出現警示: Access to a protected member _action of a class
dog._action = "Swim" # 見到 underscore prefix/suffix 應該要警覺!! We are all consenting adults!
print(dog._action) # shadow effect, 變數遮蔽, 自作孽該死, 注意命名慣例 _X.
Instance Object 追加 Method
以 Java 作比喻,我的理解是:
Instance Object 類似 Class 的子類別,當想生出一個 anonymous 子類並擴充功能時使用。
步驟:
- 先建立一個新 method
- instance object 再以新成員變數與 method 建立關聯
- instance object 以新成員觸發新行為
import functools
class Student:
def __init__(self, name: str, grade: int, scores=[]):
self.name = name
self.grade = grade
self.scores = scores
def mean(scores: list = []) -> float:
if len(scores) == 0:
return 0
sum = functools.reduce(lambda a, b: a + b, scores)
return sum / len(scores)
totem = Student('Tom', 7)
totem.scores.append(60)
totem.scores.append(70)
print(totem.scores)
totem.average = mean # append new function
print(totem.average(totem.scores)) # 65.0
Class Object 追加 Function
我的理解是,為第三方提供的工具再追加其他功能函數時使用。
步驟:
- 先建立一個帶 self 參數 function (self 指向 instance object)
- function 可以藉由 self 取得 class 的其他成員
- class object 再以新成員變數與 function 建立關聯
- class object 以新成員觸發新行為
man.persion.py
class Person:
def __init__(self, first_name: str, last_name: str):
self.first_name = first_name
self.last_name = last_name
run.py
- 注意: 欲附加給 class 的 Function 需帶 self 參數
# append method to class
from man.person import Person
def full_name(self, separator: str): # 註意此處第一個參數是 self
return self.first_name + separator + self.last_name
Person.full_name = full_name
totem = Person("Totem", "Insect")
print(totem.full_name('_')) # Totem_Insect
__str__() 方法
__str__() 可以用來為 class 實作可讀的內容訊息。
通常這個訊息會與商業邏輯相關,這邊指的是有別於 __repr__() 會產生供程式(interpreter)使用的訊息內容。
__str__() 可以在回應使用者時使用。預設的 __str__() 會直接呼叫 __repr__()
使用時可以:
由 instance: x.__str__() 或
由 built-in: str(x)
class Student:
def __init__(self, name):
self.name = name
# default: equals to __repr__()
# 商業邏輯用
def __str__(self):
return self.name
s = Student('Totoem')
print(s) # Totoem
print(s.__str__()) # Totoem
print(str(s)) # Totoem
__repr__() 方法
__repr__() 可以將 instance 轉成 Python interpreter 可以辨認的文字內容。
因為,__repr__() 是供 interpreter 使用,所以通長不該像 __str__() 被覆寫改變,以確保 interpreter 作用正常。
部分情形下 __repr__() 與 eval() 可互相轉換。這邊指的通常是 Python 內建的類別間可互相轉換
__repr__(): 將 instance 轉成 string
eval() 則可以將 string 轉回 instance同樣可以由 instance/built-in fun 來呼叫:
由 instance: x.__repr__() 或
由 built-in: str(x)reference: Python Built-in Functions: repr()
strA = '[1,2,3]'
listA = eval(strA)
print(type(listA))
print(listA)
# <class 'list'>
# [1, 2, 3]