python筆記:詳細介紹python中的面向對象!

詳細介紹python中的面向對象!

一、python面向對象知識補充!

1.1、介紹一下對象

首先大家知道把亂七八糟的數據扔進列表裏面,這是一種封裝,是數據層面的封裝;把常用的代碼打包成一個函數,這也是一種封裝,是語句層面的封裝;下面介紹的對象,也是一種封裝的思想,不過這種思想顯然更加先進一些:對象來源是模擬真實世界,把數據和代碼都封裝在一起。
打個比方,烏龜就是真實世界的一個對象,那麼通常應該如何來描述這個對象呢?是不是把它分爲2部分來說呢?

  1. 可以從靜態的特徵來描述,例如,綠色的、有4條腿,10kg重,有外殼,還有個大嘴巴,這是靜態一方面的描述。
  2. 還可以從動態的行爲來描述,例如說它會爬,還會吃東西,還會睡覺等等。這些都是行爲方面的描述。

1.2、對象=屬性+方法

python中的對象也是如此,一個對象的特徵稱爲屬性,一個對象的行爲叫做方法

  • 如果把烏龜寫成代碼,如下:
class Turtle:
    #python中類名字約定以大寫字母開頭
    #特徵的描述稱爲屬性,在代碼層面來看就是變量
    color = 'green'
    weight = 10
    legs = 4
    shell = True
    
    #方法實際就是函數,通過調用這些函數來完成某些工作
    def climb(selfs):
        print('我正在努力爬!')
    def run(self):
        print('我正在往前跑!')
    def eat(self):
        print('我正在吃東西!')
    def sleep(self):
        print('我要睡覺了親!')

以上代碼定義了對象的特徵(屬性)和行爲(方法),但是還不是一個完整的對象,將定義的這些稱爲類(Class),需要使用類來創建一個真正的對象,這個對象叫做這個類的一個實例(Instance),也叫實例對象(Instance Object)。
可能還是不太理解,可以這麼思考:就好比一個工廠的流水線要生產一系列的玩具,是不是需要先做出這個玩具的模具,然後根據這個模具再進行批量生產,纔得到真正的玩具?
再舉例一個:蓋房子,是不是要現有個圖紙,但圖紙並不是真正的房子。要根據圖紙用鋼筋水泥建造出來的房子才能住人,另外根據一張圖紙就能蓋出很多的房子。因此:類用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。

  • 創建一個對象,也叫類的實例化,如下:
tt = Turtle()

注意:類名後邊跟着小括號,這跟調用函數是一樣的,所以Python中,類名約定用大寫字母開頭,函數用小寫字母開頭,這樣更容易區分。另外賦值操作並不是必需的,但是如果沒有把創建好的實例對象賦值給一個變量,那這個對象就沒辦法使用,因爲沒有任何引用指向這個實例,最終會被python的垃圾收集機制自動回收。

那如果要調用對象裏面的方法,使用操作符(.)即可,如下:

tt.climb()
tt.sleep()
tt.eat()
  • 綜合演示:
class Turtle:
    #python中類名字約定以大寫字母開頭
    #特徵的描述稱爲屬性,在代碼層面來看就是變量
    color = 'green'
    weight = 10
    legs = 4
    shell = True

    #方法實際就是函數,通過調用這些函數來完成某些工作
    def climb(selfs):
        print('我正在努力爬!')
    def run(self):
        print('我正在往前跑!')
    def eat(self):
        print('我正在吃東西!')
    def sleep(self):
        print('我要睡覺了親!')

tt = Turtle()
tt.climb()
tt.sleep()
tt.eat()
  • 運行結果:
我正在努力爬!
我要睡覺了親!
我正在吃東西!

Process finished with exit code 0

1.3、面向對象編程

1.3.1、self是什麼

可能我們會發現對象的方法都會有一個self的參數,那麼這個參數到底是什麼東西呢?如果之前接觸過其它面向對象的編程語言,例如c++,那麼很容易對號入座,python中的self就是相當於c++中的this指針。
self到底是什麼東西呢?解釋一下:如果把類比作一張圖紙,那麼類實例化的對象纔是真正的房子,根據一張圖紙可以設計出成千上萬的房子,它們長得差不多,但他們都有不同的主人。每個人只能回到自己的家中,所以self這裏就是相當於每個房子的門牌號,有了self,你就可輕鬆的找到自己的房子。
python的self參數也是同一個道理,由同一個類可以生成無數的對象,當一個對象的方法被調用的時候,對象會將自身的引用作爲第一個參數傳給該方法,那麼python就知道操作哪個對象的方法了。

  • 例子如下:
class Ball:
    def setName(self, name):
        self.name = name
    def kick(self):
        print('我叫%s,誰在踢我?'%self.name)


a = Ball()
a.setName('zhang')
b = Ball()
b.setName('wang')
c = Ball()
c.setName('li')
a.kick()
b.kick()
c.kick()
  • 運行結果如下:
我叫zhang,誰在踢我?
我叫wang,誰在踢我?
我叫li,誰在踢我?

Process finished with exit code 0
1.3.2、初始化方法

python對象天生有一些神奇的方法,它們是面向對象的python的一切,它們是可以給你的類增加魔力的特殊方法,如果你的對象實現了這些方法中的某一個,那麼這個方法就會在特殊的情況下被python所調用,而這一切都是自動發生的。
python中的這些方法,總是被雙下劃線所包圍,下面介紹一個最基本的特殊方法:_ _ init _ ()方法。通常 _ init _ ()方法稱爲構造方法,它的魔力體現在只要實例化一個對象,這個方法就會在創建對象的時候自動調用(在c++裏你也可以看到類似的東西,叫“構造函數”)。其實實例化對象可以傳入參數的,這些參數會自動的傳入方法 _ init _ _()中,可以通過重寫這個方法來自定義對象的初始化操作。

  • 舉個例子:
class Potato:
    def __init__(self, name):
        self.name = name

    def kick(self):
        print('我叫%s,誰在踢我'%self.name)

p = Potato('devin zhang')
p.kick()
  • 運行結果:
我叫devin zhang,誰在踢我

Process finished with exit code 0
1.3.3、共有和私有

一般面向對象的編程語言都會區分共有和私有的數據類型,像c++和java中使用public和private關鍵字,用於聲明數據是公有的還是私有的,但是在python中並沒有用類似的關鍵字來修飾。
難道python中的所有的東西都是透明的?也不全是,默認上對象的屬性和方法都是公開的,可以通過操作符(.)進行訪問:

class Person:
    name = 'devin zhang'

p = Person()
print(p.name)
  • 運行結果:
devin zhang

Process finished with exit code 0

python中爲了實現私有變量的特徵,python內部採用了一種叫做name mangling(名字改編)的技術,在python中定義私有變量只需要在變量名前加“_ _”兩個下劃線,那麼這個函數或變量就會成爲私有的了:

class Person:
    __name = 'devin zhang'

p = Person()
print(p.__name)
  • 運行結果出現下面的錯誤:
Traceback (most recent call last):
  File "/home/zhangkf/tf1/TF/test.py", line 5, in <module>
    print(p.__name)
AttributeError: 'Person' object has no attribute '__name'

Process finished with exit code 1
  • 這樣在外部將變量名“隱藏”起來了,理論上如果要訪問,就需要從內部進行:
class Person:
    def __init__(self, name):
        self.__name = name
    
    def getName(self):
        return self.__name

p = Person('devin zhang')
print(p.getName())
  • 運行結果:
devin zhang

Process finished with exit code 0

但是認真琢磨一下這個技術的名字name mangling(名字改編),那就不難發現其實python只是動了一下手腳,把雙下橫線開頭的變量進行了該名而已。實際上在外部你使用“_ _ 類名 _ _變量名”即可訪問雙下劃線開頭的私有變量了:

class Person:
    def __init__(self, name):
        self.__name = name

    def getName(self):
        return self.__name

p = Person('devin zhang')
print(p.getName())
print(p._Person__name)
  • 運行結果:
devin zhang
devin zhang

Process finished with exit code 0

注意:python目前的私有機制其實是僞私有,python的類是沒有權限控制的,所有的變量都可以被外部調用的。

1.4、繼承

假如現在需要擴展一款遊戲,對魚類進行細分,有金魚(Goldfish)、鯉魚(Carp)、三文魚(Salmon),還有鯊魚(Shark)。那麼我們就再思考一個問題:能不能不要每次都從頭到尾去重新定義一個新的魚類呢?因爲我們知道大部分魚的屬性和方法都是相似的,如果你有一種機制可以讓這些相似的東西自動傳遞,那就方便快捷多了。就是下面要講的繼承

  • 語法很簡單:
class 類名(被繼承的類)
	....

被繼承的類稱爲基類、父類、母類、超類;繼承者稱爲子類,一個子類可以繼承他的父類的任何屬性和方法。例如:

class Parent:
    def hello(self):
        print('正在調用父類的方法!')

class Child(Parent):
    pass

p = Parent()
print(p.hello())
c = Child()
print(c.hello())
  • 運行結果:
正在調用父類的方法!
正在調用父類的方法!

Process finished with exit code 0

需要注意的是: 如果子類中定義與父類同名的方法或者屬性,則會自動覆蓋父類對應的方法或屬性:

class Parent:
    def hello(self):
        print('正在調用父類的方法!')

class Child(Parent):
    def hello(self):
        print('正在調用子類的方法!')

p = Parent()
print(p.hello())
c = Child()
print(c.hello())
  • 運行結果:
正在調用父類的方法!
正在調用子類的方法!

Process finished with exit code 0
  • 現在嘗試寫一下剛纔提到的金魚、鯉魚、三文魚、還有鯊魚的例子:
import random as r

class Fish:
    def __init__(self):
        self.x = r.randint(0, 10)
        self.y = r.randint(0, 10)

    def move(self):
        # 這裏主要演示類的繼承機制,就不考慮檢查場景邊界和移動方向問題
        # 假設所有魚都是一路向西遊
        self.x -= 1
        print('我的位置是:', self.x, self.y)


class Goldfish(Fish):
    pass

class Carp(Fish):
    pass

class Salmon(Fish):
    pass

class Shark(Fish):
    def __init__(self):
        self.hugry = True

    def eat(self):
        if self.hugry:
            print('吃貨的夢想就是天天有肉吃!')
        else:
            print('吃撐了,吃不下了!')

fish = Fish()
print(fish.move())  #試試小魚能不能移動

goldfish = Goldfish()
print(goldfish.move())
print(goldfish.move())
print(goldfish.move())

shark = Shark()
shark.eat()
shark.move()

運行結果:

我的位置是: -1 9
None
我的位置是: 0 10
None
我的位置是: -1 10
None
我的位置是: -2 10
None
吃貨的夢想就是天天有肉吃!
Traceback (most recent call last):
  File "/home/zhangkf/tf1/TF/test.py", line 44, in <module>
    shark.move()
  File "/home/zhangkf/tf1/TF/test.py", line 11, in move
    self.x -= 1
AttributeError: 'Shark' object has no attribute 'x'

Process finished with exit code 1

奇怪!同樣是繼承Fish類,爲什麼金魚(goldfish)可以移動,而鯊魚(shark)一移動就會報錯呢?其實這裏拋出異常說的都很清楚了:Shark對象沒有x屬性。原因是因爲在Shark類中,重寫了構造方法 _ _ init _ _ 方法,但新的 _ _init _ _方法裏面沒有初始化鯊魚的x座標和y座標,因此調用基類Fish的 _ _ init _ _ 方法。

下面介紹兩種可以實現的技術:

  • 調用未綁定的父類方法。
  • 調用super函數。
1.4.1、調用未綁定的父類方法

調用未綁定的父類方法,聽起來有些高深,但大家參考下面的代碼就能領會了:

class Shark(Fish):
    def __init__(self):
        Fish.__init__(self)
        self.hugry = True

再運行發現鯊魚就可以成功移動了;這裏注意的是這個self並不是父類Fish的實例對象,而是子類Shark的實例對象,所以這裏說的未綁定是指並不需要綁定父類的實例對象,使用子類的實例對象替代即可。

  • 可能不是太理解,沒關係,這一點不重要!python中有一個更好的方案取代它,就是使用super函數。
1.4.2、使用super函數

super函數能夠幫我實現自動找到基類的方法,而且還爲我們傳入了self參數,這樣就不需要做這些事情了。

class Shark(Fish):
    def __init__(self):
        #Fish.__init__(self)
        super().__init__()
        self.hugry = True

運行能得到同樣的結果;super函數的超級之處在於你不需要明確給出任何基類的名字,它會自動幫你找出所有基類以及對應的方法。由於你不用給出基類的名字,這就意味着如果需要改變類的繼承關係,只要改變class語句裏的父類即可,而不必在大量代碼中修改所有被繼承的方法。

1.5、多重繼承

除此之外python還支持多繼承,就是可以同時繼承多個父類的屬性和方法:

class 類名(父類1,父類2,父類3,...)
  • 舉例如下:
class Base1:
    def foo1(self):
        print('我是devin,我在Base1中!')

class Base2:
    def foo2(self):
        print('我是wang,我在Base2中!')

class C(Base1, Base2):
    pass

c = C()
print(c.foo1())
print(c.foo2())
  • 運行結果:
我是devin,我在Base1中!
None
我是wang,我在Base2中!
None

Process finished with exit code 0

上面就是最基本的多重繼承語法。但是多重繼承其實很容易導致代碼混亂,所以當你不確定是否真的必須使用多重繼承的時候,請儘量不要使用它,因爲有些時候會出現不可預見的BUG。

1.6、組合

前邊剛剛介紹了繼承的概念,然後又介紹了多重繼承,經常看到一些博客大牛介紹到,不到必要的時候不使用多重繼承。假如現在遇到這樣一個問題:我們有了烏龜類、魚類,現在要定義一個類叫做水池,水池裏面有烏龜和魚。用多重繼承就顯得很奇怪,因爲水池和烏龜、魚不是同一個物種,那要怎樣才能把它們組合成一個水池的類呢?
在python中其實很簡單,直接把需要的類放進實例化就可以了,這就叫做組合:

class Turtle:
    def __init__(self, x):
        self.num = x


class Fish:
    def __init__(self, x):
        self.num = x

class Pool:
    def __init__(self, x, y):
        self.turtle = Turtle(x)
        self.fish = Fish(y)

    def print_num(self):
        print('水池裏總共有烏龜:%d只,小魚%d條!' %(self.turtle.num, self.fish.num))

pool = Pool(1, 10)
print(pool.print_num())
  • 運行結果如下:
水池裏總共有烏龜:1只,小魚10條!
None

Process finished with exit code 0

1.7、銷燬對象(垃圾收集)

Python自動刪除不需要的對象(內置類型或類實例)以釋放內存空間。 Python定期回收不再使用的內存塊的過程稱爲垃圾收集。
Python的垃圾收集器在程序執行期間運行,當對象的引用計數達到零時觸發。 對象的引用計數隨着指向它的別名數量而變化。
當對象的引用計數被分配一個新名稱或放置在容器(列表,元組或字典)中時,它的引用計數會增加。 當用del刪除對象的引用計數時,引用計數減少,其引用被重新分配,或者其引用超出範圍。 當對象的引用計數達到零時,Python會自動收集它。

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40>原文出自【易百教程】,商業轉載請聯繫作者獲得授權,非商業請保留原文鏈接:https://www.yiibai.com/python/python_classes_objects.html

通常情況下,垃圾回收器會銷燬孤立的實例並回收其空間。 但是,類可以實現調用析構函數的特殊方法_ _ del _ _(),該方法在實例即將被銷燬時被調用。
此方法可能用於清理實例使用的任何非內存資源。

  • 如下:這個_ _ del _ _()析構函數打印要被銷燬的實例的類名
class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __del__(self):
        class_name = self.__class__.__name__
        print('class_name', 'destroyed')

pt1 = Point()
pt2 = pt1
pt3 = pt1
print(id(pt1), id(pt2), id(pt3))  # prints the ids of the obejcts
  • 運行結果:
1446780765912 1446780765912 1446780765912
class_name destroyed

Process finished with exit code 0

注意: 理想情況下,應該在單獨的文件中定義類,然後使用import語句將其導入主程序文件。

在上面的例子中,假定Point類的定義包含在point.py中,並且其中沒有其他可執行代碼。

import point
p1 = point.Point()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章