本文中所使用的譯名,主要參考侯捷的 中英程式譯詞對照。
class:類別
object:物件
instance:實體
property:屬性
attribute:屬性
initialize:初始化
method:方法(行為、函式)
function:函式(函數)
Section 2 github 原始碼
Initializing Class Instances
當我們用類別建立一個新實體時:
-
The object instance is created - 物件實體創建
Creates a new instance of the class.
The creation of the object (instance). -
The object instance is then further initialized - 物件實體初始化
Initializes the namespace of the class - 命名空間初始化
The initialization of the object (instance).
class MyClass:
language = 'Python'
obj = MyClass() # obj.__dict__ => {}
Part 1:
我們可以在 __init__
中,用 print 列印,就可以證明在 instance 創建時,__init__
就會被執行。
而被綁定的 instance,因為在 __init__
執行前就創建,所以才可以傳入 __init__
(第一個參數),以下程式碼的 namespace 位址,可以證明。
class Person:
def __init__(self):
print(f'Initializing a new Person object: {self}')
p = Person()
# Initializing a new Person object: <__main__.Person object at 0x7f80a022b0f0>
hex(id(p))
# '0x7f80a022b0f0'
Part 2:
正因為在 __init__
已經傳入 instance,我們可以藉此來操控 instance 其中的屬性(*args, **kwargs)。
<instance>.__init__(self, *args, **kwargs)
class Person:
def __init__(self, name):
self.name = name
p = Person('Eric')
p.__dict__
# {'name': 'Eric'}
我們可以通過使用特殊方法來 “攔截” 創建和初始化階段:__new__
& __init__
。
這裡不是完整程式,只是說明。
class MyClass:
language = 'Python' # class attribute (in class namespace)
obj = MyClass() # obj.__dict__ => {}
def __init__(self, version): # initialization, a method (bound function)
self.version = version
__init__
是一個 instance method .
當 __init__
被呼叫時,object (instance) 已經被創建了。
__init__
是綁定(bound)instance 的方法(method)。
__init__
function defined in the class is now treated like a method bound to the instance.
Part 3:
我們也可以不用 __init__
,改用自定義的 init
。只是這樣在 Initializing Class Instances 時,就不會自動執行,而要自己手動執行。
我們看一下實例:
# 方法一:正常作法 __init__
class Person:
def __init__(self, name):
self.name = name
p = Person('Eric')
p.__dict__
# {'name': 'Eric'}
# 方法二:自定義作法 init
class Person:
def init(self, name):
self.name = name
p = Person() # 沒有傳參數(因為沒有實作 __init__)
p.__dict__
# {} # 空的 dictionary
p.init('Eric')
p.__dict__
# {'name': 'Eric'} # 結果和方法一相同
那當然是選簡單的方法做啊。
Creating Attributes at Run-Time
這節就講一件事:我們可以在 instance 創建之後,才將 attribute method 綁定到 instance 上。
-
Attribute 可以是變數,也可以是函式(*args, **kwargs)。
-
attribute method 可以在
__init__
初始化時就綁定,也可以之後再綁定。 -
在
__init__
初始化時綁定的 method,每個 instance 都會有;之後再綁定的 method,只有被綁定的 instance 有。
Part I
在程式執行期間,修改 instance 的 attribute。
先看變數範例。以下示範兩件事:一、程式執行期間,修改 instance 的 attribute。二、以一的方法修改 attribute 時,只作用在被修改的 attribute。
class Person:
pass
p1 = Person()
p2 = Person()
p1.name = 'Alex'
p1.__dict__
# {'name': 'Alex'}
p2.__dict__
{}
接著看函式範例。注意:這裡的函式,未與 instance 綁定(Binding)。
(程式續上方範例)
p1.say_hello = lambda: 'Hello!'
p1.__dict__
# {'name': 'Alex', 'say_hello': <function __main__.<lambda>()>}
p1.say_hello
# <function __main__.<lambda>()>
p1.say_hello()
'Hello!'
p2.__dict__
{}
Part II:MethodType
接著就來看,在程式執行期間,將函式與 instance 綁定,成為 method。
MethodType 語法如下。function 是我們要綁定的函式、object 是被綁定的 object (instance)。
from types import MethodType
MethodType(function, object)
MethodType 範例:
from types import MethodType
class Person:
def __init__(self, name):
self.name = name
p1 = Person('Eric')
p2 = Person('Alex')
def say_hello(self):
return f'{self.name} says hello!'
say_hello(p1), say_hello(p2)
# ('Eric says hello!', 'Alex says hello!')
## 複習:MethodType(function, object)
## function 是我們要綁定的函式
## object 是被綁定的 object (instance)
p1_say_hello = MethodType(say_hello, p1)
p1_say_hello
# <bound method say_hello of <__main__.Person object at 0x7f9750295630>>
## 但 p1 無法呼叫(dotted notation 和 getattr 皆是)
try:
p1.p1_say_hello()
except AttributeError as ex:
print(ex)
# 'Person' object has no attribute 'p1_say_hello'
## 將上述 method 加入 instance dictionary 中即可
p1.say_hello = p1_say_hello
p1.__dict__
# {'name': 'Eric',
'say_hello': <bound method say_hello of <__main__.Person object at 0x7f9750295630>>}
## 然後就可以呼叫了(dotted notation 和 getattr 皆是)
p1.say_hello()
# 'Eric says hello!'
getattr(p1, 'say_hello')()
'Eric says hello!'
## p2 目前為止,完全置身事外
p2.__dict__
{'name': 'Alex'}
簡單小結:
-
寫一個 function
-
透過 MethodType 與 instance 綁定
-
加入 instance dictionary 中(透過 dotted notation 指定)
p1 = Person('Alex')l
p1.__dict__
# {'name': 'Alex'}
## 上述三件事,一行程式搞定。
p1.say_hello = MethodType(lambda self: f'{self.name} says hello', p1)
p1.say_hello()
# 'Alex says hello'
Part III
假設我們要讓 class 中的同樣 function,依照 instance 各自的 attribute 執行。
繼承是一個解法,但透過上述方式(事後綁定)來做,可以達成類似一種 plugin 的效果。
from types import MethodType
class Person:
def __init__(self, name):
self.name = name
def register_do_work(self, func):
setattr(self, '_do_work', MethodType(func, self))
def do_work(self):
do_work_method = getattr(self, '_do_work', None)
# if attribute exists we'll get it back, otherwise it will be None
if do_work_method:
return do_work_method()
else:
raise AttributeError('You must first register a do_work method')
math_teacher = Person('Eric')
english_teacher = Person('John')
try:
math_teacher.do_work()
except AttributeError as ex:
print(ex)
# You must first register a do_work method
數學老師的 do_work
def work_math(self):
return f'{self.name} will teach differentials today.'
math_teacher.register_do_work(work_math)
math_teacher.__dict__
# {'name': 'Eric',
'_do_work': <bound method work_math of <__main__.Person object at 0x7f97584cdac8>>}
math_teacher.do_work()
# 'Eric will teach differentials today.'
英文老師的 do_work
def work_english(self):
return f'{self.name} will analyze Hamlet today.'
english_teacher.register_do_work(work_english)
english_teacher.do_work()
# 'John will analyze Hamlet today.'
instance properties
大家講到 Python Class 中的 attribute 和 method 時,最常拿來和一般的 property 和 function 作比較。所以先強調,本節所講的 property,是在說明 instance 中的 property。
instance properties 和 instance attributes 很類似,主要區別在於我們將使用訪問器方法(Accessor Method)來獲取、設置及刪除關聯的實體值。
補充參考資料:Accessor and Mutator methods in Python - GeeksforGeeks
-
Accessor Method: 此方法用於訪問對象的狀態,即可以通過此方法訪問隱藏在對像中的數據。但是,這個方法不能改變對象的狀態,它只能訪問隱藏的數據。我們可以用 get 來命名這些方法。
-
Mutator Method: 此方法用於改變/修改對象的狀態,即,它改變數據變量的隱藏值。它可以立即將變量的值設置為新值。此方法也稱為更新方法。此外,我們可以用單詞 set 來命名這些方法。
bare attribute 裸屬性
在Python中,裸屬性(Bare attribute)是指直接在類別定義中定義的屬性,而不使用特殊的裝飾器或設置器方法,例如@property
,@attribute.setter
等等。
裸屬性是一種簡單的數據屬性,只能通過 點記號(dotted notation)直接訪問或設置,無法對訪問或設置行為進行進一步控制。
class Person:
def __init__(self, name):
self.name = name
p = Person('Alex')
# 讀取、寫入:dotted notation or the getattr and setattr methods
p.name # 方法一
getattr(p, 'name') # 方法二
setattr(p, 'name', 'Eric')
假設我們要對上述的人名 name 設限,限定為 非空白字串
。
因為 Python 不像 Java 有 private 變數這種設定,所以採用約定成俗的前置底線 _
來告訴其他程式設計師(或是忘性極佳的、未來的自己),這是內部專用的私有變數,建議你不要亂搞,否則出槌概不負責。
以下 _name
為範例:
class Person:
def __init__(self, name):
self.set_name(name)
def get_name(self):
return self._name
def set_name(self, value):
if isinstance(value, str) and len(value.strip()) > 0:
# this is valid
self._name = value.strip()
else:
raise ValueError('name must be a non-empty string')
然後乖乖的使用 get_name
& set_name
來存取修改其值。
p = Person('Alex')
p.set_name('Eric')
p.get_name()
property function
因為程式設計師大多很懶,總想用最簡捷的方式來寫扣,所以 Python 就變出 property function
來滿足大家的需求。
讓程式看起來是使用 點記號(dotted notation)
,但骨子裡其實還是用 訪問器方法
。
class Person:
def __init__(self, name):
self.name = name # note how we are actually setting the value for name using the property!
def get_name(self):
return self._name
def set_name(self, value):
if isinstance(value, str) and len(value.strip()) > 0:
# this is valid
self._name = value.strip()
else:
raise ValueError('name must be a non-empty string')
name = property(fget=get_name, fset=set_name)
執行看看:
p = Person('Alex')
p.name
p.name = 'Eric'
try:
p.name = None
except ValueError as ex:
print(ex)
# name must be a non-empty string
補充:其實我們也可以直接呼叫 getattr
& setattr
,結果一樣。
setattr(p, 'name', 'John') # or p.name = 'John'
getattr(p, 'name') # or simply p.name
# 我們來看看 instance dictionary
p.__dict__
# {'_name': 'John'}
從上面可以看出,instance dictionary 裡是 _name
這個私有變數,而不是 name
(property),請參考以下資訊。
Person.__dict__
# 輸出結果
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.Person.__init__(self, name)>,
'get_name': <function __main__.Person.get_name(self)>,
'set_name': <function __main__.Person.set_name(self, value)>,
'name': <property at 0x7fbad886e138>, # 注意 name 的 type 是 property
'__dict__': <attribute '__dict__' of 'Person' objects>,
'__weakref__': <attribute '__weakref__' of 'Person' objects>,
'__doc__': None})
當看到 class 中使用 property function 時,Python 很清楚知道是要用 訪問器方法
,而不是點記號(dotted notation)
。
看看 Python 並不會搞錯 _name
和 name
的角色。
# 續
p = Person('Alex')
p.name
# 'Alex'
p.__dict__
# {'_name': 'Alex'}
p.__dict__['name'] = 'John'
p.__dict__
# {'_name': 'Alex', 'name': 'John'}
p.name # getter method
# 'Alex'
p.name = 'Raymond' # setter method
p.__dict__
# {'_name': 'Raymond', 'name': 'John'}
# 然後用 setattr & getattr 再做一次來證明,本處略。
接著老師示範 deleter method 的作法,一樣用 property function 來讓 Python 知道要用訪問器方法,這裡就不贅述了。
參考資料
Attribute vs. Property
Attribute | Property |
---|---|
Attributes are defined by data variables like name, age, height etc. | Properties are special type of attributes. |
There are two types of attributes - Class attributes & Instance attributes |
Property method comes with the getter, setter and delete methods like get, set, and delete methods. |
Class attributes are defined in the class body not in the functions. | We can define getters, setters, and delete methods with the property() function. |
Instance attributes are defined in the class body using the self keyword usually it the init() method. | To read the property, we can use the @property decorator which can be added above our method. |
來源:
講人話的解釋