現如今面向對象編程的使用非常廣泛,本文我們就來探討一下Python中的面向對象編程。
作者 | Radek Fabisiak
譯者 | 彎月,責編 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下為譯文:
Python支持多種類型的編程范式,例如過程式編程、函數式編程、面向對象編程,而且還可以融合多種類型的范式。
現如今面向對象編程的使用非常廣泛。面向對象編程的基本元素是對象,其包含的數據成員稱為屬性,函數(例程、過程)稱為方法。
對象是類的實例。換句話說,類主要定義對象的結構,然后我們以類為模板創建對象。類不但包含方法定義,而且還包含所有實例共享的數據。
本文我們來探討一下Python中的面向對象編程。我們將演示如何創建類,并使用類來實例化對象。本文的主要內容如下:
- 創建Python類
- 數據屬性
- 實例方法
- 屬性
- 類和靜態方法
- 繼承
本文無法涵蓋這些主題的所有詳細信息。Python中的面向對象編程還包含其他很多方面。希望本文能夠為你學習Python及實現面向對象提供一個良好的開端。
創建Python類
我們可以使用關鍵字class定義Python類,關鍵字后面緊跟類的名稱、分號和類的實現:
>>> classMyClass:
... pass
...
按照慣例,Python類的命名采用首字母大寫(即PascalCase)。
現在讓我們創建這個新類的一個實例,名為MyClass:
>>> a = MyClass
>>> a
<__main_ _.MyClass object at 0x7f32ef3deb70>
語句a = MyClass創建了MyClass的一個實例,并將它的引用賦值給變量a。
我們可以通過Python內置的函數type或直接通過屬性.__class__來獲取類型(即對象的類)。在拿到類(類型)之后,我們就可以利用屬性.__ name__獲取類的名字:
>>> type(a)
< class' __main__. MyClass'>
>>> a.__class_ _
< class' __main__. MyClass'>
>>> a.__class_ _.__name_ _
'MyClass'
順便提一句,Python類也是對象。它們是type的實例:
>>> type(MyClass)
< class' type'>
下面,我們來定義一個方法。
Python中每個實例方法的第一個參數必須對應于該實例,即該對象本身。按照慣例,這個參數名為self。后面是其他參數(如果有需要的話)。在調用方法時,我們無需明確提供與參數self相對應的參數。
通常,我們需要定義的一個最重要的方法是.__init__。在類的實例創建后就會調用這個方法。該方法負責初始化類成員。我們定義的.__init__如下:
>>> classMyClass:
... def__init__(self, arg_1, arg_2, arg_3):
... print( f'an instance of {type(self).__name__}created' )
... print( f'arg_1: {arg_1}, arg_2: {arg_2}, arg_3: {arg_3}' )
...
下面,我們來創建一個MyClass實例,看看這個初始化方法的具體工作。我們的.__init__方法需要三個參數(arg_1、arg_2和arg_3),記住我們不需要傳遞與self對應的第一個參數。所以,在實例化對象時,我們需要傳遞三個參數:
>>> a = MyClass( 2, 4, 8)
an instance ofMyClasscreated
arg_1: 2, arg_2: 4, arg_3: 8
上述聲明產生的結果如下:
- 創建一個MyClass類型的對象的實例。
- 自動調用該實例的方法.__init__。
- 我們傳遞給MyClass方法的參數:(2,4和8)會被傳遞給.__init__。
- .__init__執行我們的請求,并輸出結果。它利用type(self).__name__獲取類的名稱。
現在我們得到了一個類,它有一個方法.__init__,以及這個類的一個實例。
數據屬性
下面我們來修改MyClass,增加一些數據屬性。
我們利用.__init__初始化和定義了實例,我們還可以在這個方法或其他實例方法中,通過給某個數據屬性賦值的方式改變屬性值:
>>> classMyClass:
... def__init__( self, arg_1, arg_2, arg_3) :
... self.x = arg_1
... self._y = arg_2
... self.__z = arg_3
...
現在MyClass有三個數據屬性:
- .x可以獲取arg_1的值
- ._y可以獲取arg_2的值
- .__ z可以獲取arg_3的值
我們可以利用Python的解包機制,用更緊湊的形式編寫這段代碼:
>>> classMyClass:
... def__init__( self, arg_1, arg_2, arg_3) :
... self.x, self._y, self.__z = arg_1, arg_2, arg_3
...
屬性名稱中的下劃線(_)是為了表明這些屬性是“私有”屬性:
- 開頭沒有下劃線的屬性(比如.x)通常可供對象外部的調用和修改。
- 開頭擁有一個下劃線的屬性(比如._y)通常也可以從對象外部調用和修改。然而,下劃線是一種慣用的標志,即該類的創建者強烈建議不要使用該變量。應該僅通過類的功能成員(比如方法和屬性)調用和修改該變量。
- 開頭擁有雙下劃線的屬性(比如.__ z)將在名字修飾過程中被改名(在本例中它將被改名為._MyClass__z)。你也可以通過這個新名稱從對象外部調用和修改它們。但是,我強烈反對這種做法。應該盡通過類的功能成員以其原始名稱進行調用和修改。
Python對象的數據屬性通常存儲在名為.__ dict__的字典中,它也是對象的屬性之一。但是,你也可以將數據屬性存儲在其他地方。我們可以直接訪問__dict__,或利用Python的內置函數vars獲取.__ dict__:
>>> a = MyClass( 2, 4, 8)
>>> vars(a)
{ 'x': 2, '_y': 4, '_MyClass__z': 8}
>>> a.__dict_ _
{ 'x': 2, '_y': 4, '_MyClass__z': 8}
名字修飾過程把鍵'__z'變成了'_MyClass__z'。
我們可以把.__ dict__當成普通的Python字典使用。
獲取和修改與數據屬性關聯的值的常規方法如下:
>>> a.x
2
>>> a._y
4
>>> a.__z
Traceback (most recent call last):
File "<stdin>", line 1, in< module>
AttributeError:'MyClass'object has no attribute '__z'
>>> a.x = 16
>>> a.x
16
>>> vars(a)
{ 'x': 16, '_y': 4, '_MyClass__z': 8}
請注意,我們無法訪問.__ z,因為.__ dict__沒有鍵'__z'。
實例方法
下面,我們來創建兩個實例方法:
●.set_z:修改.__ z。
●.get_z:返回.__ z的值。
請記住,每個實例方法的第一個參數(按照約定名為self)引用對象本身,但我們無需在調用方法時指定這個參數:
>>> classMyClass:
... def__init__( self, arg_1, arg_2, arg_3) :
... self.x, self._y, self.__z = arg_1, arg_2, arg_3
...
... defset_z( self, value) :
... self.__z = value
...
... defget_z( self) :
... returnself.__z
...
>>> b = MyClass( 2, 4, 8)
方法.get_z和.set_z提供了傳統的檢索和修改.__ z值的方法:
>>> b.get_z
8
>>> b.set_z( 16)
>>> vars(b)
{ 'x': 2, '_y': 4, '_MyClass__z': 16}
你也可以在.get_z和.set_z中添加其他功能,例如檢查數據的有效性。這種方法實現了面向對象編程中的一個主要概念:封裝。
屬性
還有一種方法(一種更Python的方式)訪問和修改數據屬性是使用屬性。屬性封裝了一系列方法:getter、setter和deleter,但其行為與普通的數據屬性相同。
下面的代碼實現了屬性.z,其中還包含.get_z和.set_z的功能:
>>> classMyClass:
... def__init__( self, arg_1, arg_2, arg_3) :
... self.x, self._y, self.__z = arg_1, arg_2, arg_3
...
... @property
... defz( self) :
... returnself.__z
...
... @z.setter
... defz( self, value) :
... self.__z = value
...
>>> b = MyClass( 2, 4, 8)
如下,我們利用相應的屬性.z來訪問和修改數據屬性.__ z:
>>> b.z
8
>>> b.z = 16
>>> vars(b)
{ 'x': 2, '_y': 4, '_MyClass__z': 16}
這段代碼比上述示例更精簡優雅。
類與靜態方法
除了實例方法和屬性之外,類還可以擁有類方法和靜態方法。
下面讓我們為MyClass添加三個方法:
>>> classMyClass:
... def__init__(self, arg_1, arg_2, arg_3):
... self.x, self._y, self.__z = arg_1, arg_2, arg_3
...
... deff(self, arg):
... print( 'instance method f called')
... print( f'instance: {self}' )
... print( f'instance attributes:n {vars(self)}' )
... print( f'class: {type(self)}' )
... print( f'arg: {arg}' )
...
... @classmethod
... defg(cls, arg):
... print( 'class method g called')
... print( f'cls: {cls}' )
... print( f'arg: {arg}' )
...
... @staticmethod
... defh(arg):
... print( 'static method h called')
... print( f'arg: {arg}' )
...
>>> c = MyClass( 2, 4, 8)
方法.f是一個實例方法。實例方法的第一個參數是對象本身的引用。這些方法可以利用self訪問對象,利用vars(self)或self.__dict__訪問對象的數據屬性,還可以利用type(self)或self.__class__訪問對象對應的類,而且它們還可以擁有自己的參數。
方法.g的開頭包含修飾器@classmethod,表明這是一個類方法。每個類方法的第一個參數都會指向類本身,按照約定該參數名為cls。與實例方法的情況一樣,我們不需要明確提供與cls對應的參數。而類方法可以利用cls和自己的參數訪問類本身。
方法.h的開頭包含修飾器@staticmethod,表明這是一個靜態方法。靜態方法只能訪問自己的參數。
Python中常見的調用實例方法的方法如下:
>>> c.f( 'my-argument')
instance method f called
instance:<__main_ _.MyClass object at 0x7f32ef3def98>
instance attributes:
{ 'x': 2, '_y': 4, '_MyClass__z': 8}
class: <class ' __main__. MyClass'>
arg:my-argument
通常,我們應該直接通過類(而不是實例)調用類方法和靜態方法:
>>> MyClass.g( 'my-argument')
classmethodgcalled
cls:< class' __main__. MyClass'>
arg:my-argument
>>> MyClass.h( 'my-argument')
static method h called
arg:my-argument
請記住,我們不需要傳遞類方法的第一個參數:與cls相對應的參數。
但是,我們可以像下面這樣調用類方法和靜態方法:
>>> c.g( 'my-argument')
classmethodgcalled
cls:< class' __main__. MyClass'>
arg:my-argument
>>> c.h( 'my-argument')
static method h called
arg:my-argument
當我們調用c.g或c.h,但實例成員沒有這樣的名稱時,Python會搜索類和靜態成員。
繼承
繼承是面向對象編程的另一個重要特性。在這個概念中,類(稱為子類或派生類)會繼承其他類(稱為超類或基類)的數據和函數成員。
在Python中,所有類都會默認繼承Python自帶的類對象。但是,我們可以根據需要定義合適的類繼承層次結構。
例如,我們可以創建一個名為MyOtherClass的新類,該類繼承了MyClass:
>>> classMyOtherClass(MyClass):
... def__init__(self, u, v, w, x, y, z):
... super.__init__(x, y, z)
... self.__u, self.__v, self.__w = u, v, w
...
... deff_(self, arg):
... print( 'instance method f_ called')
... print( f'instance: {self}' )
... print( f'instance attributes:n {vars(self)}' )
... print( f'class: {type(self)}' )
... print( f'arg: {arg}' )
...
>>> d = MyOtherClass( 1, 2, 4, 8, 16, 32)
如上,MyOtherClass擁有MyClass的成員:.x、._y、.__z以及.f。你可以通過語句super.__init__(x, y, z)初始化基類的數據成員x、._y和.__z,該語句會調用基類的.__init__方法。
除此之外,MyOtherClass還有自己的成員:.__u、.__v、.__w和.f_。
下面,我們通過vars獲取數據成員:
>>> vars(d)
{ 'x': 8,
'_y': 16,
'_MyClass__z': 32,
'_MyOtherClass__u': 1,
'_MyOtherClass__v': 2,
'_MyOtherClass__w': 4}
我們可以調用基類和派生類中的所有方法:
>>> d.f( 'some-argument')
instance method f called
instance:<__main_ _.MyOtherClass object at 0x7f32ef3e7048>
instance attributes:
{ 'x': 8,
'_y': 16,
'_MyClass__z': 32,
'_MyOtherClass__u': 1,
'_MyOtherClass__v': 2,
'_MyOtherClass__w': 4}
class: <class ' __main__. MyOtherClass'>
arg:some-argument
>>> d.f _( 'some-argument')
instance method f _called
instance:<__main_ _.MyOtherClass object at 0x7f32ef3e7048>
instance attributes:
{ 'x': 8,
'_y': 16,
'_MyClass__z': 32,
'_MyOtherClass__u': 1,
'_MyOtherClass__v': 2,
'_MyOtherClass__w': 4}
class: <class ' __main__. MyOtherClass'>
arg:some-argument
但是,如果派生類包含的某個成員與基類同名,則優先使用派生類的成員。
總結
面向對象編程是Python支持的編程范式之一。面向對象蘊含的抽象以及表征的現實世界行為在某些時候會非常有幫助性。然而,有時也可能會違反直覺,并為開發過程帶來不必要的麻煩。
在本文中,我們介紹了如何利用Python編寫基本的面向對象程序。Python中還有很多類和面向對象的功能,例如:
- 方法:.__repr__和.__str__
- 方法:.__new__
- 操作符
- 方法:.__getattribute__、.__getattr__、.__setattr__和.__delattr__
- 生成器
- 可調用性
- 創建序列
- 描述器
- 上下文管理
- 抽象類和成員
- 多重繼承
- 使用super
- 拷貝
- 序列化
- slot
- 類修飾器
- 數據類
等等……
現如今面向對象是非常流行的編程方式。如果你立志做一名Python開發人員,那么就應該學習面向對象編程。但請不要忘記,Python還支持其他編程范式,例如過程式編程、函數式編程等,在某些情況下也許選用這些范例更為合適。
盡情享受編程的快樂!
原文:https://www.blog.duomly.com/object-oriented-programming-in-python/
本文為 CSDN 翻譯,轉載請注明來源出處。