面向過程編程最易被初學者接受,其每每用一長段代碼來實現指定功能,開發過程當中最多見的操做就是粘貼複製,即:將以前實現的代碼塊複製到現需功能處。python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
while
True
:
if
cpu利用率 >
90
%
:
#發送郵件提醒
鏈接郵箱服務器
發送郵件
關閉鏈接
if
硬盤使用空間 >
90
%
:
#發送郵件提醒
鏈接郵箱服務器
發送郵件
關閉鏈接
if
內存佔用 >
80
%
:
#發送郵件提醒
鏈接郵箱服務器
發送郵件
關閉鏈接
|
隨着時間的推移,開始使用了函數式編程,加強代碼的重用性和可讀性,就變成了這樣:編程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def
發送郵件(內容)
#發送郵件提醒
鏈接郵箱服務器
發送郵件
關閉鏈接
while
True
:
if
cpu利用率 >
90
%
:
發送郵件(
'CPU報警'
)
if
硬盤使用空間 >
90
%
:
發送郵件(
'硬盤報警'
)
if
內存佔用 >
80
%
:
發送郵件(
'內存報警'
)
|
今天咱們來學習一種新的編程方式:面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)
注:Java和C#來講只支持面向對象編程,而python比較靈活即支持面向對象編程也支持函數式編程服務器
面向對象編程是一種編程方式,此編程方式的落地須要使用 「類」 和 「對象」 來實現,因此,面向對象編程其實就是對 「類」 和 「對象」 的使用。python2.7
類就是一個模板,模板裏能夠包含多個函數,函數裏實現一些功能ide
對象則是根據模板建立的實例,經過實例對象能夠執行類中的函數函數式編程
ps:類中的函數第一個參數必須是self(詳細見:類的三大特性之封裝)
類中定義的函數叫作 「方法」函數
#!usr/bin/env python # -*- coding:utf-8 -*- #建立類 class Foo: def Bar(self): print('Bar') def Hello(self,name): print('hello %s' %name) #根據類Foo建立對象 foo = Foo() foo.Bar() #執行Bar方法 foo.Hello('SUNXIAO') #執行Hello方法
內部實際傳參數的邏輯: foo.Hello('SUNXIAO') --> Hello(self = foo,name = 'SUNXIAO')學習
誒,你在這裏是否是有疑問了?使用函數式編程和麪向對象編程方式來執行一個「方法」時函數要比面向對象簡便this
觀察上述對比答案則是確定的,而後並不是絕對,場景的不一樣適合其的編程方式也不一樣。spa
總結:
1)函數式的應用場景 --> 各個函數之間是獨立且無共用的數據
2)面向對象的使用場景:
A、同一類型的方法具備相同的參數時,直接封裝到對象中便可
B、把類當作模板,建立多個對象,對象內封裝的數據能夠不同
self詳解
#!usr/bin/env python # -*- coding:utf-8 -*- #建立類 class Foo: def Bar(self): print('Bar',self) #self打印:<__main__.Foo object at 0x000001EC4601ADA0> def Hello(self,name): print('hello %s' %name) #根據類Foo建立對象 foo1 = Foo() print(foo1) foo1.Bar() #執行Bar方法,打印出:Bar <__main__.Foo object at 0x000001EC4601ADA0> #由上面的代碼結果能夠看出,在執行Bar方法時,python自動將對象foo1傳遞了Bar方法做爲第一個參數
面向對象的三大特性是指:封裝、繼承和多態。
1、封裝
封裝,顧名思義就是將內容封裝到某個地方,之後再去調用被封裝在某處的內容。
因此,在使用面向對象的封裝特性時,須要:
第一步:將內容封裝到某處
__init__(self)爲構造方法,當建立對象時,首先執行的就是構造方法
__del__() 析構方法,當解釋器銷燬對象時自動調用該方法
self 是一個形式參數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等於 obj1
當執行 obj2 = Foo('alex', 78 ) 時,self 等於 obj2
因此,內容其實被封裝到了對象 obj1 和 obj2 中,每一個對象中都有 name 和 age 屬性,在內存裏相似於下圖來保存。
第二步:從某處調用被封裝的內容
調用被封裝的內容時,有兩種狀況:
一、經過對象直接調用被封裝的內容
上圖展現了對象 obj1 和 obj2 在內存中保存的方式,根據保存格式能夠如此調用被封裝的內容:對象.屬性名
class Foo: def __init__(self,name,age): self.name = name self.age = age obj1 = Foo('sunxiao',28) print(obj1.name) #sunxiao print(obj1.age) #28 obj2 = Foo('sunyu','23') print(obj2.name) #sunyu print(obj2.age) #23
二、經過self間接調用被封裝的內容
執行類中的方法時,須要經過self間接調用被封裝的內容
class Foo: def __init__(self,name,age): self.name = name self.age = age def detail(self): print(self.name) print(self.age) obj1 = Foo('sunxiao',28) obj1.detail() # Python默認會將obj1傳給self參數,即:obj1.detail(obj1),因此,此時方法內部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 obj2 = Foo('sunyu','23') obj2.detail() # Python默認會將obj2傳給self參數,即:obj1.detail(obj2),因此,此時方法內部的 self = obj2,即:self.name 是 alex ; self.age 是 78
綜上所述,對於面向對象的封裝來講,其實就是使用構造方法將內容封裝到 對象 中,而後經過對象直接或者self間接獲取被封裝的內容。
練習一:在終端輸出以下信息
- 小明,10歲,男,上山去砍柴
- 小明,10歲,男,開車去東北
- 小明,10歲,男,最愛大保健
- 老李,90歲,男,上山去砍柴
- 老李,90歲,男,開車去東北
- 老李,90歲,男,最愛大保健
- 老張...
函數式編程def kanchai(name,age,gender): print('%s,%s歲,%s,上山去砍柴' %(name,age,gender)) def qudongbei(name,age,gender): print('%s,%s歲,%s,去東北' %(name,age,gender)) def dabaojian(name,age,gender): print('%s,%s歲,%s,大保健' %(name,age,gender)) kanchai('小敏',20,'男') qudongbei('小明',18,'男') dabaojian('老張',65,'男')
面向對象編程class Person: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def kanchai(self): print('%s,%s歲,%s,上山去砍柴' %(self.name,self.age,self.gender)) def qudongbei(self): print('%s,%s歲,%s,開車去東北' %(self.name,self.age,self.gender)) def dabaojian(self): print('%s,%s歲,%s,最愛大保健' %(self.name,self.age,self.gender)) p1 = Person('xiaoming','18','男') p1.kanchai() p1.qudongbei() p2 = Person('老張',65,'男') p2.dabaojian()上述對比能夠看出,若是使用函數式編程,須要在每次執行函數時傳入相同的參數,若是參數多的話,又須要粘貼複製了... ;而對於面向對象只須要在建立對象時,將全部須要的參數封裝到當前對象中,以後再次使用時,經過self間接去當前對象中取值便可。
練習二:遊戲人生程序
一、建立三個遊戲人物,分別是:
- 梅超風,女,18,初始戰鬥力1000
- 張三丰,男,20,初始戰鬥力1800
- 黃蓉,女,19,初始戰鬥力2500
二、遊戲場景,分別:
- 草叢戰鬥,消耗200戰鬥力
- 自我修煉,增加100戰鬥力
- 多人遊戲,消耗500戰鬥力
遊戲人生#!usr/bin/env python #-*- coding:utf-8 -*- class Person: def __init__(self,name,gender,age,fight): self.name = name self.gender = gender self.age = age self.fight = fight def grassland(self): """註釋:草叢戰鬥,消耗200戰鬥力""" self.fight -= 200 def practice(self): """註釋:自我修煉,增加100戰鬥力""" self.fight += 100 def commandfight(self): """多人遊戲:消耗500戰鬥力""" self.fight -= 500 def detail(self): temp = '姓名%s;性別%s;年齡%s;戰鬥力%s' %(self.name,self.gender,self.age,self.fight) print(temp) p1 = Person('sun','男',100,10000) p1.detail() p1.commandfight() p1.detail()
2、繼承
繼承,面向對象中的繼承和現實生活中的繼承相同,即:子能夠繼承父的內容。
例如:
貓能夠:喵喵叫、吃、喝、拉、撒
狗能夠:汪汪叫、吃、喝、拉、撒
若是咱們要分別爲貓和狗建立一個類,那麼就須要爲 貓 和 狗 實現他們全部的功能,以下所示:
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具備的功能,而咱們卻分別的貓和狗的類中編寫了兩次。若是使用 繼承 的思想,以下實現:
動物:吃、喝、拉、撒
貓:喵喵叫(貓繼承動物的功能)
狗:汪汪叫(狗繼承動物的功能)
#!usr/bin/env python # -*- coding:utf-8 -*- class Animal: def eat(self): print('%s吃' %self.name) def drink(self): print('%s喝' %self.name) def shit(self): print('%s拉' %self.name) def pee(self): print('%撒' %self.name) class Cat(Animal): def __init__(self,name): self.name = name def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self,name): self.name = name def cry(self): print('汪汪叫') dog1 = Dog('老李家的小狗') dog1.eat() dog1.cry() cat1 = Cat('小明家的小貓') cat1.drink() cat1.cry()
因此,對於面向對象的繼承來講,其實就是將多個類共有的方法提取到父類中,子類僅需繼承父類而沒必要一一實現每一個方法。
注:除了子類和父類的稱謂,你可能看到過 派生類 和 基類 ,他們與子類和父類只是叫法不一樣而已。
若是子類和父類都存在相同名稱的方法,子類對象優先調用子類中的方法,父類對象優先調用父類的方法
那麼問題又來了,多繼承呢?
一、Python的類能夠繼承多個類,Java和C#中則只能繼承一個類
二、Python2.7中的類若是繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先和廣度優先
Python中經典類和新式類的區別:
區別主要體如今繼承上:
Python的類能夠繼承多個類,Java和C#中則只能繼承一個類
Python的類若是繼承了多個類,那麼其尋找方法的方式有兩種
當類是經典類時,多繼承狀況下,會按照深度優先方式查找
當類是新式類時,多繼承狀況下,會按照廣度優先方式查找
簡單點說就是:經典類是縱向查找,新式類是橫向查找
以下例:
#!usr/bin/env python # -*- coding:utf-8 -*- class Life(): def daily(self): print "繁衍" class Animal(Life): def daily(self): print "吃" print "喝" print "拉" print "撒" class Puru(Life): def __init__(self): pass class cat(Puru,Animal): def __init__(self): print "喵喵" a = cat() a.daily() # 執行結果是: # 喵喵 # 繁衍
#!usr/bin/env python # -*- coding:utf-8 -*- class Life(object): def daily(self): print "繁衍" class Animal(Life): def daily(self): print "吃" print "喝" print "拉" print "撒" class Puru(Life): def __init__(self): pass class cat(Puru,Animal): def __init__(self): print "喵喵" a = cat() a.daily() # 執行結果是: # 喵喵 # 吃 # 喝 # 拉 # 撒
經典類和新式類,從字面上能夠看出一個老一個新,新的必然包含了不少的功能,也是以後推薦的寫法,從寫法上區分的話,若是 當前類或者父類繼承了object類,那麼該類即是新式類,不然即是經典類。
三、python3中再也不區分經典類和新式類,只有一種查找方式
#!usr/bin/env python # -*- coding:utf-8 -*- class A: def f1(self): print("A") def Bar(self): self.f1() class B(A): def f1(self): print("B") class C: def f1(self): print("C") class D(B,C): pass d = D() d.Bar() #B,在執行self.f1()時,由於是D建立的對象,因此也是按照D B A C的順序查找的
子類執行父類構造方法的兩種方式:
#!usr/bin/env python # -*- coding:utf-8 -*- class Animal: def __init__(self): print('A構造方法') self.ty = "動物" def aaa(self): print("aaa") class Cat(Animal): def __init__(self): print('B構造方法') self.n = '貓' #執行父類的構造方法,這樣系統會按照上圖的規則去父類找init構造方法,因此建議此種方法 super(Cat,self).__init__() #或者用下面方法執行,此種方法隨意性比較強,容易混亂 #Animal.__init__(self) b = Cat() # B構造方法 # A構造方法
補充:
子類調用父類的構造方法__init__()的兩種方法:
#!/ufr/bin/env python # -*- coding:utf-8 -*- class Animal: def __init__(self): print('A構造方法') self.ty = '動物' class Cat(Animal): def __init__(self): print('B構造方法') self.n = "貓" super(Cat, self).__init__() #第一種調用父類的構造方法,推薦這種使用方法,按照python的規則(順序)去調用 #Animal.__init__(self) #第二種調用父類的構造方法 c = Cat() print(c.__dict__) #結果: # B構造方法 # A構造方法 #{'n': '貓', 'ty': '動物'}
利用反射導入模塊、查找類、建立對象、查找字段
3、多態
Pyhon不支持Java和C#這一類強類型語言中多態的寫法,由於python的變量是不區分類型的,因此python原生支持多態,以下例:
#!usr/bin/env python # -*- coding:utf-8 -*- class C1: def f1(self): print('C1') class C2: def f1(self): print('C2') def f2(arg): arg.f1() c1 = C1() c2 = C2() f2(c1) #C1 f2(c2) #C2
練習實例:
#!usr/bin/env python # -*- coding:utf-8 -*- class Weapon: def __init__(self,name,limit_level,attack,duration): self.name = name #武器名字 self.limit_level = limit_level #等級限制 self.attack = attack #攻擊 self.duration = duration #持久 耐久 class Role: #角色 def __init__(self,name,blood,base_attack = 100): self.name = name #角色名字 self.blood = blood #血 self.base_attack = base_attack #基礎攻擊 class Magic(Role): #魔法師 def magic_attack(self,target,weapon): """魔法攻擊,target爲攻擊目標,需傳入Role的子類對象,weapon,需傳入Weapon對象""" target.blood -= (weapon.attack + self.base_attack) #目標血量下降=攻擊方的基礎攻擊+攻擊方的武器攻擊 magic1 = Magic('笑熬漿糊',3000) scarecrow1 = Role('稻草人',500,50) mofazhang = Weapon('魔法杖',10,50,100) magic1.magic_attack(scarecrow1,mofazhang) #傳入被攻擊對象和使用武器 print(scarecrow1.blood) magic1.magic_attack(scarecrow1,mofazhang) print(scarecrow1.blood)
擴展:
重載:類中的方法,方法名相同,方法的個數不一樣,python是不支持重載的,C# JAVA語言中支持重載
重寫:子類集成父類能夠重寫方法,子類對象調用方法的時候優先調用的是子類的方法
4、靜態字段、靜態方法、類方法、特性(屬性)
一、靜態字段存在的意義:當類中具備相同字段和值的時候,若是在對象中設置須要每一個對象都須要賦值,例如:
靜態字段儘可能用類去調用,類的方法用對象去調用
#!/ufr/bin/env python # -*- coding:utf-8 -*- class Province: country = "China" #建立靜態字段,靜態字段存儲在類中 def __init__(self,name): self.name = name #建立普通字段,普通字段存儲在對象中 def print_name(self): print('%s' %(self.name)) shandong = Province('山東省') hebei = Province("河北省") shandong.print_name() print(hebei.country) #調用靜態字段的第1中方法 print(Province.country) #調用靜態字段的第2種方法,推薦使用這種,原則是誰的成員誰調用 Province.print_name(shandong) #調用類的方法的第1種方法 shandong.print_name() #調用類的方法的第2種方法,推薦使用這種,原則類的方法由對象調用
靜態字段、靜態方法、類方法實例:
#!usr/bin/env python # -*- coding:utf-8 -*- class Province: country = "China" #建立靜態字段,靜態字段存儲在類中 def __init__(self,name): self.name = name #建立普通字段,普通字段存儲在對象中 def print_name(self): print('%s' %(self.name)) @staticmethod #靜態方法,靜態方法至關於普通函數 def static_method(args): #注意靜態方法沒有self print(args) @classmethod #類方法 def class_method(cls): #類方法必須有cls參數,代指類 cls.static_method(cls.country) #能夠直接調用類的靜態方法,把靜態字段傳入 shandong = Province('山東省') hebei = Province("河北省") #調用類的普通方法的兩種方式 shandong.print_name() Province.print_name(hebei) #能夠用這種方式調用方法 #調用類的靜態方法的兩種方式 Province.static_method('類調用靜態方法') shandong.static_method('shandong對象調用靜態方法') #調用靜態字段的兩種方法 print(Province.country) print(shandong.country) #類的方法有兩種調用方式 Province.class_method() shandong.class_method() #總結:雖然有兩種方式調用,但潛規則是: #一、對象調用:類中的方法、普通字段 #二、由類調用:靜態字段、靜態方法、類方法
二、特性(屬性)
#!usr/bin/env python # -*- coding:utf-8 -*- class Province: def __init__(self,name): self.name = name @property #特性(屬性),只能傳遞self參數;調用時不用加括號,即以字段的形式調用 def county(self): if self.name == "山東省": return ["五蓮縣","莒縣","東港區"] else: return["未知"] shandong = Province("山東省") re = shandong.county #特性(屬性)的調用方法 print(re)
#!usr/bin/env python # -*- coding:utf-8 -*- class Province: def __init__(self,name): self.name = name if self.name == "山東省": self.li = ["五蓮縣","莒縣","東港區"] else: self.li = ["未知"] @property #特性(屬性),只能傳遞self參數;調用時不用加括號,即以字段的形式調用 def county(self): return self.li @county.setter #當設置特性(屬性)時調用此函數 def county(self,value): self.li = value shandong = Province("山東省") re1 = shandong.county #特性(屬性)的調用方法 print(re1) re2 = shandong.county = ["日照市","濟南市"] #['日照市', '濟南市'] print(re2)
成員總結,類的成員有:
對象中: 通常字段、通常方法
類中:靜態字段、靜態方法、類方法、特性(屬性)
其實就是三大類:字段、方法、特性
那麼用類仍是對象調用呢:只記住一句話,傳self的用對象調用,其餘的用類調用
三、面向對象之成員修飾符
成員的前面加兩個下劃線(__)表示私有的,只能在類的內部調用,外部不能調用,只能在成員所屬類的內部能訪問,其子類和對象都是不能訪問的,字段和方法都是適用的
1)靜態字段的私有修飾符
#!usr/bin/env python # -*- coding:utf-8 -*- class Province: __country = 'China' def __init__(self): pass def print_country(self): return Province.__country p = Province() #print(Province.__country) #此句報錯由於__country是私有靜態字段 print(p.print_country())
成員修飾符:全部成員加兩個下劃線(__)都可變爲私有的,變爲私有的後只能在本類中被訪問,其繼承類中也不能訪問。
2)若是想調用私有字段或方法怎麼辦呢?能夠在字符或方法名前面加(_類名.)的方法進行調用,但通常不建議這麼作,例如:
#!usr/bin/env python # -*- coding:utf-8 -*- class Province: __country = 'China' def __init__(self,name): self.__name = name def print_country(self): return Province.__country p = Province("山東") print(p._Province__name) #山東
四、類的特殊成員
1)__init__()
2)__del__()
3)__call__(self),類中的call的特殊方法,能夠用對象加括號執行。(對象())
#!usr/bin/env python # -*- coding:utf-8 -*- class Foo: def __init__(self): print('__init__') def __call__(self,*args,**kwargs): print('__call__') foo = Foo() foo() #輸出:'__call__'。對象加括號,實際是執行的__call__()函數。兩句合起來:Foo()()
4)getitem setitm delitem
foo["key"] 調用 getitem ; foo[1:3] 調用getitem ; python2.x中是調用的getslice
foo["key"] = value 調用setitem ; foot[1:3] = [11,22,33]調用的是setitem;python2.x中調用的是setslice
del foo["key"] 調用delitem; del foor[1:3] 調用的是delitem ; python2.x中調用的是delslice
例如:
#!usr/bin/env python # -*- coding:utf-8 -*- class Foo: def __init__(self): pass def __getitem__(self,item): #對應的是:foo["key"] print(item) def __setitem__(self, key, value): #對應的是:foo["key"] = "value" print("%s:%s" %(key,value)) def __delitem__(self, key): #對應的是:del foo["key"] print('delte this' + key) foo = Foo() foo['key'] foo["key"] = "value" del foo["key"]
#!usr/bin/env python # -*- coding:utf-8 -*- class Foo: def __init__(self): pass def __getitem__(self,item): #對應的是:foo[1:20:2] print(item.indices(100)) #最大值爲100,將slice對象轉爲元組(1, 20, 2) def __setitem__(self, key, value): #對應的是:foo[1:3] = [11,22] emp = key.indices(100) print(emp,value) def __delitem__(self, key): #對應的是:del foo[1:3] print(key.indices(100)) foo = Foo() foo[1:20:2] #打印:(1, 20, 2) foo[1:3] = [11,22] #打印輸出:(1, 3, 1) [11, 22] del foo[1:3] #打印輸出:(1, 3, 1) #slice.indices()
5)類的特殊成員之__dict__ ,查看對象或類中的成員
#!usr/bin/env python # -*- coding:utf-8 -*- class Foo: def __init__(self): self.name = 'sunshuhai' def hello(self): pass foo = Foo() print(foo.__dict__) #查看對象中的成員 print(Foo.__dict__) #查看類中的成員
6)特殊成員之__iter__(self)方法
當for循環類的對象時,實際執行的就是類中的__iter__(self)方法
#!usr/bin/env python # -*- coding:utf-8 -*- class Foo: def __iter__(self): yield 1 yield 2 yield 3 foo = Foo() for i in foo: #當循環對象時,實際執行的是特殊方法__iter__(self) print(i)
7)類的特殊成員之__str__(self)
在用print打印對象,或將對象轉爲字符串時,會自動執行__str__(self)方法
#!usr/bin/env python # -*- coding:utf-8 -*- class Foo: def __init__(self,ef): self.ef = ef def __str__(self): return self.ef exception = Foo('出錯了。。。。') print(exception) #出錯了。。。。,實際調用的是__str__(self)方法
8)瞭解:類的特殊成員之new和metaclass
類是由對象Type建立的