面向對象

1. 三大編程範式

  • 面向過程
  • 函數式編程
  • 面向對象設計

2. 編程進化論

  • 最開始無組織無結構,從簡單控制流中按步驟寫指令
  • 從上述指令中提取重複的代碼看或邏輯,組織到一塊兒(定義了一個函數),實現代碼重用,由無結構走向告終構化,變得更具邏輯性。
  • 定義函數是獨立函數外定義變量,而後做爲參數傳遞給函數,意味着數據與動做是分離的。
  • 吧數據和動做封裝到一個結構(函數或類),就有了個對象系統(對象就是數據與函數的封裝)。

3. 類和對象

列表:數據封裝、函數:語句層面的封裝、對象:數據、函數的封裝java

  • 類:種類,將一類具備相同特性(抽象的)和動做的事物整合到一塊兒就是類。
  • 基於類而建立的一個具體的事物(具體存在),也是特徵和動做整合到一塊兒。

現實世界是先有對象再有類,程序中先定義類,後再有對象python

面向對象設計 OOD(object oriented design):將一類具體事務的數據和動做整合到一塊兒linux

面向對象編程 OOP(object-oriented programming):用定義類+實例/對象的方式去實現面向對象的設計程序員

實例化:由類抽象對象的過程實例化算法

不是隻有用 class 定義類纔是面向對象,def 定義函數就是函數相關,跟面向對象沒有關係:編程

面向對象設計:c#

def school(name,addr,type):
    def init(name, addr, type):
        sch = {
            'name': name,
            'addr': addr,
            'type': type,
            'kao_shi': kao_shi,
            'zhao_sheng': zhao_sheng,
        }
        return sch
    def kao_shi(school):
        print('%s 學校訂在考試' %school['name'])
    def zhao_sheng(school):
        print('%s %s 正在招生' %(school['type'],school['name']))
    return  init(name,addr,type)
s1 = school('清華大學', '中關村北大街', '公立學校')
print(s1)
print(s1['name'])
s1['kao_shi'](s1)
{'name': '清華大學', 'addr': '中關村北大街', 'type': '公立學校', 'kao_shi': <function school.<locals>.kao_shi at 0x0000000005382D90>, 'zhao_sheng': <function school.<locals>.zhao_sheng at 0x0000000005382BF8>}
清華大學
清華大學 學校訂在考試

面向對象編程:服務器

class School:
    def __init__(self, name, addr, types):
        self.name = name
        self.addr = addr
        self.types = types
    
    def kao_shi(self):
        print('%s 正在考試' % self.name)
    
    def zhao_sheng(self):
        print('%s %s 正在招生' %(self.types, self.name))
        
s1 = School('清華大學', '中關村北大街', '公立學校')
s1.kao_shi()
print(s1.__dict__)
清華大學 正在考試
{'name': '清華大學', 'addr': '中關村北大街', 'types': '公立學校'}

總結:網絡

Python 是一門面向對象的語言,可是它不強制要求必須使用面向對象去設計代碼,它不像 Java 同樣萬物皆是類。app

3.1 類

類聲明

class 類名:
    '類體字符串'
    類體
    
def func():
    '函數文檔字符串'
    函數體

3.2 實例化

self

若是把對象比做一個房子,那麼 self 就是門牌號,有了 self 就能夠輕鬆找到本身的房子。

當一個對象的方法被調用時,對象會將 自身的引用做爲第一個參數傳給該方法,那麼 Python 就知道須要操做哪一個對象的方法了。

class Students:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s 分數是:%s' % (self.name, self.score))
s1 = Students('rose', 98)       # 至關於 s1 = Stuents.__init__(s1, 'rose', 98)
s1.print_score()

rose 分數是:98

構造方法

一般把 __init__() 方法稱爲構造方法,只有實例化一個對象,這個方法就會在對象被建立時自動調用。實例化傳入的參數自動傳入 __init__() 中,能夠經過重寫這個方法來自定義對象的初始化操做:

class Students:
    def __init__(self, name):
        self.name = name
    def print_score(self):
        print('名字 %s' % self.name)
s1 = Students('rose')       # 自動調用 __init__(),參數自動傳入 __init__() 中
s1.print_score()

實例屬性

對象只有數據屬性,沒有函數屬性,用的時候找類要:

  • 類的數據屬性是全部對象共享的
  • 類的函數屬性是綁定給對象用的,在對象調用函數屬性時,也能找到
# s1.__dict__ 查看對象屬性
class Students:
    color = 'black'
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s 分數是:%s' % (self.name, self.score))
s1 = Students('rose', 98)
print(s1.__dict__)          # 查看對象屬性字典,發現只有屬性屬性
print(id(Students.color))   # 對比發現類和對象的數據屬性共享的
print(id(s1.color))
print(Students.print_score)     # 發現類的函數屬性與實例的函數屬性不同,內存地址不同
print(s1.print_score)
{'name': 'rose', 'score': 98}
82934784
82934784
<function Students.print_score at 0x0000000004F85620>
<bound method Students.print_score of <__main__.Students object at 0x0000000004F98BE0>>
# bound method :綁定方法(綁定了 類 Students 的 print_score 方法)

能夠看出對象只有數據屬性沒有函數屬性,類的函數屬性與實例的函數屬性不同,內存地址不同。

3.3 類屬性增刪改查

類屬性又稱爲靜態變量,或靜態數據,這些數據與它們所屬的類對象綁定,不依賴任何實例。若是是 java 程序員,這種類型數據至關於在一個變量聲明前加上 static 關鍵字

類名:大寫、函數屬性:動詞+名詞

數據屬性

class Chinese:
    country = 'China'   # 數據屬性
    def __init__(self, name):
        self.name = name
    def play_ball(self, ball):
        print('%s 正在打 %s' % (self.name))
p1 = Chinese('rose')
# 查看類屬性
print(Chinese.country)
# 修改
Chinese.country = 'CHINA'
print(Chinese.country)
# 增長
Chinese.gender = 'male'
print(Chinese.__dict__)
# 刪除
del Chinese.gender
print(Chinese.__dict__)
China
CHINA
{'__module__': '__main__', 'country': 'CHINA', '__init__': <function Chinese.__init__ at 0x0000000004F85D08>, 'play_ball': <function Chinese.play_ball at 0x0000000004F85BF8>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None, 'gender': 'male'}
{'__module__': '__main__', 'country': 'CHINA', '__init__': <function Chinese.__init__ at 0x0000000004F85D08>, 'play_ball': <function Chinese.play_ball at 0x0000000004F85BF8>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None}

函數屬性

# 增長一個 eat 方法
def eat(self, food):
    print('%s 正在吃 %s' % (self.name, food))
    
Chinese.test = eat      # test 指向 eat
p1.test('fish')
print(Chinese.__dict__) 
# Chinese.play_ball = ball      修改屬性
rose 正在吃 fish
{'__module__': '__main__', 'country': 'China', '__init__': <function Chinese.__init__ at 0x0000000004FA82F0>, 'play_ball': <function Chinese.play_ball at 0x0000000004FA8268>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None, 'test': <function eat at 0x0000000004F850D0>}

3.4 實例屬性的增刪改查

class Students:
    color = 'black'
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s 分數是:%s' % (self.name, self.score))
s1 = Students('rose', 98)
# 查看數據、函數屬性
print(s1.__dict__)
print(s1.name)
print(s1.print_score)  # <bound method Students.print_score of <__main__.Students object at 0x0000000004FDD198>>  能夠看出實例的函數屬性綁定的是 類的函數屬性(<bound method Students.print_score)

# 增長
s1.age = 18
print(s1.__dict__)

# 修改
s1.age = 20
print(s1.__dict__)

#不要修改底層的屬性字典,會形成 OOP 不穩定
# s1.__dict__['age']='30'
# print(s1.__dict__)
# print(s1.sex)

# 刪除
del s1.age
print(s1.__dict__)
{'name': 'rose', 'score': 98}
rose
<bound method Students.print_score of <__main__.Students object at 0x0000000004FDD198>>
{'name': 'rose', 'score': 98, 'age': 18}
{'name': 'rose', 'score': 98, 'age': 20}
{'name': 'rose', 'score': 98}

屬性做用域

在 class 類中定義的數據屬性,屬於類的屬性。在 __init__() 中定義的是實例的數據屬性。無論是類的仍是實例的數據屬性,都只能使用點(.)方式訪問,其餘方式訪問皆爲普通變量

country = '中國》》》'
class Chinese:
    country = '中國'
    def __init__(self, name):
        self.name = name
        print('---->', country)         # 這裏訪問的就是普通變量,沒有選擇類的屬性 country = '中國',而是選擇了普通變量 country = '中國》》》'
    def eat(self):
        pass
p1 = Chinese('林雷')
# p1.country = '》》》中國'          # 其實這裏是增長實例的數據屬性
# print('類的數據屬性:', Chinese.country)     類的數據屬性: 中國
# print('實例的數據屬性:', p1.country)        實例的數據屬性: 》》》中國

print(Chinese.country)
print(p1.country)

----> 中國》》》
中國
中國

總結

  • 類的數據屬性是全部對象均可以共享的,函數屬性綁定給對象使用
  • 實例(對象)只有數據屬性,沒有函數屬性,調用函數屬性是對類的函數屬性的引用

3.5 靜態屬性

咱們知道類既有數據屬性也有函數屬性,而實例只有數據屬性。在使用實例調用類的函數屬性時,總要帶上函數後面的括號才能運行,否則調用的是函數的內存地址:

class Room:
    def __init__(self, name, owner, width, heigh, length):
        self.name = name
        self.owner = owner
        self.width = width
        self.heigh = heigh
        self.length = length
    
    def calc_area(self):
        print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length))

r1 = Room('臥室', 'rose', 10, 3.5, 16)
r1.calc_area    # <bound method Room.calc_area of <__main__.Room object at 0x0000000004FC9EB8>>

問題:那麼有什麼方法能夠向調用數據屬性同樣調用函數屬性呢?

類中提供了 @property 關鍵字,它的做用是在調用函數屬性時,能夠像調用數據屬性同樣並容許該函數,相似於函數中的裝飾器。

class Room:
    def __init__(self, name, owner, width, heigh, length):
        self.name = name
        self.owner = owner
        self.width = width
        self.heigh = heigh
        self.length = length
        
    @property    
    def calc_area(self):
        print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length))
#         return self.width * self.length
r1 = Room('臥室', 'rose', 10, 3.5, 16)
# r1.calc_area()
r1.calc_area        # 省略括號

只需在須要被裝飾的函數上面,加上 @property 便可。須要注意的是:被裝飾的函數不能有除 self 參數之外的參數

3.6 類方法

若是不進行實例化,直接用類名調用類的函數屬性,會報缺乏位置參數:

class Room:
    def __init__(self, name, owner, width, heigh, length):
        self.name = name
        self.owner = owner
        self.width = width
        self.heigh = heigh
        self.length = length
    @property
    def calc_area(self):
        print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length))
        
    def open_door(self):
        print('打開%s的門' % self.name)

Room.open_door()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-60-9b6abeb723cc> in <module>()
     13         print('打開%s的門' % self.name)
     14 
---> 15 Room.open()

TypeError: open_door() missing 1 required positional argument: 'self'

傳入 self 作參數,也會報 name 'self' is not defined。這樣由於 self 是特殊意義的,是實例自己,只有在實例化時纔有效。爲了解決這種問題,咱們引入了 @classmethod ,:

class Room:
    size = 'small'
    def __init__(self, name, owner, width, heigh, length):
        self.name = name
        self.owner = owner
        self.width = width
        self.heigh = heigh
        self.length = length
    @property
    def calc_area(self):
        print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length))
        
    @classmethod
    def open_door(cls, color):
        print(cls)      # cls 至關於 類自己,在定義類方法時自動補全
        print('請打開那扇 %s 的 %s門' % (color, cls.size))

Room.open_door('red')

---------------------------------------------------
<class '__main__.Room'>
請打開那扇 red 的 small門

能夠看出一旦類的某個函數屬性被定義爲類方法時,能夠直接使用類名調用類的函數屬性,而不用擔憂參數的問題。在定義類方法時,Python 會自動補全一個名叫 cls 的參數,能夠看出它就是類自己,在調用類函數屬性時不須要手動傳入,自己會自動傳入,與 self 相似。

3.7 靜態方法

若是有想在類中定義一個函數,要求該函數的參數與類、實例都無關(沒有綁定),咱們可使用靜態方法 @staticmethod

class Room:
    size = 'small'
    def __init__(self, name, owner, width, heigh, length):
        self.name = name
        self.owner = owner
        self.width = width
        self.heigh = heigh
        self.length = length
   
    @staticmethod
    def close_door(position, color):
        print('請關閉 %s 的 %s 小門' % (position, color))
        
r1 = Room('臥室', 'rose', 10, 20, 30)
Room.close_door('廚房', 'black')
r1.close_door('廁所', 'white')

被定義爲靜態方法的函數屬性,名義上歸類管理,但不能使用類變量和實例變量,只是類的工具包。

3.8 總結

  • 靜態屬性:可使用類、實例的數據屬性,不能累調用(綁定實例)
  • 類方法:不能使用實例的數據屬性,能夠用類、實例調用(與類綁定)
  • 靜態方法:不能使用類、實例的數據屬性,能夠用類、實例調用(與類、實例都不綁定)
  • 常規函數:不能使用類、實例的數據屬性,不能用實例調用(與類、實例都不綁定)
class Room:
    size = 'small'
    def __init__(self, name, owner, width, heigh, length):
        self.name = name
        self.owner = owner
        self.width = width
        self.heigh = heigh
        self.length = length
    @property
    def calc_area(self):
        print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length))
        
    @classmethod
    def open_door(cls, color):
        #print(cls)
        print('請打開那扇 %s 的 %s門' % (color, cls.size))
        
    @staticmethod
    def close_door(position, color):
        print('請關閉 %s 的 %s 小門' % (position, color))
        
    def test(a, b):
        print(a, '-->', 'b')
        
r1 = Room('臥室', 'rose', 10, 20, 30) 
# 靜態屬性
Room.calc_area      # 不能調用
r1.calc_area        # rose 住的臥室,總面積:300

# 類方法
Room.open_door('white')     # 請打開那扇 white 的 small門
r1.open_door('white')       # 請打開那扇 white 的 small門

# 靜態方法
Room.close_door('臥室', 'black')      # 請關閉 臥室 的 black 小門
r1.close_door('臥室', 'black')        # 請關閉 臥室 的 black 小門

# 常規函數
Room.test(1, 2)         # 1 --> b
r1.test(1, 2)    # 不能調用

4. 組合

組合就是把一個類實例化傳入另外一個類中使用。類與類之間相互關聯,而且小的類組成大的類,這個時候能夠用組合。

class Turtle:
    def __init__(self, x):
        self.x = x
class Fish:
    def __init__(self, x):
        self.x = x
        
class Pool:
    def __init__(self, x, y):
        self.tutle = Turtle(x)
        self.fish = Fish(y)
        
    def print_num(self):
        print('水池裏總共有%s只烏龜,%s條魚' % (self.tutle.x, self.fish.x))
        
p1 = Pool(2, 3)
p1.print_num()
水池裏總共有2只烏龜,3條魚

選課系統:

class School:
    def __init__(self, name, addr):
        self.name = name
        self.addr = addr
        
    def zhao_sheng(self):
        print('%s正在招生' % self.name)

class Course:
    def __init__(self, name, price, period, school):
        self.name = name
        self.price = price
        self.period = period
        self.school = school
        
class Teacher:
    def __init__(self, name, gender, age):
        self.name = name
        self.gender = gender
        self.age = age
        
        
s1 = School('oldboy', '北京')
s2 = School('oldboy', '南京')
s3 = School('oldboy', '東京')

t1 = Teacher('林老師', '男', '45')
t2 = Teacher('李老師', '女', '28')
t3 = Teacher('孫老師', '女', '32')


c1 = Course('linux', '10000', '1', s1)
print(c1.__dict__)

msg = """
1 老男孩 北京校區
2 老男孩 南京校區
3 老男孩 東京校區
4 林老師
5 李老師
6 孫老師
"""

while True:
    print(msg)
    menu = {
        '1': s1,
        '2': s2,
        '3': s3,
        '4': t1,
        '5': t2,
        '6': t3
    }
    
    choice = input('請選擇校區:')
    choice_teacher = input('請選擇老師:')
    teacher_obj = menu[choice_teacher]
    school_obj = menu[choice]
    name = input('課程名:')
    price = input('課程費用:')
    period = input('課程週期:')
    
    new_course = Course(name, price, period, school_obj)  # school_obj = s1  
    print('課程【%s】屬於【%s】學校' % (new_course.name, new_course.school.name)) 
    # new_courese.school = s1    s1.name = name = oldboy
    
    new_teacher = Course(name, price, period, teacher_obj)
    print('%s由%s教' % (new_teacher.name, new_teacher.school.name))

5. 面向對象三大特性

繼承、多肽、封裝

5.1 繼承

類的繼承與現實生活中的父、子繼承關係同樣。被繼承的類稱爲父類、基類或超類,繼承者稱爲子類。Python 繼承分爲:單繼承和多繼承(繼承多個類)。

class Parent1:
    pass
class Parent2:
    pass
class Sub1(Parent1):
    pass
class Sub2(parent1, Parent2):
    pass
class Dad:
    """父類"""
    money = 100000
    def __init__(self, name):
        print('爸爸')
        self.name = name
    
    def hit_son(self):
        print('%s正在打兒子' % self.name)
        
class Son(Dad):
    pass
    
print(Son.money)   100000
Son.hit_son()     # hit_son() missing 1 required positional argument: 'self'
# hit_son() 有一個位置參數 self,使用類名調用,不能傳入 self,只能用實例調用

print(Dad.__dict__)
print(Son.__dict__)

s1 = Son('tom')   # 爸爸
print(s1.money)    # 100000
s1.hit_son()      # tom正在打兒子
print(s1.__dict__)    # {'name': 'tom'}

子類繼承父類的全部屬性,調用父類的方法只能用實例調用。

class Son(Dad):
    money = 9000
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def hit_son(self):
        print('來自子類')
        
s2 = Son('rose', 18)
print(s2.money)
print(Dad.money)
s2.hit_son()
9000
100000
來自子類

若是子類中定義與父類同名的方法或屬性,不會覆蓋父類對應的方法或屬性,只是優先調用本身類自己的方法或屬性。

5.1.1 何時用繼承

  • 當類之間有顯著的不一樣,而且較小的類是較大類所需組件時,用組合比較好
    • 如:一個機器人能夠是一個類,它的手臂、腿和身體等也均可以是一個類,它們互不相關
  • 當類之間有不少相同功能,提取這些共同的功能作成基類,用繼承比較好
    • 如:貓、狗都要吃喝拉撒,同時也有本身獨特的聲音,提取它們的共性做爲基類
class Cat:
    def 喵喵叫(self):
        pass
    def 吃(self):
        pass
    def 喝(self):
        pass
    
class Dog:
    def 汪汪叫(self):
        pass
    def 吃(self):
        pass
    def 喝(self):
        pass

狗和貓都屬於動物,提取它們的共性,作成基類:

class Animal:
    def 吃(self):
        pass
    def 喝(self):
        pass

class Cat(Animal):
    def 喵喵叫(self):      # 派生
        pass
class Dog(Animal):
    def 汪汪叫(self):      # 派生
        pass

喵喵叫、汪汪叫是派生而來的,吃喝拉撒是繼承而來

派生(衍生):子類在繼承父類的基礎上衍生出新的屬性,如:Cat 的喵喵叫、Dog 的汪汪叫。也能夠是子類定義與父類重名的屬性,子類也稱爲派生類。

5.1.2 繼承的兩種含義

  • 繼承基類的方法,而且作出本身的改變(派生)或拓展(代碼重用)
    • 這種方法應儘可能少用,由於子類於基類出現強耦合,容易出現未知錯誤。
  • 聲明某個子類兼容於某基類,定義一個接口類,子類繼承接口類,而且實現接口中定義的方法。—— 接口繼承

5.1.3 接口繼承與歸一化

接口繼承:

抽象:即提取相似部分

接口:基類的函數屬性,只提供函數名,不實現具體功能

定義一個基類,在基類中利用裝飾器 (@abc.abstractclassmethod)將本身的函數屬性定義成接口函數,在子類中必須實現該接口函數,不然沒法實例化。

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

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

import abc
class All_file(metaclass=abc.ABCMeta):   # 基類
    @abc.abstractmethod           # 裝飾器定義兩個接口函數 read、write
    def read(self):            # 那麼子類必須實現這兩個函數,否沒法實例化
        pass
    
    @abc.abstractmethod
    def write(self):
        pass
    
class Disk(All_file):
    def read(self):
        print('disk read')
    def write(self):
        print('disk write')
    
class Memory(All_file):
    def read(self):
        print('memory read')
        
d1 = Disk()
d1.read()
d1.write()

m1 = Memory()
m1.read()
disk read
disk write
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-53d88a516ad1> in <module>()
     23 d1.write()
     24 
---> 25 m1 = Memory()
     26 m1.read()

TypeError: Can't instantiate abstract class Memory with abstract methods write

能夠看出 Disk 子類實現了基類的接口函數,能夠正常實例化並調用函數屬性。而 Memory 子類,沒有實現 Write() ,實例化失敗。

5.1.4 繼承順序

Python 能夠繼承多個類,c#、java 只能繼承一個。若是繼承多個,那麼其尋找方式有兩種:深度優先和廣度優先

  • 當類是經典類時,多繼承按照深度優先查找
  • 當類是新式類時(Python 3.x 都是新式類),多繼承按照廣度優先查找

問題:Python 是如何實現繼承的?按照什麼樣的方式去查找的呢?

對於定義的每個類,Python 會計算出一個方法解析順序(MRO)元組,它是一個簡單的全部基類的線性順序元組,即一旦實現了繼承。就已經計算好查找順序,並存儲在元組中。查看方式:

Class.__mro__       # F.__mro__

爲了實現繼承,Python 會在 MRO 元組上從左到右開始查找基類,直至找到第一個匹配這個屬性的類位置。

而這個 MRO 元組的構造是經過一個 C3 線性化算法來實現的。實際上就是合併全部父類的 MRO 元組並遵循以下三條準則:

  • 子類會優先父類被檢查:即子類與父類有一樣的方法時,優先檢查子類
  • 多個父類會根據它們在列表中的順序被檢查:F(D, E) D 要優先 E
  • 若是對下一個類存在兩個合法的選擇,選擇第一個父類
class A(object):
    def test(self):
        print('A')

class B(A):
    def test(self):
        print('B')
        
class C(A):
    def test(self):
        print('C')
        
class D(B):
    def test(self):
        print('E')
        
class E(C):
    def test(self):
        print('E')
        
class F(D, E):
    def test(self):
        print('F')
        
f1 = F()
f1.test()
print(F.__mro__)   # F --> D --> B --> E --> C --> A --> object
F
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

在 Python 2.x 中有新式類和經典類之分,新式類與 Python3.x 的查找順序同樣。而經典類的查找順序爲:F --> D --> B --> A --> E --> C

5.1.5 子類中調用父類的方法

若是想在子類中調用父類的方法(函數屬性),就須要重寫父類方法,不然會報錯:

class Vehicle:
    """交通工具"""
    Country = 'China'
    def __init__(self, name, speed, load, power):
        """名字、速度、負載、能量"""
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power
        
    def run(self):
        print('%s今天開通~' % self.name)
        
class Subway(Vehicle):
    """地鐵"""
    def __init__(self, line):
        """名字、速度、負載、能量、幾號線"""
        self.line = line
    def test():
        pass
        
line1 = Subway(1)
line1.run()                 # AttributeError: 'Subway' object has no attribute 'name'

子類 Subway 中調用父類 Vehicle 的 run 方法,報 AttributeError: 'Subway' object has no attribute 'name'。顯然調用沒有成功。

那麼咱們能夠重寫父類的方法:

class Subway(Vehicle):
    """地鐵"""
    def __init__(self, name, speed, load, power, line):     # 調用父類的屬性
        """名字、速度、負載、能量、幾號線"""
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power
        self.line = line
        
    def run(self):
        print('%s今天開通~' % self.name)
        print('%s%s號線開通啦~' % (self.name, self.line))
        
line1 = Subway('長沙地鐵', '200km/h', 1000, 'electric', 1)
line1.run()         # 調用與父類同名的方法

------------------------------
長沙地鐵今天開通~
長沙地鐵1號線開通啦~

當子類中與父類方法同名時,優先調用子類自己的。可是若是也想實現父類中的功能,就須要在子類相應的方法中重寫,如有幾十上百行代碼,重寫就顯得有點麻煩。

Python 給咱們提供了兩種解決方法:

  • 調用未綁定的父類方法
  • 使用 super 函數

調用未綁定的父類方法

只需在子類中須要調用父類的方法中,加上這樣一行代碼便可:

類名.__init__(self, x, y...)          # 調用父類中的變量
類名.test(self)               # 調用父類方法

具體示例以下:

class Subway(Vehicle):
    """地鐵"""
    def __init__(self, name, speed, load, power, line):
        """名字、速度、負載、能量、幾號線"""
        Vehicle.__init__(self, name, speed, load, power)    # 調用幾個變量就添加幾個
        self.line = line
        
    def run(self):
        Vehicle.run(self)
        print('%s%s號線開通啦~' % (self.name, self.line))
        
line1 = Subway('長沙地鐵', '200km/h', 1000, 'electric', 1)
line1.run()

----------------------------------------
長沙地鐵今天開通~
長沙地鐵1號線開通啦~

5.1.6 super 函數

在子類中調用父類方法,使用上面的方法,有個缺點:一旦父類名字變化,後面調用邏輯就須要大量更改被繼承的方法。Python 還提供了另外一種方法:super()函數。

它能夠自動找到父類的方法,而且自動傳入 self 參數。無需關心父類的名字,不須要去大量修改被繼承的方法:

super().meth([args])
class Subway(Vehicle):
    """地鐵"""
    def __init__(self, name, speed, load, power, line):
        """名字、速度、負載、能量、幾號線"""
        super().__init__(name, speed, load, power)
        self.line = line
        
    def run(self):
        super.run()
        print('%s%s號線開通啦~' % (self.name, self.line))
        
line1 = Subway('長沙地鐵', '200km/h', 1000, 'electric', 1)
line1.run()

----------------------------------------
長沙地鐵今天開通~
長沙地鐵1號線開通啦~

5.2 多肽

由不一樣的類實例化獲得的對象,調用同一個方法,執行的邏輯不一樣。對象如何經過他們共同的屬性和動做來操做及訪問,而不考慮他們具體的類。

#_*_coding:utf-8_*_
__author__ = 'Hubery_Jun'

class H2O:
    def __init__(self, name, temperature):
        self.name = name
        self.temperature = temperature
    def turn_ice(self):
        if self.temperature < 0:
            print('[%s]水溫過低,結冰了' % self.name)
        elif self.temperature > 0 and self.temperature < 100:
            print('[%s]液態化成水' % self.name)
        elif self.temperature > 100:
            print('[%s]溫度過高變成水蒸氣了' % self.name)
            
class Water(H2O):
    pass

class Ice(H2O):
    pass

class Steam(H2O):
    pass

w1 = Water('水', 25)
i1 = Water('冰', -25)
s1 = Water('水蒸氣', 3000)

# w1.turn_ice()
# i1.turn_ice()
# s1.turn_ice()

def func(obj):
    obj.turn_ice()
func(w1)
func(i1)
func(s1)
[水]液態化成水
[冰]水溫過低,結冰了
[水蒸氣]溫度過高變成水蒸氣了

對象 w一、i一、s1 由不一樣的類實例而來,可是它們調用同一個方法 turn_ice()。咱們能夠定義一個函數 func,專門用來處理 turn_ice() 方法,不須要關心類內部邏輯,只需考慮對象的類型,這就是多肽。

5.3 封裝

5.3.1 公有私有數據屬性

通常面向對象編程語言都有公有私有數據屬性,Java、C++ 使用 public 和 private 關鍵字來區分。而 Python 默認對象的屬性和方法都是公開的(即公有的),能夠直接經過點(.)來訪問。

class Person:
    name = 'rose'
    def __init__(self, age, gender):
        self.age = age
        self.gender = gender
    
p1 = Person(18, 'female')
print(p1.name)

----------------
rose

爲了實現相似私有變量的特性,Python 有一種約束,能夠對要私有化的變量,在其前面加一條下劃線(_)。可是從外部也能夠訪問,這是一種約束,約束看到這種變量就不去調用:

_name = 'rose'

Python 內部採用一種叫 name mangling(名字改編) 的技術,只**需在變量名前加雙下劃線便可(__),那麼這個變量就變成了私有變量**:

__name = 'rose'
print(p1.__name)        # AttributeError: 'Person' object has no attribute '_name'

其實 Python 目前的私有機制是僞私有,在外部仍是能夠經過 _類名__變量名 的方式去訪問私有變量:

print(p1._Person__rose())
print(Person.__dict__)
{'__module__': '__main__', '_Person__name': 'rose', '__init__': <function Person.__init__ at 0x00000000053D6598>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
rose

能夠發如今 Python 內部,將原有的變量名改變了名字:(__rose 變成了 _Person__name

5.3.2 封裝的三個層面

  • 類自己就是對數據和函數的一種封裝。
  • 另外一種封裝類中定義私有屬性,內部能訪問,外部不能訪問。
  • 明確區份內外,內部的實現邏輯,外部沒法知曉。而且爲封裝到內部的邏輯提供一個訪問接口給外部使用(這纔是真正的封裝)
class Person:
    name = 'rose'
    def __init__(self, age, gender):
        self.__age = age
        self.__gender = gender
        
    # 接口函數    
    def interface_func(self):
        return self.__age, self.__gender
        
    
p1 = Person(18, 'female')
print(p1.interface_func())
--------------------------

(18, 'female')

對於類中已經封裝的方法,只能在內部訪問,外部要想訪問。咱們能夠定義一個接口函數,而後調用接口函數間接使用封裝的方法。可是在實際開發中應謹慎使用接口函數,濫用接口函數就會致使類 千瘡百孔

5.4 總結

面向對象是一種更高級的結構化編程方式,其優勢有兩點:

  • 經過封裝,明確內外。外部調用無需知道內部邏輯。
  • 經過繼承和多肽在語言層面支持歸一化設計

6. 反射

反射是指程序能夠訪問、檢查和修改它自己狀態或行爲的一種能力(自省)。Python 內部實現自省的四個函數:hasattr()、getattr()、setattr()、delattr()

6.1.1 實現反射的四個函數

class Animal:
    def __init__(self, name, color):
        self.name = name
        self.color = color

    def eat(self):
        print('【%s】正則吃東西' % self.name)

a1 = Animal('dog', 'black')

1. hasattr(object, name)

判斷一個對象中是否有指定的屬性,第一個參數爲對象,第二個爲屬性名。

print(hasattr(a1, 'name'))      # True
print(hasattr(a1, 'eat'))       # True
print(hasattr(a1, 'edfa'))      # False

2. getattr(object, name[,default])

返回對象的指定屬性,若屬性不存在且未指定默認值,拋出 AttributeError 異常。指定默認值返回默認值。

print(getattr(a1, 'name'))      # dog
print(getattr(a1, 'eat'))       # <bound method Animal.eat of <__main__.Animal object at 0x00000000025ACEF0>>
#print(getattr(a1, 'edfa'))      # AttributeError: 'Animal' object has no attribute 'edfa'
print(getattr(a1, 'edfa', '沒有找到'))      # 沒有找到

3. setattr(object, name, value)

設置對象中指定屬性的值,如屬性不存在則新增,存在則修改。

# a1.name = 'cat'       # 常規修改方法
setattr(a1, 'name', 'cat')  # 修改
setattr(a1, 'age', 1)   # 新增

4. delattr(object, name)

刪除對象指定的屬性,屬性不存在拋出 AttributeError 異常。

# a1.name       # 常規修改方法
delattr(a1, 'name')

6.1.2 反射的應用場景

反射的有兩大好處:一是能夠實現可插拔機制,二是動態導入模塊(基於反射當前模塊成員)

實現可插拔機制

好比說有兩個程序員,分別負責開發兩個模塊ftp_client.py、user.pyuser.py 依賴 ftp_client.py。若哪天負責開發 ftp_client.py 的程序員忽然去有事了,爲了避免影響總體進度,咱們能夠利用反射判斷 ftp_client.py 是否實現了某個功能,即便沒實現也不會報錯:

# ftp_client.py
class FtpClient:
    'ftp 客戶端,具體功能還沒有實現'
    def __init__(self, addr):
        print('正在鏈接服務器【%s】' % addr)
        self.addr = addr

利用 hasattr() 判斷 FtpClient 類 中是否有 put 方法,如有則獲取,不然繼續實現其餘功能邏輯:

# user.py
from ftp_client import FtpClient

f1 = FtpClient('8.8.8.8')
if hasattr(f1, 'put'):
    func_put = getattr(f1, 'put')
    func_put()
else:
    print('put 功能還沒有實現')
    print('其餘功能邏輯')

動態導入模塊

若導入的模塊是字符串形式,咱們以前能夠採用 __import__() 函數導入。

包 h 下有個 a1 模塊,要想在另外一個模塊中調用 a1 中的 test() 函數:

r = __import__('h.a1')
r.a1.test()

另外一種方式是利用 importlib 模塊,動態導入模塊:

import importlib
r = importlib.import_module('h.a1')
r.test()

6.1.3 __getattr__、__setarrr__、__delattr__

__getattr__
當獲取不存在的屬性時觸發:

class Foo:
    x = 1
    def __init__(self, y):
        self.y = y
    
    def __getattr__(self, item):
        print('屬性【%s】不存在~' % item)

f1 = Foo(10)
print(f1.y)     # 10
print(f1.s)     # 屬性【s】不存在~、None

對象 f1 傳給 self,y 或 s 傳給 item。

__setattr__
設置屬性時觸發

def __setattr__(self, key, value):
    print('正在設置屬性~')
    # self.key = value    
    # RecursionError: maximum recursion depth exceeded in comparison(超出最大遞歸深度)
    self.__dict__[key] = value

f1 = Foo(10)
f1.m = 20
print(f1.__dict__)

# 正在設置屬性~
# 正在設置屬性~
# {'y': 10, 'm': 20}

__delattr__
刪除屬性時觸發

def __delattr__(self, item):
    print('正在刪除~')

f1 = Foo(10)
del f1.y
del f1.x

# 正在刪除~
# 正在刪除~

7. 二次加工標準類型(包裝)

Python 提供了不少內置的標準數據類型,以及內置方法。在不少場景下咱們須要基於標準數據類型來定製咱們本身的類型,新增或改寫方法。咱們能夠用繼承/派生來對標準類型進行二次加工:
需求:
基於 list 新增一個方法用於返回列表中間元素,修改 list 內置的 append 方法,要求只有當新增的元素是字符串時才能添加進去。

class List(list):
    """繼承 list 全部的屬性,也派生本身的,好比 append,mid"""
    def append(self, str_obj):
        """派生本身的 append:類型檢查"""
        if isinstance(str_obj, str):
            super().append(str_obj)   # 調用父類的 append()方法,調用本身的會無限遞歸直至溢出
        else:
            print('必須是字符串類型')

    
    def mid(self):
        """新增屬性"""
        middle = int(len(self)/2)
        return '列表%s,中間元素是【%s】' % (self, self[middle])
      
# 新增屬性
#  List 繼承 list,那麼 List('hello')至關於 list('hello') ,self 爲實例自己,那麼 self = list(‘heelo')’)      
l1 = List('hello')   # 列表['h', 'e', 'l', 'l', 'o'],中間元素是【l】
l1.mid()

# # 派生本身的 append
# l2 = List('hello')
# l2.append('2')
# print(l2)         # ['h', 'e', 'l', 'l', 'o', '2']
# l2.append(3)      # 必須是字符串類型

受權
受權是包裝的另外一特性,它與包裝的區別在於:

  • 包裝: 一般是對已存在的類型進行定製,可新建、修改和刪除原有類型的功能,其餘則不變。(繼承和派生)
  • 受權: 對於已存在的功能受權給對象的默認屬性,更新的功能交給類的某些方法來處理。 (組合)

受權的關鍵點在於覆蓋 __getattr__

需求: 模擬 open 函數的read、write 方法,要求 read 方法交給類方法處理,write 方法受權給對象默認屬性:

read 方法

class FileHandle:
    """模擬 open 函數"""
    def __init__(self, file, mode='r', encoding='utf-8'):
        self.file = open(file, mode, encoding=encoding)   # 文件句柄或文件對象 <_io.TextIOWrapper name='a.txt' mode='r+' encoding='utf-8'>

    def __getattr__(self, item):
#         print('不存在')
        print(self, item)    # self:f1,item:'read'
        
        return getattr(self.file, item)  # 在文件對象中查找 字符串 read
        

f1 = FileHandle('a.txt', 'r+')
print(f1.read())    # read = f1.read, print(read())
# f1.read 不存在則會觸發 __getattr__

--------------
# aaaaa

類 FileHandle 的構造函數中以組合的形式,將參數傳給 open() 函數。得到文件句柄 self.file,利用文件句柄咱們能夠對文件進行讀寫操做。
類中不存在 read 方法,所以 f1.read 會觸發 __getattr__() ,而後利用 getattr 得到 open 函數的 read 屬性(或方法),最後返回。

write 方法

import time
def write(self, line):
    t = time.strftime('%Y-%m-%d %X')    # 獲取當前字符串時間
    time.sleep(0.1)
    self.file.write('%s %s' % (line, t))
    # 把 write 受權給 FileHandle

f1 = FileHandle('a.txt', 'r+')
f1.write('第一行\n')
f1.write('第二行\n')
f1.write('第三行\n')
f1.seek(0)  # seek 不存在,觸發 __getattr__
print(f1.read())
相關文章
相關標籤/搜索