程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python復習筆記2——面向對象編程

編輯:Python

面向對象編程——Object Oriented Programming,簡稱OOP,是一種程序設計思想。OOP把對象作為程序的基本單元,一個對象包含了數據和操作數據的函數。

面向過程的程序設計把計算機程序視為一系列的命令集合,即一組函數的順序執行。為了簡化程序設計,面向過程把函數繼續切分為子函數,即把大塊函數通過切割成小塊函數來降低系統的復雜度。

而面向對象的程序設計把計算機程序視為一組對象的集合,而每個對象都可以接收其他對象發過來的消息,並處理這些消息,計算機程序的執行就是一系列消息在各個對象之間傳遞。

一、類Class和實例Instance

面向對象最重要的概念就是類和實例。

  • 類是抽象的模板
  • 實例是根據類創建出來的一個個具體的“對象”

在Python中,定義類是通過class關鍵字:

class Student(object):
pass

class後面緊接著是類名,即Student類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的,繼承的概念我們後面再講,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。

創建實例是通過類名+()實現

huffie = Student()

變量bart指向的就是一個Student的實例

二、面向對象的三大特性:封裝、繼承、多態

2.1 封裝

(1)__init__方法

通過定義一個特殊的__init__方法,在創建實例的時候,就把namescore等屬性綁上去:

class Student(object):
def __init__(self, name, score):
self.name = name
self.age = age

__init__方法的第一個參數永遠是self,表示創建的實例本身,因此,在__init__方法內部,就可以把各種屬性綁定到self,因為self就指向創建的實例本身。

有了__init__方法,在創建實例的時候,就不能傳入空的參數了,必須傳入與__init__方法匹配的參數,但self不需要傳,Python解釋器自己會把實例變量傳進去:

huffie = Student('huffie', 22)

(2)類的方法

Student實例本身就擁有這些數據,要訪問這些數據,就沒有必要從外面的函數去訪問,可以直接在類的內部定義訪問數據的函數,這樣,就把“數據”給封裝起來了。這些封裝數據的函數是和Student類本身是關聯起來的,我們稱之為類的方法。

要定義一個方法,除了第一個參數是self外,其他和普通函數一樣。

class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))

要調用一個方法,只需要在實例變量上直接調用,除了self不用傳遞,其他參數正常傳入:

bart.print_score()

(3)訪問限制

類的內部可以有屬性和方法,外部代碼通過實例變量的方法來操作數據,這樣就隱藏了內部的復雜邏輯。

但外部代碼還是可以自由的修改實例的屬性。

要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__。在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問

class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))

這樣就確保了外部代碼不能隨意修改對象的屬性,使代碼更穩定可靠。

如果外部代碼要獲取name和score,或者要修改score,可以給Student類增加get_nameget_score這樣的方法:

class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score

(4)類屬性和實例屬性

給實例綁定屬性的方法是通過實例變量,或者通過self變量:

class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90

給類綁定屬性可以直接在class中定義屬性。

class Student(object):
name = 'Student'

類屬性的特性參考以下測試:

>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 創建實例s
>>> print(s.name) # 打印name屬性,因為實例並沒有name屬性,所以會繼續查找class的name屬性
Student
>>> print(Student.name) # 打印類的name屬性
Student
>>> s.name = 'Michael' # 給實例綁定name屬性
>>> print(s.name) # 由於實例屬性優先級比類屬性高,因此,它會屏蔽掉類的name屬性
Michael
>>> print(Student.name) # 但是類屬性並未消失,用Student.name仍然可以訪問
Student
>>> del s.name # 如果刪除實例的name屬性
>>> print(s.name) # 再次調用s.name,由於實例的name屬性沒有找到,類的name屬性就顯示出來了
Student

從上面的例子可以看出,在編寫程序的時候,千萬不要對實例屬性和類屬性使用相同的名字,因為相同名稱的實例屬性將屏蔽掉類屬性,但是當你刪除實例屬性後,再使用相同的名稱,訪問到的將是類屬性。

2.2 繼承

定義一個class的時候,可以從某個現有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。

(1)繼承的優點1:子類擁有父類的屬性和方法

繼承最大的好處是子類獲得了父類的全部功能。即子類自動擁有父類的所有共有屬性和方法。

舉例如下,對於Dog來說,Animal就是它的父類,對於Animal來說,Dog就是它的子類。CatDog類似。

class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass

(2)繼承的優點2:子類可以修改父類的方法

當子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運行的時候,總是會調用子類的run()。這樣,我們就獲得了繼承的另一個好處:多態。

class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')

2.3 多態

首先說明,當我們定義一個class的時候,我們實際上就定義了一種數據類型。我們定義的數據類型和Python自帶的數據類型,比如str、list、dict沒什麼兩樣。

在繼承關系中,如果一個實例的數據類型是某個子類,那它的數據類型也可以被看做是父類。

(1)多態的優點

當新增子類時,任何依賴父類作為參數的函數或方法都可以不加修改的正常運行。即

  • 對擴展開放:允許新增父類的子類;
  • 對修改封閉:不需要修改依賴父類類型的函數。(例如def func(父類)

(2)動態語言

定義一個Animal類:

class Animal(object):
def run(self):
print('Animal is running...')

參考如下依賴Animal類的函數,其中調用了Animal類的方法run

def run_twice(animal):
animal.run()
animal.run()

對於Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:

class Timer(object):
def run(self):
print('Start...')

即使Timer類和Animal類毫無關系,但只要他們都有run方法就可以使用依賴Animal的函數。

這就是動態語言的“鴨子類型”,它並不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。

animal = Animal()
timer = Timer()
run_twice(animal)
run_twice(timer)

三、面向對象高級編程

3.1 @property

前面介紹了setget方法用來保證代碼的穩定性,但這種調用方式過於復雜。因此…對於追求完美的Python程序員來說,參數的使用必須要簡單!

Python內置的@property裝飾器就是負責把一個方法變成屬性調用的:

class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

把一個getter方法變成屬性,只需要加上@property就可以了。
把一個setter方法變成屬性,只需要加上@func_name.setter就可以了
只定義getter方法,不定義setter方法就是一個只讀屬性

>>> s = Student()
>>> s.score = 60 # OK,實際轉化為s.set_score(60)
>>> s.score # OK,實際轉化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!

3.2 定制類

(1)slot

定義類:

class Student(object):
pass

為實例綁定屬性:

>>> s = Student()
>>> s.name = 'Michael' # 動態給實例綁定一個屬性

為實例綁定方法:

>>> def set_age(self, age): # 定義一個函數作為實例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 給實例綁定一個方法

為類綁定方法,所有實例均可調用:

>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score

上面可以看到,在定義了實例後可以任意添加實例的屬性,但如果我們想要限制實例的屬性怎麼辦。

Python允許在定義class的時候,定義一個特殊的__slots__變量,來限制該class實例能添加的屬性

class Student(object):
__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱

測試:

>>> s = Student() # 創建新的實例
>>> s.name = 'Michael' # 綁定屬性'name'
>>> s.age = 25 # 綁定屬性'age'
>>> s.score = 99 # 綁定屬性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

使用__slots__要注意,__slots__定義的屬性僅對當前類實例起作用,對繼承的子類是不起作用的

(2)str

我們先定義一個Student類,打印一個實例:

>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> s = Student('Michael')
>>> print(s)
<__main__.Student object at 0x109afb190>
>>> s
<__main__.Student object at 0x109afb190>

打印出一堆<__main__.Student object at 0x109afb190>,不好看。

怎麼才能打印得好看呢?只需要定義好__str__()方法和__repr__方法,返回一個好看的字符串就可以了。

>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s) from __str__' % self.name
... def __repr__(self):
... return 'Student object (name: %s) from __repr__' % self.name
...
>>> s = Student('Michael')
>>> print(s)
Student object (name: Michael) from __str__
>>> s
Student object (name: Michael) from __repr__

這樣打印出來的實例,不但好看,而且容易看出實例內部重要的數據。

簡便寫法:__repr__ = __str__

(3)getattr

正常情況下,當我們調用類的方法或屬性時,如果不存在,就會報錯。

要避免這個錯誤,除了可以加上一個score屬性外,Python還有另一個機制,那就是寫一個__getattr__()方法,動態返回一個屬性。修改如下:

class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

當調用不存在的屬性或方法時,就會有返回:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
>>> s.age()
25

(4)call

任何類,只需要定義一個__call__()方法,就可以直接對實例進行調用。

class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)

調用方式如下:

>>> s = Student('Michael')
>>> s() # self參數不要傳入
My name is Michael.

__call__()還可以定義參數。對實例進行直接調用就好比對一個函數進行調用一樣,所以你完全可以把對象看成函數,把函數看成對象,因為這兩者之間本來就沒啥根本的區別。

3.3 枚舉類

當我們需要定義常量時,一個辦法是用大寫變量通過整數來定義,好處是簡單,缺點是類型是int,並且仍然是變量。

更好的方法是為這樣的枚舉類型定義一個class類型,然後,每個常量都是class的一個唯一實例。Python提供了Enum類來實現這個功能:

from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

這樣我們就獲得了Month類型的枚舉類,可以直接使用Month.Jan來引用一個常量。

for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)

value屬性則是自動賦給成員的int常量,默認從1開始計數。

如果需要更精確地控制枚舉類型,可以從Enum派生出自定義類:

from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被設定為0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved