初識——面向對象

Python3 面向對象

Python從設計之初就已是一門面向對象的語言,正由於如此,在Python中建立一個類和對象是很容易的。本章節咱們將詳細介紹Python的面向對象編程。java

若是你之前沒有接觸過面向對象的編程語言,那你可能須要先了解一些面嚮對象語言的一些基本特徵,在頭腦裏頭造成一個基本的面向對象的概念,這樣有助於你更容易的學習Python的面向對象編程。python

接下來咱們先來簡單的瞭解下面向對象的一些基本特徵。linux


面向對象技術簡介

  • 類(Class): 用來描述具備相同的屬性和方法的對象的集合。它定義了該集合中每一個對象所共有的屬性和方法。對象是類的實例。
  • 類變量:類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體以外。類變量一般不做爲實例變量使用。
  • 數據成員:類變量或者實例變量用於處理類及其實例對象的相關的數據。
  • 方法重寫:若是從父類繼承的方法不能知足子類的需求,能夠對其進行改寫,這個過程叫方法的覆蓋(override),也稱爲方法的重寫。
  • 實例變量:定義在方法中的變量,只做用於當前實例的類。
  • 繼承:即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也容許把一個派生類的對象做爲一個基類對象對待。例如,有這樣一個設計:一個Dog類型的對象派生自Animal類,這是模擬"是一個(is-a)"關係(例圖,Dog是一個Animal)。
  • 實例化:建立一個類的實例,類的具體對象。
  • 方法:類中定義的函數。
  • 對象:經過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和方法。

和其它編程語言相比,Python 在儘量不增長新的語法和語義的狀況下加入了類機制。git

Python中的類提供了面向對象編程的全部基本功能:類的繼承機制容許多個基類,派生類能夠覆蓋基類中的任何方法,方法中能夠調用基類中的同名方法。程序員

對象能夠包含任意數量和類型的數據。github

類定義

語法格式以下:算法

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

類實例化後,可使用其屬性,實際上,建立一個類以後,能夠經過類名訪問其屬性。編程

類對象

類對象支持兩種操做:屬性引用和實例化。設計模式

屬性引用使用和 Python 中全部的屬性引用同樣的標準語法:obj.name微信

類對象建立後,類命名空間中全部的命名都是有效屬性名。因此若是類定義是這樣:

實例(Python 3.0+)

#!/usr/bin/python3
 
class MyClass:
    """一個簡單的類實例"""
    i = 12345
    def f(self):
        return 'hello world'
 
# 實例化類
x = MyClass()
 
# 訪問類的屬性和方法
print("MyClass 類的屬性 i 爲:", x.i)
print("MyClass 類的方法 f 輸出爲:", x.f())

以上建立了一個新的類實例並將該對象賦給局部變量 x,x 爲空的對象。

執行以上程序輸出結果爲:

MyClass 類的屬性 i 爲: 12345
MyClass 類的方法 f 輸出爲: hello world

不少類都傾向於將對象建立爲有初始狀態的。所以類可能會定義一個名爲 __init__() 的特殊方法(構造方法),像下面這樣:

def __init__(self):
    self.data = []

類定義了 __init__() 方法的話,類的實例化操做會自動調用 __init__() 方法。因此在下例中,能夠這樣建立一個新的實例:

x = MyClass()

固然, __init__() 方法能夠有參數,參數經過 __init__() 傳遞到類的實例化操做上。例如:

實例(Python 3.0+)
#!/usr/bin/python3
 
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i)   # 輸出結果:3.0 -4.5

self表明類的實例,而非類

類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是 self。

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
 
t = Test()
t.prt()
#以上實例執行結果爲:
<__main__.Test instance at 0x100771878>
__main__.Test

從執行結果能夠很明顯的看出,self 表明的是類的實例,表明當前對象的地址,而 self.class 則指向類。

self 不是 python 關鍵字,咱們把他換成 runoob 也是能夠正常執行的:

class Test:
    def prt(runoob):
        print(runoob)
        print(runoob.__class__)
 
t = Test()
t.prt()
#以上實例執行結果爲:
<__main__.Test instance at 0x100771878>
__main__.Test

類的方法

在類地內部,使用 def 關鍵字來定義一個方法,與通常函數定義不一樣,類方法必須包含參數 self, 且爲第一個參數,self 表明的是類的實例。 

實例(Python 3.0+)
#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age = 0
    #定義私有屬性,私有屬性在類外部沒法直接進行訪問
    __weight = 0
    #定義構造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 說: 我 %d 歲。" %(self.name,self.age))
 
# 實例化類
p = people('runoob',10,30)
p.speak()
#執行以上程序輸出結果爲:
runoob 說: 我 10 歲。

類的方法

在類地內部,使用 def 關鍵字來定義一個方法,與通常函數定義不一樣,類方法必須包含參數 self,且爲第一個參數,self 表明的是類的實例。

self 的名字並非規定死的,也可使用 this,可是最好仍是按照約定是用 self。

面向過程 VS 面向對象 

面向過程的程序設計的核心是過程(流水線式思惟),過程即解決問題的步驟,面向過程的設計就比如精心設計好一條流水線,考慮周全何時處理什麼東西。

優勢是:極大的下降了寫程序的複雜度,只須要順着要執行的步驟,堆疊代碼便可。

缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一髮而動全身。

用場景:一旦完成基本不多改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。 

面向對象的程序設計的核心是對象(上帝式思惟),要理解對象爲什麼物,必須把本身當成上帝,上帝眼裏世間存在的萬物皆爲對象,不存在的也能夠創造出來。面向對象的程序設計比如如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題須要四我的:唐僧,沙和尚,豬八戒,孫悟空,每一個人都有各自的特徵和技能(這就是對象的概念,特徵和技能分別對應對象的屬性和方法),然而這並很差玩,因而如來又安排了一羣妖魔鬼怪,爲了防止師徒四人在取經路上被搞死,又安排了一羣神仙保駕護航,這些都是對象。而後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥着直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。

面向對象的程序設計的

優勢是:解決了程序的擴展性。對某一個對象單獨修改,會馬上反映到整個體系中,如對遊戲中一我的物參數的特徵和技能修改都很容易。

缺點:可控性差,沒法向面向過程的程序設計流水線式的能夠很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題即使是上帝也沒法預測最終結果。因而咱們常常看到一個遊戲人某一參數的修改極有可能致使陰霸的技能出現,一刀砍死3我的,這個遊戲就失去平衡。

應用場景:需求常常變化的軟件,通常需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方。

在python 中面向對象的程序設計並非所有。

面向對象編程可使程序的維護和擴展變得更簡單,而且能夠大大提升程序開發效率 ,另外,基於面向對象的程序可使它人更加容易理解你的代碼邏輯,從而使團隊開發變得更從容。

瞭解一些名詞:類、對象、實例、實例化

類:具備相同特徵的一類事物(人、狗、老虎)

對象/實例:具體的某一個事物(隔壁阿花、樓下旺財)

實例化:類——>對象的過程(這在生活中表現的不明顯,咱們在後面再慢慢解釋)

初識面向對象小結

定義一我的類

class Person:  # 定義一我的類
    role = 'person'  # 人的角色屬性都是人

    def __init__(self, name, aggressivity, life_value, money):
        self.name = name  # 每個角色都有本身的暱稱;
        self.aggressivity = aggressivity  # 每個角色都有本身的攻擊力;
        self.life_value = life_value  # 每個角色都有本身的生命值;
        self.money = money

    def attack(self,dog):
        # 人能夠攻擊狗,這裏的狗也是一個對象。
        # 人攻擊狗,那麼狗的生命值就會根據人的攻擊力而降低
        dog.life_value -= self.aggressivity

定義一個狗類

class Dog:  # 定義一個狗類
    role = 'dog'  # 狗的角色屬性都是狗

    def __init__(self, name, breed, aggressivity, life_value):
        self.name = name  # 每一隻狗都有本身的暱稱;
        self.breed = breed  # 每一隻狗都有本身的品種;
        self.aggressivity = aggressivity  # 每一隻狗都有本身的攻擊力;
        self.life_value = life_value  # 每一隻狗都有本身的生命值;

    def bite(self,people):
        # 狗能夠咬人,這裏的狗也是一個對象。
        # 狗咬人,那麼人的生命值就會根據狗的攻擊力而降低
        people.life_value -= self.aggressivity

接下來,又建立一個新的兵器類。

class Weapon:
    def __init__(self,name, price, aggrev, life_value):
        self.name = name
        self.price = price
        self.aggrev = aggrev
        self.life_value = life_value

    def update(self, obj):  #obj就是要帶這個裝備的人
        obj.money -= self.price  # 用這個武器的人花錢買因此對應的錢要減小
        obj.aggressivity += self.aggrev  # 帶上這個裝備可讓人增長攻擊
        obj.life_value += self.life_value  # 帶上這個裝備可讓人增長生命值

    def prick(self, obj):  # 這是該裝備的主動技能,扎死對方
        obj.life_value -= 500  # 假設攻擊力是500

測試交互

lance = Weapon('長矛',200,6,100)
egg = Person('egon',10,1000,600)  #創造了一個實實在在的人egg
ha2 = Dog('二愣子','哈士奇',10,1000)  #創造了一隻實實在在的狗ha2

#egg獨自力戰"二愣子"深感吃力,決定窮畢生積蓄買一把武器
if egg.money > lance.price: #若是egg的錢比裝備的價格多,能夠買一把長矛
    lance.update(egg) #egg花錢買了一個長矛防身,且自身屬性獲得了提升
    egg.weapon = lance #egg裝備上了長矛

print(egg.money,egg.life_value,egg.aggressivity)

print(ha2.life_value)
egg.attack(ha2)   #egg打了ha2一下
print(ha2.life_value)
egg.weapon.prick(ha2) #發動武器技能
print(ha2.life_value) #ha2不敵狡猾的人類用武器取勝,血槽空了一半

按照這種思路一點一點的設計類和對象,最終你徹底能夠實現一個對戰類遊戲。

類命名空間與對象、實例的命名空間

建立一個類就會建立一個類的名稱空間,用來存儲類中定義的全部名字,這些名字稱爲類的屬性

而類有兩種屬性:靜態屬性和動態屬性

  • 靜態屬性就是直接在類中定義的變量
  • 動態屬性就是定義在類中的方法

其中類的數據屬性是共享給全部對象的

>>>id(egg.role)
4341594072
>>>id(Person.role)
4341594072

 

而類的動態屬性是綁定到全部對象的

>>>egg.attack
<bound method Person.attack of <__main__.Person object at 0x101285860>>
>>>Person.attack
<function Person.attack at 0x10127abf8> 

建立一個對象/實例就會建立一個對象/實例的名稱空間,存放對象/實例的名字,稱爲對象/實例的屬性

在obj.name會先從obj本身的名稱空間裏找name,找不到則去類中找,類也找不到就找父類...最後都找不到就拋出異常

面向對象的組合用法

軟件重用的重要方式除了繼承以外還有另一種方式,即:組合

組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合

class Weapon:
    def prick(self, obj):  # 這是該裝備的主動技能,扎死對方
        obj.life_value -= 500  # 假設攻擊力是500

class Person:  # 定義一我的類
    role = 'person'  # 人的角色屬性都是人

    def __init__(self, name):
        self.name = name  # 每個角色都有本身的暱稱;
        self.weapon = Weapon()  # 給角色綁定一個武器;
        
egg = Person('egon')
egg.weapon.prick() 
#egg組合了一個武器的對象,能夠直接egg.weapon來使用組合類中的全部方法


圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積。圓環的周長是內部圓的周長加上外部圓的周長。
這個時候,咱們就首先實現一個圓形類,計算一個圓的周長和麪積。而後在"環形類"中組合圓形的實例做爲本身的屬性來用

from math import pi

class Circle:
    '''
    定義了一個圓形類;
    提供計算面積(area)和周長(perimeter)的方法
    '''
    def __init__(self,radius):
        self.radius = radius

    def area(self):
         return pi * self.radius * self.radius

    def perimeter(self):
        return 2 * pi *self.radius


circle =  Circle(10) #實例化一個圓
area1 = circle.area() #計算圓面積
per1 = circle.perimeter() #計算圓周長
print(area1,per1) #打印圓面積和周長

class Ring:
    '''
    定義了一個圓環類
    提供圓環的面積和周長的方法
    '''
    def __init__(self,radius_outside,radius_inside):
        self.outsid_circle = Circle(radius_outside)
        self.inside_circle = Circle(radius_inside)

    def area(self):
        return self.outsid_circle.area() - self.inside_circle.area()

    def perimeter(self):
        return  self.outsid_circle.perimeter() + self.inside_circle.perimeter()


ring = Ring(10,5) #實例化一個環形
print(ring.perimeter()) #計算環形的周長
print(ring.area()) #計算環形的面積

用組合的方式創建了類與組合的類之間的關係,它是一種‘有’的關係,好比教授有生日,教授教python課程

class BirthDate:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

class Couse:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

class Teacher:
    def __init__(self,name,gender,birth,course):
        self.name=name 
        self.gender=gender
        self.birth=birth
        self.course=course
    def teach(self): 
        print('teaching')

p1=Teacher('egon','male', 
            BirthDate('1995','1','27'), 
            Couse('python','28000','4 months')
           ) 

print(p1.birth.year,p1.birth.month,p1.birth.day) 

print(p1.course.name,p1.course.price,p1.course.period)
''' 
運行結果: 
27 
python 28000 4 months 
'''

當類之間有顯著不一樣,而且較小的類是較大的類所須要的組件時,用組合比較好

 

初識面向對象小結

class Person:  # 定義一我的類
    role = 'person'  # 人的角色屬性都是人

    def __init__(self, name, aggressivity, life_value, money):
        self.name = name  # 每個角色都有本身的暱稱;
        self.aggressivity = aggressivity  # 每個角色都有本身的攻擊力;
        self.life_value = life_value  # 每個角色都有本身的生命值;
        self.money = money

    def attack(self,dog):
        # 人能夠攻擊狗,這裏的狗也是一個對象。
        # 人攻擊狗,那麼狗的生命值就會根據人的攻擊力而降低
        dog.life_value -= self.aggressivity


class Person:  # 定義一我的類
    role = 'person'  # 人的角色屬性都是人

    def __init__(self, name, aggressivity, life_value, money):
        self.name = name  # 每個角色都有本身的暱稱;
        self.aggressivity = aggressivity  # 每個角色都有本身的攻擊力;
        self.life_value = life_value  # 每個角色都有本身的生命值;
        self.money = money

    def attack(self,dog):
        # 人能夠攻擊狗,這裏的狗也是一個對象。
        # 人攻擊狗,那麼狗的生命值就會根據人的攻擊力而降低
        dog.life_value -= self.aggressivity

class Weapon:
    def __init__(self,name, price, aggrev, life_value):
        self.name = name
        self.price = price
        self.aggrev = aggrev
        self.life_value = life_value

    def update(self, obj):  #obj就是要帶這個裝備的人
        obj.money -= self.price  # 用這個武器的人花錢買因此對應的錢要減小
        obj.aggressivity += self.aggrev  # 帶上這個裝備可讓人增長攻擊
        obj.life_value += self.life_value  # 帶上這個裝備可讓人增長生命值

    def prick(self, obj):  # 這是該裝備的主動技能,扎死對方
        obj.life_value -= 500  # 假設攻擊力是500

lance = Weapon('長矛',200,6,100)
egg = Person('egon',10,1000,600)  #創造了一個實實在在的人egg
ha2 = Dog('二愣子','哈士奇',10,1000)  #創造了一隻實實在在的狗ha2

#egg獨自力戰"二愣子"深感吃力,決定窮畢生積蓄買一把武器
if egg.money > lance.price: #若是egg的錢比裝備的價格多,能夠買一把長矛
    lance.update(egg) #egg花錢買了一個長矛防身,且自身屬性獲得了提升
    egg.weapon = lance #egg裝備上了長矛

print(egg.money,egg.life_value,egg.aggressivity)

print(ha2.life_value)
egg.attack(ha2)   #egg打了ha2一下
print(ha2.life_value)
egg.weapon.prick(ha2) #發動武器技能
print(ha2.life_value) #ha2不敵狡猾的人類用武器取勝,血槽空了一半
組合遊戲

繼承

什麼是繼承

繼承是一種建立新類的方式,在python中,新建的類能夠繼承一個或多個父類,父類又可稱爲基類或超類,新建的類稱爲派生類或子類

 

python中類的繼承分爲:單繼承和多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass

查看繼承

>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看全部繼承的父類
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

提示:若是沒有指定基類,python的類會默認繼承object類,object是全部python類的基類,它提供了一些常見方法(如__str__)的實現。

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

繼承與抽象(先抽象再繼承)

抽象即抽取相似或者說比較像的部分。

抽象分紅兩個層次: 

1.將奧巴馬和梅西這倆對象比較像的部分抽取成類; 

2.將人,豬,狗這三個類比較像的部分抽取成父類。

抽象最主要的做用是劃分類別(能夠隔離關注點,下降複雜度)

 

繼承:是基於抽象的結果,經過編程語言去實現它,確定是先經歷抽象這個過程,才能經過繼承的方式去表達出抽象的結構。

抽象只是分析和設計的過程當中,一個動做或者說一種技巧,經過抽象能夠獲得類

繼承與重用性

==========================第一部分
例如

  貓能夠:喵喵叫、吃、喝、拉、撒

  狗能夠:汪汪叫、吃、喝、拉、撒

若是咱們要分別爲貓和狗建立一個類,那麼就須要爲 貓 和 狗 實現他們全部的功能,僞代碼以下:
 

#貓和狗有大量相同的內容
class 貓:

    def 喵喵叫(self):
        print '喵喵叫'

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

class 狗:

    def 汪汪叫(self):
        print '汪汪叫'

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something



==========================第二部分
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具備的功能,而咱們卻分別的貓和狗的類中編寫了兩次。若是使用 繼承 的思想,以下實現:

  動物:吃、喝、拉、撒

     貓:喵喵叫(貓繼承動物的功能)

     狗:汪汪叫(狗繼承動物的功能)

僞代碼以下:
class 動物:

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

# 在類後面括號中寫入另一個類名,表示當前類繼承另一個類
class 貓(動物):

    def 喵喵叫(self):
        print '喵喵叫'
        
# 在類後面括號中寫入另一個類名,表示當前類繼承另一個類
class 狗(動物):

    def 汪汪叫(self):
        print '汪汪叫'




==========================第三部分
#繼承的代碼實現
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 ("%s 撒 " %self.name)


class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = ''

    def cry(self):
        print('喵喵叫')

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed=''

    def cry(self):
        print('汪汪叫')


# ######### 執行 #########

c1 = Cat('小白家的小黑貓')
c1.eat()

c2 = Cat('小黑的小白貓')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

使用繼承來重用代碼比較好的例子
使用繼承來解決代碼重用的例子

在開發程序的過程當中,若是咱們定義了一個類A,而後又想新創建另一個類B,可是類B的大部份內容與類A的相同時

咱們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。

經過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的全部屬性(數據屬性和函數屬性),實現代碼重用

class Animal:
    '''
    人和狗都是動物,因此創造一個Animal基類
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有本身的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有本身的攻擊力;
        self.life_value = life_value  # 人和狗都有本身的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    pass

class Person(Animal):
    pass

egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
egg.eat()
ha2.eat()

提示:用已經有的類創建一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大生了編程工做量,這就是常說的軟件重用,不只能夠重用本身的類,也能夠繼承別人的,好比標準庫,來定製新的數據類型,這樣就是大大縮短了軟件開發週期,對大型軟件開發來講,意義重大.

派生

固然子類也能夠添加本身新的屬性或者在本身這裏從新定義這些屬性(不會影響到父類),須要注意的是,一旦從新定義了本身的屬性且與父類重名,那麼調用新增的屬性時,就以本身爲準了。

class Animal:
    '''
    人和狗都是動物,因此創造一個Animal基類
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有本身的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有本身的攻擊力;
        self.life_value = life_value  # 人和狗都有本身的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    '''
    狗類,繼承Animal類
    '''
    def bite(self, people):
        '''
        派生:狗有咬人的技能
        :param people:  
        '''
        people.life_value -= self.aggressivity

class Person(Animal):
    '''
    人類,繼承Animal
    '''
    def attack(self, dog):
        '''
        派生:人有攻擊的技能
        :param dog: 
        '''
        dog.life_value -= self.aggressivity

egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
print(ha2.life_value)
print(egg.attack(ha2))
print(ha2.life_value)

注意:像ha2.life_value之類的屬性引用,會先從實例中找life_value而後去類中找,而後再去父類中找...直到最頂級的父類。

 

在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能須要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,所以即使是self參數也要爲其傳值.

在python3中,子類執行父類的方法也能夠直接用super方法.

class Animal:
    '''
    人和狗都是動物,因此創造一個Animal基類
    '''
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有本身的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有本身的攻擊力;
        self.life_value = life_value  # 人和狗都有本身的生命值;

    def eat(self):
        print('%s is eating'%self.name)

class Dog(Animal):
    '''
    狗類,繼承Animal類
    '''
    def __init__(self,name,breed,aggressivity,life_value):
        super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法
        self.breed = breed  #派生出了新的屬性

    def bite(self, people):
        '''
        派生出了新的技能:狗有咬人的技能
        :param people:  
        '''
        people.life_value -= self.aggressivity

    def eat(self):
        # Animal.eat(self)
        #super().eat()
        print('from Dog')

class Person(Animal):
    '''
    人類,繼承Animal
    '''
    def __init__(self,name,aggressivity, life_value,money):
        #Animal.__init__(self, name, aggressivity, life_value)
        #super(Person, self).__init__(name, aggressivity, life_value)
        super().__init__(name,aggressivity, life_value)  #執行父類的init方法
        self.money = money   #派生出了新的屬性

    def attack(self, dog):
        '''
        派生出了新的技能:人有攻擊的技能
        :param dog: 
        '''
        dog.life_value -= self.aggressivity

    def eat(self):
        #super().eat()
        Animal.eat(self)
        print('from Person')

egg = Person('egon',10,1000,600)
ha2 = Dog('二愣子','哈士奇',10,1000)
print(egg.name)
print(ha2.name)
egg.eat()
super示例

經過繼承創建了派生類與基類之間的關係,它是一種'是'的關係,好比白馬是馬,人是動物。

當類之間有不少相同的功能,提取這些共同的功能作成基類,用繼承比較好,好比教授是老師

>>> class Teacher:
...     def __init__(self,name,gender):
...         self.name=name
...         self.gender=gender
...     def teach(self):
...         print('teaching')
... 
>>> 
>>> class Professor(Teacher):
...     pass
... 
>>> p1=Professor('egon','male')
>>> p1.teach()
teaching

抽象類與接口類

繼承有兩種用途:

一:繼承基類的方法,而且作出本身的改變或者擴展(代碼重用)  

二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,而且實現接口中的功能

class Alipay:
    '''
    支付寶支付
    '''
    def pay(self,money):
        print('支付寶支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)


def pay(payment,money):
    '''
    支付函數,整體負責支付
    對應支付的對象和要支付的金額
    '''
    payment.pay(money)


p = Alipay()
pay(p,200)


#。。。。。。。。。。。
class Alipay:
    '''
    支付寶支付
    '''
    def pay(self,money):
        print('支付寶支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)

class Wechatpay:
    def fuqian(self,money):
        '''
        實現了pay的功能,可是名字不同
        '''
        print('微信支付了%s元'%money)

def pay(payment,money):
    '''
    支付函數,整體負責支付
    對應支付的對象和要支付的金額
    '''
    payment.pay(money)


p = Wechatpay()
pay(p,200)   #執行會報錯

class Payment:
    def pay(self):
        raise NotImplementedError

class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)


p = Wechatpay()  #這裏不報錯
pay(p,200)      #這裏報錯了


#。。。。。。。。。。。。。

from abc import ABCMeta,abstractmethod

class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self,money):
        pass


class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)

p = Wechatpay() #不調就報錯了
View Code
class Alipay:
    '''
    支付寶支付
    '''
    def pay(self,money):
        print('支付寶支付了%s元'%money)

class Applepay:
    '''
    apple pay支付
    '''
    def pay(self,money):
        print('apple pay支付了%s元'%money)

class Wechatpay:
    def fuqian(self,money):
        '''
        實現了pay的功能,可是名字不同
        '''
        print('微信支付了%s元'%money)

def pay(payment,money):
    '''
    支付函數,整體負責支付
    對應支付的對象和要支付的金額
    '''
    payment.pay(money)


p = Wechatpay()
pay(p,200)   #執行會報錯

接口初成:手動報異常:NotImplementedError來解決開發中遇到的問題

class Payment:
    def pay(self):
        raise NotImplementedError

class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)


p = Wechatpay()  #這裏不報錯
pay(p,200)      #這裏報錯了

借用abc模塊來實現接口

from abc import ABCMeta,abstractmethod

class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self,money):
        pass


class Wechatpay(Payment):
    def fuqian(self,money):
        print('微信支付了%s元'%money)

p = Wechatpay() #不調就報錯了

實踐中,繼承的第一種含義意義並不很大,甚至經常是有害的。由於它使得子類與基類出現強耦合。

繼承的第二種含義很是重要。它又叫「接口繼承」。
接口繼承實質上是要求「作出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的全部對象」——這在程序設計上,叫作歸一化。

歸一化使得高層的外部使用者能夠不加區分的處理全部接口兼容的對象集合——就好象linux的泛文件概念同樣,全部東西均可以當文件處理,沒必要關心它是內存、磁盤、網絡仍是屏幕(固然,對底層設計者,固然也能夠區分出「字符設備」和「塊設備」,而後作出針對性的設計:細緻到什麼程度,視需求而定)。

依賴倒置原則:
高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程

在python中根本就沒有一個叫作interface的關鍵字,上面的代碼只是看起來像接口,其實並無起到接口的做用,子類徹底能夠不用去實現接口 ,若是非要去模仿接口的概念,能夠藉助第三方模塊:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py裏使用zope.interface

文檔https://zopeinterface.readthedocs.io/en/latest/

設計模式:https://github.com/faif/python-patterns

接口提取了一羣類共同的函數,能夠把接口當作一個函數的集合。

而後讓子類去實現接口中的函數。

這麼作的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個接口實現的類,那麼全部的這些類產生的對象在使用時,從用法上來講都同樣。

歸一化,讓使用者無需關心對象的類是什麼,只須要的知道這些對象都具有某些功能就能夠了,這極大地下降了使用者的使用難度。

好比:咱們定義一個動物接口,接口裏定義了有跑、吃、呼吸等接口函數,這樣老鼠的類去實現了該接口,松鼠的類也去實現了該接口,由兩者分別產生一隻老鼠和一隻松鼠送到你面前,即使是你分別不到底哪隻是什麼鼠你確定知道他倆都會跑,都會吃,都能呼吸。

再好比:咱們有一個汽車接口,裏面定義了汽車全部的功能,而後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,你們只須要學會了怎麼開汽車,那麼不管是本田,仍是奧迪,仍是大衆咱們都會開了,開的時候根本無需關心我開的是哪一類車,操做手法(函數調用)都同樣

爲什麼要用接口
爲什麼要用接口

抽象類

什麼是抽象類

    與java同樣,python也有抽象類的概念可是一樣須要藉助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化

爲何要有抽象類

    若是說類是從一堆對象中抽取相同的內容而來的,那麼抽象類是從一堆中抽取相同的內容而來的,內容包括數據屬性和函數屬性。

  好比咱們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠沒法吃到一個叫作水果的東西。

    從設計角度去看,若是類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。

  從實現角度來看,抽象類與普通類的不一樣之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點相似,但實際上是不一樣的,即將揭曉答案

在python中實現抽象類

#一切皆文件
import abc #利用abc模塊實現抽象類

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定義抽象方法,無需實現功能
    def read(self):
        '子類必須定義讀功能'
        pass

    @abc.abstractmethod #定義抽象方法,無需實現功能
    def write(self):
        '子類必須定義寫功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法

class Txt(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self):
        print('文本數據的讀取方法')

    def write(self):
        print('文本數據的讀取方法')

class Sata(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self):
        print('硬盤數據的讀取方法')

    def write(self):
        print('硬盤數據的讀取方法')

class Process(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self):
        print('進程數據的讀取方法')

    def write(self):
        print('進程數據的讀取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#這樣你們都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

抽象類與接口類

抽象類的本質仍是類,指的是一組類的類似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的類似性。

抽象類是一個介於類和接口直接的一個概念,同時具有類和接口的部分特性,能夠用來實現歸一化設計 

在python中,並無接口類這種東西,即使不經過專門的模塊定義接口,咱們也應該有一些基本的概念。

1.多繼承問題

在繼承抽象類的過程當中,咱們應該儘可能避免多繼承;
而在繼承接口的時候,咱們反而鼓勵你來多繼承接口

接口隔離原則:
使用多個專門的接口,而不使用單一的總接口。即客戶端不該該依賴那些不須要的接口。

2.方法的實現

在抽象類中,咱們能夠對一些抽象方法作出基礎實現;
而在接口類中,任何方法都只是一種規範,具體的功能須要子類實現

 

鑽石繼承

繼承順序

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式纔有這個屬性能夠查看線性列表,經典類沒有這個屬性

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類

繼承順序

繼承原理

python究竟是如何實現繼承的,對於你定義的每個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的全部基類的線性順序列表,例如

>>> F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

 

爲了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類爲止。
而這個MRO列表的構造是經過一個C3線性化算法來實現的。咱們不去深究這個算法的數學原理,它實際上就是合併全部父類的MRO列表並遵循以下三條準則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.若是對下一個類存在兩個合法的選擇,選擇第一個父類

繼承小結

繼承的做用:
減小代碼的重用
提升代碼可讀性
規範編程模式

 

幾個名詞:
抽象:抽象即抽取相似或者說比較像的部分。是一個從具題到抽象的過程。
繼承:子類繼承了父類的方法和屬性
派生:子類在父類方法和屬性的基礎上產生了新的方法和屬性

#egon老師的生日 是否是birth類的一個實例
#egon.birth = Birth()  #組合

#在繼承中
#繼承的語法:
# class 類名(父類名):
# 想在子類中實現調用父類的方法
    #在類內 ——super(子類名,self).方法名()
    #在類外面 ——super(子類名,對象名).方法名()
#若是不指定繼承的父類,默認繼承object
#子類可使用父類的全部屬性和方法
    #若是子類有本身的方法就執行本身的的
    #若是是子類沒有的方法就執行父類的
    #若是子類父類都沒有這個方法就報錯

#繼承、抽象、派生
#繼承 是從大範圍到小範圍
#抽象 小範圍到大範圍
#派生 就是在父類的基礎上又產生子類——派生類
        #父類裏沒有的 但子類有的 ——派生方法
        #派生屬性
#方法的重寫
    #父類裏有的方法,在子類裏從新實現

 

抽象類與接口類

1.多繼承問題
在繼承抽象類的過程當中,咱們應該儘可能避免多繼承;
而在繼承接口的時候,咱們反而鼓勵你來多繼承接口


2.方法的實現
在抽象類中,咱們能夠對一些抽象方法作出基礎實現;
而在接口類中,任何方法都只是一種規範,具體的功能須要子類實現

鑽石繼承

新式類:廣度優先
經典類:深度優先

多態

多態

多態指的是一類事物有多種形態

動物有多種形態:人,狗,豬

import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #動物的形態之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #動物的形態之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #動物的形態之三:豬
    def talk(self):
        print('say aoao')

文件有多種形態:文本文件,可執行文件

import abc
class File(metaclass=abc.ABCMeta): #同一類事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形態之一:文本文件
    def click(self):
        print('open file')

class ExeFile(File): #文件的形態之二:可執行文件
    def click(self):
        print('execute file')

多態性

一 什麼是多態動態綁定(在繼承的背景下使用時,有時也稱爲多態性)

多態性是指在不考慮實例類型的狀況下使用實例

在面向對象方法中通常是這樣表述多態性:
向不一樣的對象發送同一條消息(!!!obj.func():是調用了obj的方法func,又稱爲向obj發送了一條消息func),不一樣的對象在接收時會產生不一樣的行爲(即方法)。
也就是說,每一個對象能夠用本身的方式去響應共同的消息。所謂消息,就是調用函數,不一樣的行爲就是指不一樣的實現,即執行不一樣的函數。

好比:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操做,學生執行的是放學操做,雖然兩者消息同樣,可是執行的效果不一樣

多態性分爲靜態多態性和動態多態性

  靜態多態性:如任何類型均可以用運算符+進行運算

  動態多態性:以下

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是動物,只要是動物確定有talk方法
#因而咱們能夠不用考慮它們三者的具體是什麼類型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更進一步,咱們能夠定義一個統一的接口來使用
def func(obj):
    obj.talk()

鴨子類型

逗比時刻:

  Python崇尚鴨子類型,即‘若是看起來像、叫聲像並且走起路來像鴨子,那麼它就是鴨子’

python程序員一般根據這種行爲來編寫程序。例如,若是想編寫現有對象的自定義版本,能夠繼承該對象

也能夠建立一個外觀和行爲像,但與它無任何關係的全新對象,後者一般用於保存程序組件的鬆耦合度。

例1:利用標準庫中定義的各類‘與文件相似’的對象,儘管這些對象的工做方式像文件,但他們沒有繼承內置文件對象的方法

例2:序列類型有多種形態:字符串,列表,元組,但他們直接沒有直接的繼承關係

#兩者都像鴨子,兩者看起來都像文件,於是就能夠當文件同樣去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

例子
示例
相關文章
相關標籤/搜索