python之面向物件程式設計基礎
面向物件程式設計是一種程式設計正規化,在面向物件程式設計中有一個很重要的概念就是類。
什麼是類呢?類是一個抽象的概念,它是一類具有共同特徵的具體物件的抽象描述。比如在一個學校有張三,李四,王五等學生,他們都是具體的一個個物件(也稱作例項),把他們抽象成一個概念就是學生這個類。
1.類物件
由於在python中一切皆物件,所以我會把類稱為類物件,例項稱為例項物件。類物件建立的程式碼如下:
class MyClass:
i = 12345
def func(self):
return 'hello world'
這樣就建立好了類物件,class與def宣告不同的地方是,第一次程式碼執行到class會繼續執行裡面的程式碼,因為以後例項的建立會依賴類物件。
類物件支援的兩種操作是屬性引用和例項化。
(1)屬性引用:
資料屬性(以後稱為屬性),Myclass.i稱為資料屬性。
方法屬性(以後稱為方法),Myclass.func稱為方法屬性,方法的使用方式和屬性的使用方式不同,需要加括號呼叫。
(2)例項化:
依據類物件建立例項,例如:s = Myclass(),這樣就建立了一個例項s。
2.例項物件
類物件經過例項化以後就生成了一個例項,例如:
class MyClass:
i = 12345
def __init__(self,name): #初始化例項的資料屬性值
self.name = name
def func(self):
return 'hello world'
s = Myclass("i am s")
例項物件支援一種操作,屬性引用,但是是屬性引用分為資料屬性和方法屬性。
(1)資料屬性:
s.name就稱為例項的資料屬性。
(2)方法屬性:
s.func就稱為例項方法屬性(但是不存在與例項物件中,只存在於類物件中),例項物件只儲存自己的資料屬性。
3.方法物件
方法物件這個概念其實是為了解釋在呼叫方法時python直譯器做的一些工作。
在呼叫不管是類的方法(以上還沒有寫屬於類的方法),例項的方法時,本質都是在呼叫類物件裡面封裝的函式。只是在呼叫的時候,例如:s.func(),直譯器裡面是執行Myclass.func(s)這個操作,表示呼叫Myclass類裡面的func函式,把s作為引數傳入,當然你也可以傳入多個其它引數。好,接下來python直譯器就把上面的呼叫函式的操作封裝成一個物件,這個物件就是方法物件s.func。
綜上的描述,我們理順在呼叫方法時(如s.func())的執行流程:首先,會判斷例項物件的類物件裡面是否有func函式,如果有那麼直譯器會把s這個例項物件的資料屬性和這個func函式封裝進一個抽象物件即方法物件s.func,當s.func()呼叫這個方法物件時將自己作為第一個引數和其它引數一起傳入到func這個函式,最後執行函式的功能(也就是操作例項物件的資料屬性)。
4.關於類和例項的特性驗證
操作類和例項就是為了操作它們的資料屬性,我們可以通過內裡面定義的函式去操作,也可以從外部用函式去操作,例如:
#從外部操作
class Student(object):
count = 0
def __init__(self, name):
self.name = name
wilber = Student("Wilber")
def foo(self):#定義從外部操作例項的函式
print(self.name)
foo(wilber)#從外部操作例項物件的資料屬性
def foo(cls):#定義從外部操作類的函式
print(cls.count)
foo(Student)#從外部操作類物件的資料屬性
#從內部操作
class Student(object):
def __init__(self, name):
self.name = name
def foo(self):
print(self.name)
wilber = Student("Wilber")
wilber.foo()#從內部操作例項物件的資料屬性,相當於Student.foo(wilber),這裡沒有封裝操作類的函式
5.資料屬性
資料屬性對於類和例項來說無非就兩種情況:公有資料、私有資料。(相對於類內部和類外部)
(1)對於類物件的資料屬性建立和使用如下
#類資料屬性的建立
class Student(object):
name = "age" #類的公有資料屬性建立
__age = 18 #類的私有資料屬性建立
def foo(self): #通過例項物件操作類的資料屬性的函式
print(Student.name)
print(Student.__age)
@classmethod
def bar(cls): #通過類物件操作類的資料屬性的函式(還可以其它類的方法)
print(cls.name)
print(cls.__age)
Student.count = 90 #建立類的資料屬性,只能是公有的(一般不會這樣建立)
#內部操作
Student.bar() #通過類操作類的資料屬性
s = Student()
s.foo() #通過例項操作類的資料屬性
#結論:從內部操作類的資料屬性無論是公有還是私有都可以進行,只是需要注意操作方法。
#外部操作
def name(cls): #通過外部操作共有屬性
print(cls.name)
def age(cls): #通過外部操作私有屬性
print(cls.__age)
name(Student) #成功
age(Student) #報錯
#結論:從外部只能操作公有資料屬性
綜上所述,類物件的資料屬性有公有和私有之分(相對於外部和內部),公有資料屬性可以隨意從外部或內部操作,私有資料屬性只能從類內部自己操作。操作的方法有兩類,一類是通過例項的方法來操作(注意定義的方法),二類是通過各種類的方法來操作(以後會談到類方法)。
(2)對於例項物件資料屬性的建立和操作如下
class Student(object):
pass
#例項資料屬性的建立
s = Student() #建立例項物件
s.count = 10 #對例項物件建立屬性count,這樣只能建立公有資料屬性(一般不會這樣建立)
print(s.count)
#如果我們想在建立例項物件時初始化一些屬性(公有和私有),我們可以這樣
class Student(object):
def __init__(self, name, age): #初始化例項的資料屬性值
self.name = name #例項物件公有資料屬性
self.__age = age #例項物件私有資料屬性
s = Student("jack", 18)
s.count = 10 #從外部設定例項資料屬性
s.__count = 90 #不是私有資料屬性
#例項資料屬性的操作
class Student(object):
def __init__(self, name, age):
self.name = name #例項物件公有資料屬性
self.__age = age #例項物件私有資料屬性
def foo(self):
print(self.name)
def bar(self):
print(self.__age)
s = Student("jack", 18)
#從內部操作
s.foo() #操作公有資料屬性
s.bar() #操作私有資料屬性
#結論:從內部可以隨意操作例項資料屬性
#從外部操作
def name(self):
print(self.name)
def age(self):
print(self.__age)
name(s) #操作公有資料屬性,成功
age(s) #操作私有資料屬性,報錯
#結論:從外部只能操作公有資料屬性,不能操作私有資料屬性
綜上所述,例項物件的資料屬性有公有和私有之分(相對於外部和內部),公有資料屬性可以隨意從外部或內部操作,私有資料屬性只能從類內部自己操作。
6.方法屬性
對於類物件有它自己的方法屬性,對於例項物件有它自己的方法屬性,我們分別來探討。
(1)類物件方法屬性
類物件的方法屬性分為,類方法和靜態方法。類方法可以由@classmethod裝飾器建立,靜態方法可以由@staticmethod裝飾器建立。
程式碼演示如下:
class Student(object):
name = "student"
@classmethod #類方法,用裝飾器@classmethod建立
def foo(cls): #類方法需要傳入一個cls作為第一個引數
print("classmethod")
print(cls.name)
@staticmethod #靜態方法,用裝飾器@staticmethod建立
def bar():
print("staticmethod")
print(Student.name)
Student.foo() #通過類物件使用類方法
Student.bar() #通過類物件使用靜態方法
s = Student()
s.foo() #通過例項物件使用類方法
s.bar() #通過例項物件使用靜態方法
#結論:對於類物件的類方法和靜態方法,類和它的例項物件都可以使用
class Abc(Student):
name = "Abc"
pass
Abc.foo() #操作類的類方法,自行執行檢視區別
Abc.bar() #操作類的靜態方法,自行執行檢視區別
靜態方法和類方法的區別:
從定義方式上看,靜態方法使用@staticmethod裝飾器建立,類方法使用@classmethod裝飾器建立並且第一個引數cls表示類本身。
(2)例項物件的方法屬性
例項方法屬性。程式碼如下:
class Student(object):
def __init__(self, name):
self.name = name
def foo(self): #例項的方法,第一個self引數表示例項自己
print(self.name)
s = Student("jack")
s.foo() #例項物件呼叫例項方法
Student.foo() #報錯 #類物件呼叫例項方法
#結論:例項方法只能例項物件呼叫,類物件不能呼叫
用例項方法只能s.foo()這樣來呼叫,我們還可以用@property裝飾器來將方法屬性偽裝成資料屬性的呼叫方式s.foo也可以執行。但是這裡只是偽裝,實際還是通過方法屬性來呼叫。
@property裝飾器
class Student(object):
def __init__(self, name):
self._name = name
@property #property裝飾器,將方法的呼叫方式偽裝成資料屬性的呼叫方式,用於讀資料
def foo(self):
"i am foo's property" #文件字串
return self._name
@foo.setter #上面使用property裝飾器後就生成了一個setter裝飾器,用於寫資料
def foo(self, new_name):
self._name = new_name
@foo.deleter
def foo(self):
del self._name
s = Student("jack")
print(s.foo)
s.foo = "vision" #為foo屬性賦值
print(s.foo)
del s.foo #刪除foo屬性
print(s.foo) #報錯
#結論:上面通過s.foo()來呼叫,現在通過@property裝飾器後可以直接s.foo來使用,可讀、可寫
#還可以用另一種方式使用property
class Student(object):
def __init__(self, name):
self._name = name
@property #property裝飾器,將方法的呼叫方式偽裝成資料屬性的呼叫方式,用於讀資料
def get_foo(self):
return self._name
@foo.setter #上面使用property裝飾器後就生成了一個setter裝飾器,用於寫資料
def set_foo(self, new_name):
self._name = new_name
@foo.deleter
def del_foo(self):
del self._name
foo = property(get_foo, set_foo, del_foo,"i am foo's property")
s = Student("jack")
#s.foo呼叫getter, s.foo = value呼叫setter, def s.foo呼叫deleter
7.公有私有說明
公有和私有是相對與類物件和例項物件的內部和外部,在資料屬性或類內建的函式前面加上就表示此物件是私有,不能從外部使用只能自己內部訪問。但是,其實在python中根本不存在完全私有的物件,不管是資料屬性還是方法屬性在前面加上都可以從外部訪問,用_class__method可以使用方法,_class__shuju可以訪問資料,但是強烈建議你不要這麼做。私有變數除了封裝資料的作用還有一個重要的使用方式就是在繼承上的應用,可以防止子類覆蓋父類的相同方法。例如:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # 類Mapping的私有方法__update(),子類若再定義update()將不會覆蓋父類的update()方法
class MappingSubclass(Mapping):
def update(self, keys, values):
# 重寫父類的update()方法,但是卻不會修改父類的update()方法,因為父類的update()方法儲存在了__update()方法
for item in zip(keys, values):
self.items_list.append(item)
還有一種情況是在資料屬性、函式前面加一個_表示實現細節,將來有變化不會另行通知。