草根學Python(九) 面向物件
前言
這篇寫的很糾結,不過還是寫完了。弄了個很遜的公眾號,如果對本文有興趣,可以關注下公眾號喔,會持續更新。
目錄
一、面向物件的概念
Python 是一門面向物件的語言, 面向物件是一種抽象,抽象是指用分類的眼光去看世界的一種方法。 用 JAVA 的程式設計思想來說就是:萬事萬物皆物件。也就是說在面向物件中,把構成問題事務分解成各個物件。
面向物件有三大特性,封裝、繼承和多型。
1、面向物件的兩個基本概念
- 類
用來描述具有相同的屬性和方法的物件的集合。它定義了該集合中每個物件所共有的屬性和方法。物件是類的例項。
- 物件
通過類定義的資料結構例項
2、面向物件的三大特性
- 繼承
即一個派生類(derived class)繼承基類(base class)的欄位和方法。繼承也允許把一個派生類的物件作為一個基類物件對待。
例如:一個 Dog 型別的物件派生自 Animal 類,這是模擬”是一個(is-a)”關係(例圖,Dog 是一個 Animal )。
- 多型
它是指對不同型別的變數進行相同的操作,它會根據物件(或類)型別的不同而表現出不同的行為。
- 封裝性
“封裝”就是將抽象得到的資料和行為(或功能)相結合,形成一個有機的整體(即類);封裝的目的是增強安全性和簡化程式設計,使用者不必瞭解具體的實現細節,而只是要通過外部介面,一特定的訪問許可權來使用類的成員。
二、類
1、定義類
類定義語法格式如下:
class ClassName:
<statement-1>
.
.
.
<statement-N>
一個類也是由屬性和方法組成的,有些時候我們定義類的時候需要設定類的屬性,因此這就需要構造函
類的建構函式如下:
def __init__(self,[...):
類定義了 init() 方法的話,類的例項化操作會自動呼叫 init() 方法。
那麼如建構函式相對應的是解構函式,理所當然,一個類建立的時候我們可以用過建構函式設定屬性,那麼當一個類銷燬的時候,就會呼叫解構函式。
解構函式語法如下:
def __del__(self,[...):
仔細觀察的童鞋都會發現,類的方法與普通的函式有一個特別的區別,它們必須有一個額外的第一個引數名稱, 按照慣例它的名稱是 self。
那麼這個 self 代表什麼呢?
我們可以看下例項,通過例項來找出答案:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()
觀察輸出的結果:
從執行結果可以很明顯的看出,self 代表的是類的例項,輸出的是當前物件的地址,而 self.__class__
則指向類。
當然 self 不是 python 關鍵字,也就是說我們把他換成其他的字元也是可以正常執行的。只不過我們習慣使用 self
2、Python 定義類的歷史遺留問題
Python 在版本的迭代中,有一個關於類的歷史遺留問題,就是新式類和舊式類的問題,具體先看以下的程式碼:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 舊式類
class OldClass:
pass
# 新式類
class NewClass(object):
pass
可以看到,這裡使用了兩者中不同的方式定義類,可以看到最大的不同就是,新式類繼承了object
類,在 Python2 中,我們定義類的時候最好定義新式類,當然在 Python3 中不存在這個問題了,因為 Python3 中所有類都是新式類。
那麼新式類和舊式類有什麼區別呢?
執行下下面的那段程式碼:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 舊式類
class OldClass:
def __init__(self, account, name):
self.account = account;
self.name = name;
# 新式類
class NewClass(object):
def __init__(self, account, name):
self.account = account;
self.name = name;
if __name__ == '__main__':
old_class = OldClass(111111, 'OldClass')
print(old_class)
print(type(old_class))
print(dir(old_class))
print('\n')
new_class=NewClass(222222,'NewClass')
print(new_class)
print(type(new_class))
print(dir(new_class))
仔細觀察輸出的結果,對比一下,就能觀察出來,注意喔,Pyhton3 中輸出的結果是一模一樣的,因為Python3 中沒有新式類舊式類的問題。
三、類的屬性
1、直接在類中定義屬性
定義類的屬性,當然最簡單最直接的就是在類中定義,例如:
class UserInfo(object):
name='兩點水'
2、在建構函式中定義屬性
故名思議,就是在構造物件的時候,對屬性進行定義。
class UserInfo(object):
def __init__(self,name):
self.name=name
3、屬性的訪問控制
在 Java 中,有 public (公共)屬性 和 private (私有)屬性,這可以對屬性進行訪問控制。那麼在 Python 中有沒有屬性的訪問控制呢?
一般情況下,我們會使用 __private_attrs
兩個下劃線開頭,宣告該屬性為私有,不能在類地外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs
。
為什麼只能說一般情況下呢?因為實際上, Python 中是沒有提供私有屬性等功能的。但是 Python 對屬性的訪問控制是靠程式設計師自覺的。為什麼這麼說呢?看看下面的示例:
仔細看圖片,為什麼說雙下劃線不是真正的私有屬性呢?我們看下下面的例子,用下面的例子來驗證:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
class UserInfo(object):
def __init__(self, name, age, account):
self.name = name
self._age = age
self.__account = account
def get_account(self):
return self.__account
if __name__ == '__main__':
userInfo = UserInfo('兩點水', 23, 347073565);
# 列印所有屬性
print(dir(userInfo))
# 列印建構函式中的屬性
print(userInfo.__dict__)
print(userInfo.get_account())
# 用於驗證雙下劃線是否是真正的私有屬性
print(userInfo._UserInfo__account)
輸出的結果如下圖:
四、類的方法
1、類專有的方法
一個類建立的時候,就會包含一些方法,主要有以下方法:
類的專有方法:
方法 | 說明 |
---|---|
__init__ |
建構函式,在生成物件時呼叫 |
__del__ |
解構函式,釋放物件時使用 |
__repr__ |
列印,轉換 |
__setitem__ |
按照索引賦值 |
__getitem__ |
按照索引獲取值 |
__len__ |
獲得長度 |
__cmp__ |
比較運算 |
__call__ |
函式呼叫 |
__add__ |
加運算 |
__sub__ |
減運算 |
__mul__ |
乘運算 |
__div__ |
除運算 |
__mod__ |
求餘運算 |
__pow__ |
乘方 |
當然有些時候我們需要獲取類的相關資訊,我們可以使用如下的方法:
type(obj)
:來獲取物件的相應型別;isinstance(obj, type)
:判斷物件是否為指定的 type 型別的例項;hasattr(obj, attr)
:判斷物件是否具有指定屬性/方法;getattr(obj, attr[, default])
獲取屬性/方法的值, 要是沒有對應的屬性則返回 default 值(前提是設定了 default),否則會丟擲 AttributeError 異常;setattr(obj, attr, value)
:設定該屬性/方法的值,類似於 obj.attr=value;dir(obj)
:可以獲取相應物件的所有屬性和方法名的列表:
2、方法的訪問控制
其實我們也可以把方法看成是類的屬性的,那麼方法的訪問控制也是跟屬性是一樣的,也是沒有實質上的私有方法。一切都是靠程式設計師自覺遵守 Python 的程式設計規範。
示例如下,具體規則也是跟屬性一樣的,
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
class User(object):
def upgrade(self):
pass
def _buy_equipment(self):
pass
def __pk(self):
pass
3、方法的裝飾器
@classmethod
呼叫的時候直接使用類名類呼叫,而不是某個物件@property
可以像訪問屬性一樣呼叫方法
具體的使用看下例項:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
class UserInfo(object):
lv = 5
def __init__(self, name, age, account):
self.name = name
self._age = age
self.__account = account
def get_account(self):
return self.__account
@classmethod
def get_name(cls):
return cls.lv
@property
def get_age(self):
return self._age
if __name__ == '__main__':
userInfo = UserInfo('兩點水', 23, 347073565);
# 列印所有屬性
print(dir(userInfo))
# 列印建構函式中的屬性
print(userInfo.__dict__)
# 直接使用類名類呼叫,而不是某個物件
print(UserInfo.lv)
# 像訪問屬性一樣呼叫方法(注意看get_age是沒有括號的)
print(userInfo.get_age)
執行的結果:
五、類的繼承
1、定義類的繼承
首先我們來看下類的繼承的基本語法:
class ClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
在定義類的時候,可以在括號裡寫繼承的類,一開始也提到過,如果不用繼承類的時候,也要寫繼承 object 類,因為在 Python 中 object 類是一切類的父類。
當然上面的是單繼承,Python 也是支援多繼承的,具體的語法如下:
class ClassName(Base1,Base2,Base3):
<statement-1>
.
.
.
<statement-N>
多繼承有一點需要注意的:若是父類中有相同的方法名,而在子類使用時未指定,python 在圓括號中父類的順序,從左至右搜尋 , 即方法在子類中未找到時,從左到右查詢父類中是否包含方法。
那麼繼承的子類可以幹什麼呢?
繼承的子類的好處:
* 會繼承父類的屬性和方法
* 可以自己定義,覆蓋父類的屬性和方法
2、呼叫父類的方法
一個類繼承了父類後,可以直接呼叫父類的方法的,比如下面的例子,UserInfo2
繼承自父類 UserInfo
,可以直接呼叫父類的 get_account
方法。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
class UserInfo(object):
lv = 5
def __init__(self, name, age, account):
self.name = name
self._age = age
self.__account = account
def get_account(self):
return self.__account
class UserInfo2(UserInfo):
pass
if __name__ == '__main__':
userInfo2 = UserInfo2('兩點水', 23, 347073565);
print(userInfo2.get_account())
3、父類方法的重寫
當然,也可以重寫父類的方法。
示例:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class UserInfo(object):
lv = 5
def __init__(self, name, age, account):
self.name = name
self._age = age
self.__account = account
def get_account(self):
return self.__account
@classmethod
def get_name(cls):
return cls.lv
@property
def get_age(self):
return self._age
class UserInfo2(UserInfo):
def __init__(self, name, age, account, sex):
super(UserInfo2, self).__init__(name, age, account)
self.sex = sex;
if __name__ == '__main__':
userInfo2 = UserInfo2('兩點水', 23, 347073565, '男');
# 列印所有屬性
print(dir(userInfo2))
# 列印建構函式中的屬性
print(userInfo2.__dict__)
print(UserInfo2.get_name())
最後列印的結果:
這裡就是重寫了父類的建構函式。
3、子類的型別判斷
對於 class 的繼承關係來說,有些時候我們需要判斷 class 的型別,該怎麼辦呢?
可以使用 isinstance()
函式,
一個例子就能看懂 isinstance()
函式的用法了。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User1(object):
pass
class User2(User1):
pass
class User3(User2):
pass
if __name__ == '__main__':
user1 = User1()
user2 = User2()
user3 = User3()
# isinstance()就可以告訴我們,一個物件是否是某種型別
print(isinstance(user3, User2))
print(isinstance(user3, User1))
print(isinstance(user3, User3))
# 基本型別也可以用isinstance()判斷
print(isinstance('兩點水', str))
print(isinstance(347073565, int))
print(isinstance(347073565, str))
輸出的結果如下:
True
True
True
True
True
False
可以看到 isinstance()
不僅可以告訴我們,一個物件是否是某種型別,也可以用於基本型別的判斷。
六、類的多型
多型的概念其實不難理解,它是指對不同型別的變數進行相同的操作,它會根據物件(或類)型別的不同而表現出不同的行為。
事實上,我們經常用到多型的性質,比如:
>>> 1 + 2
3
>>> 'a' + 'b'
'ab'
可以看到,我們對兩個整數進行 + 操作,會返回它們的和,對兩個字元進行相同的 + 操作,會返回拼接後的字串。也就是說,不同型別的物件對同一訊息會作出不同的響應。
看下面的例項,來了解多型:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name):
self.name = name
def printUser(self):
print('Hello !' + self.name)
class UserVip(User):
def printUser(self):
print('Hello ! 尊敬的Vip使用者:' + self.name)
class UserGeneral(User):
def printUser(self):
print('Hello ! 尊敬的使用者:' + self.name)
def printUserInfo(user):
user.printUser()
if __name__ == '__main__':
userVip = UserVip('兩點水')
printUserInfo(userVip)
userGeneral = UserGeneral('水水水')
printUserInfo(userGeneral)
輸出的結果:
Hello ! 尊敬的Vip使用者:兩點水
Hello ! 尊敬的使用者:水水水
可以看到,userVip 和 userGeneral 是兩個不同的物件,對它們呼叫 printUserInfo 方法,它們會自動呼叫實際型別的 printUser 方法,作出不同的響應。這就是多型的魅力。
要注意喔,有了繼承,才有了多型,也會有不同類的物件對同一訊息會作出不同的相應。