十六、物件導向程式設計

袁勇i發表於2018-04-27
物件導向程式設計
類的概念 : 具有相同屬性和技能的一類事物
人類 抽象
物件 : 就是對一個類的具體的描述
具體的人 具體

使用物件導向的好處:
使得程式碼之間的角色關係更加明確
增強了程式碼的可擴充套件性
規範了物件的屬性和技能
物件導向的特點:結局的不確定性
 1 def Person(name,sex,hp,ad):
 2     # 人模子
 3     self = {`name`: name, `sex`:sex, `hp`: hp, `ad`: ad}
 4     def attack(dog): # 閉包
 5         # 人攻擊狗
 6         print(`%s攻擊%s` % (self[`name`], dog[`name`]))
 7         # 狗掉血,狗的血量-人的攻擊力
 8         dog[`hp`] -= self[`ad`]
 9     self[`attack`] = attack
10     return self
11 
12 def Dog(name,kind,hp,ad):
13     # 狗模子
14     self = {`name`: name, `kind`:kind, `hp`: hp, `ad`: ad}
15     def bite(person):
16         print(`%s咬了%s` % (self[`name`], person[`name`]))
17         # 人掉血,人的血量-狗的攻擊力
18         person[`hp`] -= self[`ad`]
19         if person[`hp`] <= 0: print(`game over,%s win` % self[`name`])
20     def bite2():pass
21     self[`bite`] = bite
22     self[`bite2`] = bite2
23     return self
24 
25 # 人 規範了屬性的個數 簡化了創造任務的程式碼
26 alex = Person(`a_sb`,`不詳`,1,5)
27 boss_jin =Person(`金老闆`,``,2,50)
28 
29 #
30 chen = Dog(`旺財`,`teddy`,50,20)
31 alex[`attack`](chen)
32 print(chen[`hp`])
在講語法規則之前我們先說下類的基礎的東西
靜態屬性
 class Person:         #類名
     role = `person`   # 靜態屬性
     def f1(self):     # 動態屬性 方法(函式)  預設帶一個引數self
         print(1234567)
檢視靜態變數的第一種方法:
print(Person.__dict__)   把類中的所有的變數以字典的方式列印出來
print(Person.__dict__[`role`])    把role對應的值列印出來
檢視靜態變數的第二種方法:
print(Person.role)
Person.role = 123  改變了靜態屬性的值 
Person.__dict__[`role`] = 456   報錯
del Persom.sole     刪除靜態屬性 
動態屬性和一些語法規則

引用動態變數
# 1.類名.方法名 檢視這個方法的記憶體地址
# 1.類名.方法名(實參) 呼叫了這個方法,必須傳一個實參,這個實參傳給了self
# 創造一個物件 - 例項化
# 產生一個例項(物件)的過程
# 物件 = 類名()
# alex = Person() # 創造一個物件
# alex 是物件、例項
# Person是類
# 物件 = 類名()

# 例項化的過程:
# 1.創造一個例項,將會作為一個實際引數 # python
# 2.自動觸發一個__init__的方法,並且把例項以引數的形式傳遞給__init__方法中的self形參
# 3.執行完__init__方法之後,會將self自動返回給alex
# __init__方法 :初始化方法,給一個物件新增一些基礎屬性的方法,一般情況下是針對self的賦值
# 物件
# 在類的內部 self是本類的一個物件
# 在類的外部,每一個物件都對應著一個名字,這個物件指向一個物件的記憶體空間
# 屬性的呼叫:
# 物件名.屬性名 第一種呼叫方法
# 物件名.__dict__[`屬性名`] 第二種呼叫方法
# 方法的呼叫 :
# 類名.方法名(物件名) # 那麼方法中的self引數就指向這個物件
# 物件名.方法名() # 這樣寫 相當於 方法中的self引數直接指向這個物件

class
Person: s = `aaaa` def __init__(self,name,sex,hp,ad): self.name = name self.sex = sex self.hp = hp self.ad = ad def atack(self): print(`%s攻擊%s` % (self.name,gou.name)) class Dog: def __init__(self,name,sex,hp,ad): self.name = name self.sex = sex self.hp = hp self.ad = ad def bite(self): print(`%s咬了%s` % (self.name,alex.name)) print(Person.s) alex = Person(`alex`,`male`,78,23) gou = Dog(`wangcai`,`female`,4,56) print(alex.__dict__) print(alex.name) alex.name = `sb` print(alex.name) alex.atack() gou.bite() Person.atack(alex) Dog.bite(gou)
#############

aaaa
{`name`: `alex`, `sex`: `male`, `hp`: 78, `ad`: 23}
alex
sb
sb攻擊wangcai
wangcai咬了sb
sb攻擊wangcai
wangcai咬了sb

 



 物件導向中的一些作用域問題

在建立一個類時,類裡面的靜態變數和方法 公用一個記憶體空間   
每個物件裡的 所有物件屬性公用一個記憶體空間  記憶體空間裡面包含一個類物件指標 當在自己的空間找不到物件屬性時 就通過類物件指標去類的記憶體空間去找
對類靜態屬性的更改 應該使用 類名.靜態屬性 = 新的值 
class Person:
    role = `person`   # 靜態屬性
    def __init__(self,name,sex,hp,ad):  # 方法  
        self.name = name     # 物件屬性 屬性
        self.sex = sex
        self.hp = hp
        self.ad = ad
        self.attack = `hahaha`
    def attack(self):
        print(`%s發起了一次攻擊`%self.name)

alex = Person(`a_sb`,`不詳`,1,5)
boss_jin = Person(`金老闆`,`女`,50,20)
print(alex.role)
print(boss_jin.role)
alex.role = `dog`             #這句程式碼是在自己的空間內建立了一個物件屬性 role = ‘dog’
print(alex.role)                #這句程式碼在找role這個物件屬性時 先找自己的空間
print(boss_jin.role)    #     結果為`person`  這個在自己的空間找不到所以去找了類的空間裡的role

 組合 

組合:一個類的物件作為另一個類的物件的屬性     表示一種什麼有什麼的關係  

         能夠組合必須是兩個物件有可以互相關聯的因素。

from math import pi
class Circle:
    def __init__(self,r):
        self.r = r
    def s(self):
        return pi*self.r**2
    def lenth(self):
        return 2*pi*self.r
class Ring:
    def __init__(self,r,R):
        self.r = Circle(r)              組合
        self.R = Circle(R)              組合
    def s(self):
        return abs(self.r.s() - self.R.s())
    def lenth(self):
        return self.r.lenth() + self.R.lenth()
a = Ring(3,5)
print(a.s(),a.lenth())

 

 繼承   

父類 :基類 超類
子類 :派生類

 

繼承的特點:什麼裡面是什麼的關係  繼承是當幾個類的物件都有共同的屬性的時候可以用繼承這樣可以不用輸入重複的程式碼。

      含有繼承關係的子類在找東西的時候先找自己記憶體空間 再找自己類的空間  再找父類空間

 

class Animal:
    role = `Animal`
    def __init__(self,name,hp,ad):
        self.name = name     # 物件屬性 屬性
        self.hp = hp         #血量
        self.ad = ad         #攻擊力

    def eat(self):
        print(`%s吃藥回血了`%self.name)

class Person(Animal):
    r = `Person`
    def attack(self,dog):   # 派生方法
        print("%s攻擊了%s"%(self.name,dog.name))

    def eat2(self):
        print(`執行了Person類的eat方法`)
        self.money = 100
        self.money -= 10
        self.hp += 10

class Dog(Animal):
    def bite(self,person):  # 派生方法
        print("%s咬了%s" % (self.name, person.name))
alex = Person(`alex`,10,5)
dog = Dog(`teddy`,100,20)
alex.eat2()
alex.eat()
dog.eat()
alex.eat = `aaa`
print(alex.eat())  # 報錯

###############
執行了Person類的eat方法
alex吃藥回血了
teddy吃藥回血了

派生

在繼承父類屬性的時候,假如幾個子類的90%的屬性都相似,而只有剩下的10%不一樣,這時候就需要用到派生屬性的方法

 

class Animal:
    def __init__(self,kind,sex):
        self.kind = kind
        self.sex = sex
    def func1(self):
        return `吃`
    def func2(self):
        print( `喝`)
class Dog(Animal):
    def __init__(self,kind,sex,kg):
        # Animal.__init__(self,kind,sex)
        super().__init__(kind,sex)    在單繼承中supper負責找到父類的,不需要傳self
        self.height  = kg             派生屬性
    def func3(self):
        print(`看門`)
    def func1(self):
        print(`抓老鼠`)
class Cat(Animal):
    def __init__(self,kind,sex,age):
        Animal.__init__(self,kind,sex)
        self.age = age
    def func4(self):
        print(`上樹`)
        super().func2()              找父類的中的方法
dog = Dog(`二哈`,`male`,`30kg`)
cat = Cat(`加菲貓`,`female`,3)
print(dog.__dict__)
print(cat.__dict__)
cat.func4()
########
{`kind`: `二哈`, `sex`: `male`, `height`: `30kg`}
{`kind`: `加菲貓`, `sex`: `female`, `age`: 3}
上樹
喝

 

磚石繼承問題

在python3x 中所有的類都是新式類(所有的類都預設含有object)所以內部的遍歷順序都是以廣度優先的順序來遍歷,當用super的時候請看下面的例子

  

class A:
    def func(self):
        print(`A`)
class B(A):
    pass
    def func(self):
        super().func()
        print(`B`)
class C(A):
    pass
    def func(self):
        super().func()
        print(`C`)
class D(B):
    pass
    def func(self):
        super().func()                          
        print(`D`)
class E(B,C):
    pass
    def func(self):
        super().func()
        print(`E`)
class F(D,E):
    pass
    def func(self):
        super().func()
        print(`F`)
f = F()
f.func()
print(mro(f)) 查詢他的上一個父類
這段程式碼super查詢父類中的方法查詢順序就如圖所示 遵循廣度優先的順序 並且每次找遍歷節點只遍歷一次

 

 

 

 

在python2x 中就不一樣了python2中的磚石繼承分為經典類和新式類

經典類(也就是不含有object):在經典類中在子類尋找父類的時候遵循深度優先 沒有mro方法 也沒有super方法

新式類(就是含有object):在新式類中在子類尋找父類的時候遵循廣度優先  有mro方法 也有super方法但是super方法要有傳參super(子類名稱,子類物件)

這個例子為經典類的遍歷方法
class
A: # def func(self): # print(`A`) pass class B(A): pass # def func(self): # # super().func() # print(`B`) class C(A): pass def func(self): # super().func() print(`C`) class D(B): pass # def func(self): # # super().func() # print(`D`) class E(B,C): pass # def func(self): # # super().func() # print(`E`) class F(D,E): pass # def func(self): # # # super().func() # print(`F`) f = F() f.func()

抽象類和介面類

說這個問題之前我們要先來聊聊程式設計屆的兩本黑皮書

《設計模式》 該書中記載了很多衍生下來的很多程式的設計模式    比如JAVA語言的單繼承的設計模式,因為在java語言中沒有多繼承。

《演算法導論》 記載了很多經典的演算法 及時在今天沒有用了的一些演算法  都記錄了下來

我們繼續來說抽象類和介面類  介面類和抽象類都只是一種程式設計規範  只是為了規範程式設計而已,並不能讓你的程式變得這麼樣,如果這個程式只由我自己一個人來完成 我自己遵守預設的規範那麼我就不需要進行這些操作

來我們看一段程式碼

這段程式碼中的class A 就是抽象類  抽象類的目的是為了下面的類都繼承A類的方法   因為在呼叫方法的時候用了一個歸一化設計  以免其他的程式設計師在建立類的方法的時候沒有建立抽象類的方法
導致歸一化設計的函式在呼叫類的方法的時候出錯
from
abc import ABCMeta,abstractmethod class A(metaclass = ABCMeta): @abstractmethod def pay(self):pass class Alipay(A): def pay(self,money): print(`支付寶支付%s元` % money) class QQpay(A): def pay(self,money): print(`用qq支付了%s元` % money) class WEchat(A): def pay(self,money): print(`用微信支付%s元` % money) a = Alipay() B = QQpay() c = WEchat() def pay(a,money): 這裡就是歸一化設計 a.pay(money) pay(a,100) pay(B,200) pay(c,300) ###### 支付寶支付100元 用qq支付了200元 用微信支付300元

 我們在來看另一個程式碼

 

from abc import ABCMeta,abstractmethod
class FlyAnimal(metaclass=ABCMeta):
    @abstractmethod
    def fly(self):pass
    @abstractmethod
    def cal_flying_speed(self):pass
    @abstractmethod
    def cal_flying_height(self):pass
class WalkAnimal(metaclass=ABCMeta):
    @abstractmethod
    def walk(self):pass
class SwimAnimal(metaclass=ABCMeta):
    @abstractmethod
    def walk(self):pass
class Tiger(WalkAnimal,SwimAnimal):
    def walk(self):pass
    def swim(self):pass
class Monkey:
    def walk(self):pass
    def climb(self):pass
class Swan(FlyAnimal,WalkAnimal,SwimAnimal):
    def swim(self):pass
    def walk(self):pass
    def fly(self):pass
    def cal_flying_speed(self):pass
    def cal_flying_height(self):pass
class Parrot(FlyAnimal):
    def fly(self):pass
    def cal_flying_speed(self):pass
    def cal_flying_height(self): pass

 

 上面這段程式碼中我們可以看到當多個類中有相同也有不同的方法,一個類中包含幾個抽象類中的方法,這時候也由於java沒有多繼承 所以當java遇到這種情況的時候 就需要將我們python中的抽象類 建成介面類  以便於多繼承  也就是說java只可以多繼承介面類

總結:無論是抽象類也好 還是介面類都是為了規範程式碼

    介面類:是java的概念 所以說介面類只存在java中,是方便java中解決多繼承的問題的  同時還有介面隔離的問題(因為當不同的類包含有多種不同的方法時候,就需要建立不同的介面類來隔離)      

         抽象類  :在python中就是用來規範程式碼的   在python中遇到不同的類包含不同的方法 又需要規範每個類中的方法名  這時候我們就需要建立不同的抽象類

 

 多型 

 在說多型之前我們首先來認識下什麼是強型別語言什麼是弱型別語言

強型別:c c+ c++  c# java 這些強型別的語言都有個共性就是在傳參的時候有個特點 只能傳一種資料型別的引數

強弱型語言:python   

弱型別的語言:php    shel   這些語言在資料型別上就沒有那麼嚴格    

什麼是多型我們看下面這個程式碼

from abc import ABCMeta,abstractmethod
class A(metaclass = ABCMeta):                   
    @abstractmethod
    def pay(self):pass                            
class Alipay(A):
    def pay(self,money):
        print(`支付寶支付%s元` % money)
class QQpay(A):
    def pay(self,money):
        print(`用qq支付了%s元` % money)
class WEchat(A):
    def pay(self,money):
        print(`用微信支付%s元` % money)
a = Alipay()           這裡我們看下a 的資料型別為<class `__main__.Alipay`>
B = QQpay()                      B 的資料型別為 <class`__main__.QQpqy`> 
c = WEchat()                     c 的資料型別為<class`__main__.WEchat`>
def pay(a,money):          
    a.pay(money)
pay(a,100)
pay(B,200)
pay(c,300)
######
支付寶支付100元
用qq支付了200元
用微信支付300元

 上面我們看到了所有的例項化後打變數名的資料型別都為類名  而在我們在呼叫歸一化設計的函式時,傳入的第一個位置引數的資料型別時不一樣的  在java中是不行的  因為他強語言的特性   所以是絕對不允許每次都傳入不同的資料型別 那怎麼辦呢 ??所以就只有再建立一個父類,讓子類都來繼承父類,把歸一化的函式的傳參的資料型別改成父類,傳參時 傳入子類的資料型別就可以了  ,這也就是java實現多型性的辦法。

而這樣複雜的問題在python中就不存在了,由於python自帶多型性,所以這種問題根本就不存在。

我們深究一下為什麼python會自帶多型,其實說白了是那些編寫python的大佬 達成了一種默契 直接把各種父類子類的問題都解決好了 都寫成一樣的方法名 所以python才會自帶多型

程式設計原則:

開放封閉原則:對擴充套件是開放的 對修改是關閉的(比如說裝飾器就是對這個原則的執行)

依賴倒置原則:底層依賴上層

介面隔離原則:上面說過了我們據不在敘述了

封裝

說道這裡我們們再來和java做下對比

public  共有的  在類的內部可以使用  子類可以使用 外部可以使用           python中所有的都可以做到

protect 保護的  在類的內部可以使用  子類可以使用  外部不能使用         Python中沒有

private 私有的  在類的內部可以使用  子類不可以使用 外部不能使用     python  中的__名

封裝什麼叫封裝,就是我要把有些屬性,和方法,物件的屬性封裝起來,不願意在類的外面直接呼叫修改。     

 我們來看一個面試題

class D:
    def __init__(self):
        self.__func()
    def __func(self):
        print(`in D`)

class E(D):
    def __func(self):
        print(`in E`)
e = E()
###
in D
分析:第一步例項化 然後自動執行了D類中的def__init__(self): self.__func() 找到D類中的私有的方法__func()
父類不想讓子類繼承自己方法也可以讓方法私有化,這樣就不能再外面呼叫父類的方法
如果要呼叫E中的__func()則要這樣寫e._E__func() 但是這樣雖說可以呼叫 但是我們絕不可以這樣寫 這樣只是可以做到。

 

 @property 關鍵字
我們在寫類的方法的時候經常會遇到一些方法是動作但是我們得到的結果只是個名詞,我們為了規範程式碼會使用property這個關鍵字,也就是把方法偽裝成屬性。
但是需要注意的是,使用property時,這個方法的的引數只能有一個引數(self)
@類名.setter
這種方法是用來對偽裝的屬性進行賦值時候呼叫的方法 需要注意的是方法名必須和要偽裝的方法名一致 並且賦值的時候呼叫的方法名也要一致
class A:
    def __init__(self,name,discount,price):
        self.name = name
        self.__discount = discount
        self.__price =price
    @property 
    def dis_price(self):                            一致dis_price
        return self.__discount*self.__price
    @dis_price.setter                               一致dis_price     
    def dis_price(self,newdiscount):
        self.__discount = newdiscount
apple = A(`apple`,0.8,5)
print(apple.dis_price)
apple.dis_price = 0.3                               一致dis_price 
print(apple.dis_price)
####
4
1.5
這裡需要注意的是 如果在類中再建立一個和偽裝方法名一個屬性,則會被將內偽裝的方法名覆蓋,列印出來的是方法的記憶體地址

 

 @方法名.deleter  刪除方法 這個基本上不用   刪除屬性
class Person:
    def __init__(self,n):
        self.__name = n  # 私有的屬性了
    @property            # 重要程度 ****
    def name(self):
        return self.__name
    @name.deleter
    def name(self):
        print(`name 被刪除了`)
    @name.deleter         # 重要程度*
    def name(self):
        del self.__name

p = Person(`alex`)
print(p.name)
del p.name  # 只是執行了被@name.deleter裝飾的函式
print(p.name)

 

@classmethod
這個我們舉兩個例子看下面的程式碼
class A:
    discount__ = 0.8
    def __init__(self,name,price):
        self.name = name
        self.__price = price
    @property
    def really_price(self):
        return self.__price*A.__discount
    @classmethod                                 類方法:可以直接被類呼叫 不用再例項化  並且只需要傳一個類引數 當
    def new_discount(cls,newdiscount):            
        cls.__discount = newdiscount
A.new_discount(0.8)                                        只需要傳一個引數
a = A(`a`,3)
print(a.really_price)
由這段程式碼可以看出 當折扣恢復到原來的狀態時只需要進行如下操作
A.new_discount(1)
所以當一個方法需要使用類中的靜態屬性時 而不需要其他操作時 就用類方法


@staticmethod
class Student:
    def __init__(self,name):pass
    @staticmethod
    def login():                   # login就是一個類中的靜態方法 靜態方法沒有預設引數 就當成普通的函式使用即可
        user = input(`user :`)
        if user == `alex`:
            print(`success`)
        else:
            print(`faild`)
Student.login()

當一個方法既不需要用到物件中的屬性又不需要用到類中的靜態屬性 就可以用到靜態方法來直接呼叫類中的方法 不需要例項化

 

 

 

 


相關文章