面向對象編程是一種程序設計思想。
咱們到目前爲止所編寫的程序,都是基於面向過程
的。python
面向過程就是咱們在寫程序過程當中,將需求拆成多個流程
,每一個封裝一個方法
,逐個調用。
好比買菜、洗菜、炒菜、裝盤這四個步驟,咱們對於的方法就是buy
、wash
、cook
、dish
。
若是需求有變化,如我還要買米、作飯,用鐵鍋炒菜,用微波爐作菜等等。這時候用面向過程就很是麻煩且會愈來愈亂。git
面向對象則是從另外一個角度來思考,它用對象
來取代方法做爲程序的基礎
。
對象是對事物的抽象
,如人能夠抽象成名字、年齡以及能作的事情,這就是類
。
咱們在程序設計時,就只要建立這個對象,讓它來作它能作的事情
。
仍是買菜、洗菜、炒菜、裝盤這個四個步驟,如今咱們就能夠有超市
、廚師
兩個對象。超市負責買菜,廚師負責洗菜、炒菜、裝盤。
進一步廚師用什麼洗菜,用什麼作菜怎麼作、怎麼裝盤又是一系列的對象來處理。
超市這個對象提供買米、買菜、買肉等等功能,必要時還能夠繼續添加
。
除了基本的功能外,沃爾瑪和永輝賣的東西就有差異,即便一樣是賣米也會有差別。
這就是繼承,沃爾瑪和永輝都是超市的繼承,但並非相同的。github
類【class
】就是對對象的描述
。
咱們要把人的名字和年齡以及作的事情用python的類寫出來。編程
建立一個類:class
。
咱們建立一個user包,在包裏建立一個Person模塊。在模塊中編寫Person類。框架
class Person: def __init__(self): pass
class後面就是類名Person,其下的代碼塊就是類的定義。類名使用駝峯寫法
。一個文件一個類
,大部分狀況咱們會這樣作。Python中並不反對一個文件多個類
。
第一個在類中要定義的方法就是構造方法
,方法名爲__init__
。這是類默認存在
的方法。
構造方法裏的第一個參數是self
,表明對象本身
。默認
類的方法第一個參數
都是self
。單元測試
實例化:將類建立出來進行使用。
咱們建立一個main.py來實例化這個類。測試
from user import Person p1 = Person.Person()
from user.Person import Person p1 = Person()
上面兩種方式是在包下的類實例化方式,與方法的調用時同樣的。
對於類,因爲一文件一類的原則,咱們更但願能簡單一點調用到包裏的類。以下:spa
from user import Person p1 = Person() p2 = Person()
那麼咱們就要在包的__init__.py
文件裏作文章了,寫入設計
from .Person import Person
而後咱們就能夠直接從包導入Person類了。3d
構造方法:用於初始化
,每一個類被實例化時會默認調用的方法。
def __init__(self, name, age): self.name = name # 名字 self.age = age # 年齡
咱們加入Person類有的數據,名字和年齡。self
表明本身,它點
出來的變量
稱爲成員變量
。self.name
和self.age
分別被賦值給構造方法傳入的參數name和age,用於初始化
。
實例化並獲取成員變量:
p1 = Person('Green', 12) print(p1.name, p1.age)
使用構造方法進行建立,而後直接用建立好的變量,點出成員變量便可。
成員變量原則上
只能在構造方法
中聲明
。
成員方法:構造方法實際上是一個特殊的成員方法。
def sleep(self, t): print('{} sleeps for {} seconds'.format(self.name, t))
定義了一個sleep方法,一樣的第一個參數
是self
,表明該方法是成員方法。
在成員方法內部,咱們就能夠用self
來得到成員變量
。調用
:一樣使用點
的方式調用該方法
。
p2 = Person('Lucy', 14) p2.sleep(10)
這時若是咱們須要在sleep前添加一步到牀上的步驟,咱們能夠這樣作,
添加一個go2bed成員方法,並在sleep方法中用self
調用這個成員方法
。
class Person: def __init__(self, name, age): self.name = name # 名字 self.age = age # 年齡 def sleep(self, t): self.go2bed() print('{} sleeps for {} seconds.'.format(self.name, t)) def go2bed(self): print(self.name + ' go to bed.')
訪問限制:對成員變量和成員方法進行限制,不讓使用者能夠直接使用。成員變量
做爲一個類的數據,不該該能夠直接被使用者修改,保證類被正確使用。
變量名用 兩個下劃線
【__
】開頭的就是被限制訪問
的私有變量
,只可內部訪問
。
def __init__(self, name, age): self.__name = name # 名字 self.__age = age # 年齡
調用時報錯
若是須要獲取這兩個變量的值,或進行修改,咱們定義get
,set
方法進行。
def get_age(self): return self.__age def set_age(self, age): self.__age = age def get_name(self): return self.__name
咱們但願age能夠進行設置,而name沒有set方法。這樣成員變量的任何修改都在咱們的控制範圍內。
成員方法也使用兩個下劃線
來作限制。__go2bed
就是隻能內部訪問
的方法了。
class Person: def __init__(self, name, age): self.__name = name # 名字 self.__age = age # 年齡 def sleep(self, t): self.__go2bed() print('{} sleeps for {} seconds.'.format(self.__name, t)) def __go2bed(self): print(self.__name + ' go to bed.') def get_name(self): return self.__name def get_age(self): return self.__age
主要目的是:
一、不但願使用者調用到。
二、對用戶提供的方法也能夠叫作接口
。接口就是使用者能夠調用到的全部方法了。這樣對於使用者來講也是最好的。
繼承是在已有類【父類
】的基礎上,再編寫一個子類
,其擁有父類的全部內容。
from user import Person class Student(Person): pass
在類名Student後面用括號抱起來的Person就是父類。
此時這個子類沒有任何的代碼,可是它已經繼承了父類全部的內容,使用方法同樣。
from user import Student student1 = Student('Green', 12) print(student1.get_name())
全部方法變量自動繼承。
object類:默認的Python繼承類。
沒有作繼承的類,其實都默認繼承
了python的object類
【對象類】。
class Person(object):
object能夠不寫。
添加成員變量和方法:子類通常擁有比父類更強大的功能。
在Students類中,咱們添加一個grade變量來標識年級,並添加一個新方法。
class Student(Person): def __init__(self, name, age, grade=1): Person.__init__(self, name, age) # 父類構造方法 self.__grade = grade # 年級 def get_grade(self): return self.__grade
代碼中的Person.__init__
表示了咱們在新的構造方法中先調用了Person父類
的構造方法
。
這樣咱們就減小了重複的代碼。
複寫成員方法:修改
父類的方法
內容。
方法的複寫必須保證方法名
和參數
與父類一致
。
def sleep(self, t): print('student ' + self.get_name()) Person.sleep(self, t) # 父類方法
經過調用父類的方法來擴充該方法。或者直接不調用父類方法所有重寫也是能夠的。
def sleep(self, t): print('student {} sleeps for {} seconds.'.format(self.__name, t))
子類
的方法覆蓋
了父類
的方法,在被調用時就會運行子類方法。
複寫實現了面向對象中多態
的概念。
此時咱們發現代碼報錯
了,緣由在於__name
變量是隻能Person類內部訪問
的成員變量。
只能使用self.get_name()
來得到名字,子類要如何調用父類的私有變量?
繼承中的訪問限制:
兩個下劃線使得該成員變量和方法只能在這個類中內部使用。
但在繼承中
,成員變量和方法
除了不能被外部調用外,其子類
應該是能夠正常使用
的。
使用一個下劃線
【_
】開頭定義的成員變量和方法就能夠實現。
def __init__(self, name, age): self._name = name # 名字 self._age = age # 年齡 def _go2bed(self): print(self._name + ' go to bed.')
這樣剛纔的複寫方法中就可使用了。
def sleep(self, t): print('student {} sleeps for {} seconds.'.format(self._name, t))
訪問限制分類:public
:公開權限,無下劃線【self.name
】,任何地方均可以訪問修改。protected
:保護權限,單下劃線【self._name
】,只容許本身和其子類訪問修改。private
:私有權限,雙下劃線【self.__name
】,只容許本身訪問修改。
根據咱們的須要,選擇對應的權限進行開發。
判斷變量類型:type
、isinstance
。
p1 = Person('Green', 12) s1 = Student('Green', 12) print(type(s1)) print(type(p1))
輸出:type
能夠得到
該變量的類型
。
print(isinstance(p1, Person)) print(isinstance(p1, Student))
輸出:isinstance
能夠直接判斷
變量是否是某個類型
。
對於父類
變量結果與下面的代碼相同:
print(type(p1) == Person) print(type(p1) == Student)
但對於子類
,狀況並不相同
。
子類的類型判斷:
type(s1) == Person # False type(s1) == Student # True isinstance(s1, Person) # True isinstance(s1, Student) # True
咱們發現子類變量在type的相等判斷中,並不等於父類。
但在isinstance
判斷中,子類被判斷爲與父類同樣,這就是多態
。
一個類能夠是本身,也能夠是其父類,邏輯上也是通的,學生也是一我的。
反之則不對,一我的未必是學生。
多態的用處:
def poly_test(person): if not isinstance(person, Person): raise Exception('數據類型傳入錯誤') return person.sleep(10)
這個方法裏,咱們對傳入的類型進行了限定。
這樣咱們就能夠直接傳入Student、Person或者其餘子類均可以。
sleep也是父類就有方法,直接調用不會有問題。
當咱們新增一個子類,如Programmer類,咱們就不須要對這個方法進行修改,直接使用。
根據對象的真實類型
來運行
,就是多態的用處。
除了成員變量和成員方法,類中還能夠定義靜態屬性
:類變量
和類方法
。靜態
的意思是不須要實例化
就可使用。
類變量的數據屬於全部實例共享
一個。
直接建立在類的定義中與方法同級。調用時使用類名 . 變量名
。
class Programmer(Person): programmers = {} # 靜態變量 def __init__(self, name, age): Person.__init__(self, name, age) if name not in Programmer.programmers: Programmer.programmers[name] = 0 Programmer.programmers[name] += 1
這裏作了一個統計,統計同一姓名的人有幾個。每建立一個實例就統計一次。
from user import Programmer p = Programmer('Green', 18) print(Programmer.programmers)
類方法是直接屬於類的方法。
定義時,與成員方法不一樣的是第一個參數不是self而是cls
【這個類】。並須要在方法上添加一個裝飾器
。
@classmethod def count_by_name(cls, name): return cls.programmers[name]
以@開頭
並寫在def上面一行的就是裝飾器
,具體內容之後的高階語法再介紹。@classmethod
就定義了下面
的方法是一個類方法
,且沒有self參數,由於沒有實例化。
第一參數是cls
,表明了當前類
,能夠經過cls調用類變量,至關於Programmer.programmers
。
使用時直接經過類名 . 方法
調用,無需實例化
。
count = Programmer.count_by_name('Green')
靜態方法與類方法相似,但更爲獨立
,僅僅表現爲放在類裏面託管的方法。
定義時,沒有self參數
,也沒有cls參數
。下面是上個方法的靜態方法版本。
@staticmethod def count_by_name(name): return Programmer.programmers[name]
@staticmethod
就定義了下面的方法是一個靜態方法
。其餘都與類方法同樣。
使用上,靜態方法通常與類沒有什麼關係,不會涉及類的操做,是一個比較獨立的方法。
繼承問題:類變量與子類共享,父類沒有該類變量。
類方法一樣能夠繼承和複寫,有複寫時要調用這個類的類方法。
靜態方法也同樣。
單元測試是一個測試框架
。
咱們直接來看一個簡單的示例代碼。
import unittest # 單元測試框架 from user import Student # 要測試的類 class TestStudentMethods(unittest.TestCase): def test_student(self): # 測試用例 s = Student('Green', 12) self.assertEqual(12, s.get_age()) self.assertEqual('Green', s.get_name()) if __name__ == '__main__': # 看成爲啓動文件時 unittest.main()
unittest
是要引入的包。建立一個類繼承unittest.TestCase
類。而後其餘方法就不須要了。
咱們直接開始寫測試用例
。以test開頭
的成員方法就是一個用例
。
定義完類後,咱們啓動該測試unittest.main()
。
前面的if語句判斷了該文件是否啓動文件
,也是一種經常使用的方法。
啓動後,單元測試會自動執行全部用例
,並給出結果。
測試用例:
def test_student(self): # 測試用例 s = Student('Green', 12) self.assertEqual(12, s.get_age()) self.assertEqual('Green', s.get_name())
先進行一些操做代碼,就是咱們的用例過程,這裏建立了一個Student對象。
而後就要使用單元測試類給咱們提供的assert系列方法
來判斷結果是否正確。assertEqual
是最經常使用的方法之一了,參數1寫入指望值
,參數2寫入要驗證的數據
。
在運行過程當中就會進行對比,若是二者相等則正確,不等則錯誤。
經常使用的assert方法還有:assertNotEqual
、assertTrue
、assertFalse
、assertIsNone
、assertIsNotNone
、assertIn
等。
一個單元測試能夠包含不少測試用例,一次進行測試。
測試結果:全部assert都正確的狀況下,即測試經過。
有沒有經過的用例時,會告訴你哪一個用例的哪一個assert有問題,與指望值不符。
一、模擬一個小遊戲,編寫一個Sprite類【擁有血量和攻擊力兩個屬性,並進行攻擊】。
而後繼承出Monster類和Hero類,讓二者每回合互相攻擊一次,直到有一方死亡。
每次攻擊扣除對方的血量是攻擊力加減N的隨機值。
二、編寫一個進行接口請求的單元測試。