《Python 基礎篇》六:物件導向

ACatSmiling發表於2024-09-29

Author: ACatSmiling

Since: 2024-09-27

什麼是物件

物件:是記憶體中專門用來儲存資料的一塊區域。

物件中可以存放各種資料,比如:數字、布林值、程式碼。

物件由三部分組成:

  • 物件的標識(id)
  • 物件的型別(type)
  • 物件的值(value)

物件導向(oop)

Python 是一門物件導向的程式語言。所謂的物件導向的語言,簡單理解就是語言中的所有操作都是透過物件來進行的。

  • 程序導向的程式設計的語言
    • 程序導向指將我們的程式的邏輯分解為一個一個的步驟,透過對每個步驟的抽象,來完成程式。
    • 例子:孩子上學,可能有以下過程。
      1. 媽媽起床。
      2. 媽媽洗漱。
      3. 媽媽做早飯。
      4. 媽媽叫孩子起床。
      5. 孩子要洗漱。
      6. 孩子吃飯。
      7. 孩子揹著書包上學校。
    • 程序導向的程式設計思想將一個功能分解為一個一個小的步驟,我們透過完成一個一個的小的步驟來完成一個程式。
    • 這種程式設計方式,符合我們人類的思維,編寫起來相對比較簡單。
    • 但是這種方式編寫程式碼的往往只適用於一個功能,如果要在實現別的功能,即使功能相差極小,也往往要重新編寫程式碼,所以它可複用性比較低,並且難於維護 。
  • 物件導向的程式語言
    • 物件導向的程式語言,關注的是物件,而不關注過程。
    • 對於物件導向的語言來說,一切都是物件。
    • 物件導向的程式設計思想,將所有的功能統一儲存到對應的物件中。比如,媽媽的功能儲存到媽媽的物件中,孩子的功能儲存到孩子物件中,要使用某個功能,直接找到對應的物件即可。
    • 這種方式編寫的程式碼,比較容易閱讀,並且比較易於維護,容易複用。
    • 但是這種方式編寫,不太符合常規的思維,編寫起來稍微麻煩一點。
  • 簡單歸納一下,物件導向的思想:
    • 第一步:建立物件。
    • 第二步:處理物件。

類的簡介

我們目前所學習的物件都是 Python 內建的物件,但是內建物件並不能滿足所有的需求,所以我們在開發中經常需要自定義一些物件。

:簡單理解它就相當於一個圖紙,在程式中我們需要根據類來建立物件。

  • 類就是物件的圖紙!

  • 我們也稱物件是類的例項(instance)

  • 如果多個物件是透過一個類建立的,我們稱這些物件是一類物件。

  • 像 int(),float(),bool(),str(),list(),dict()等,這些都是類。

    • a = int(10) # 建立一個 int 類的例項 等價於 a = 10
  • 我們自定義的類都需要使用大寫字母開頭,使用大駝峰命名法(帕斯卡命名法)來對類命名。

  • 類也是一個物件!

  • 類就是一個用來建立物件的物件!

  • 類是 type 型別的物件,定義類實際上就是定義了一個 type 型別的物件。

  • 使用類建立物件的流程:

    image-20210926155702330

    • 第一步:建立一個變數。
    • 第二步:在記憶體中建立一個新物件。
    • 第三步:將物件的 id 賦值給變數。

語法:

class 類名([父類]):
    程式碼塊
  • 如果沒有父類,() 可以省略。

示例:

a = int(10)  # 建立一個 int 類的例項
b = str('hello')  # 建立一個 str 類的例項
print(a, type(a))  # 10 <class 'int'>
print(b, type(b))  # hello <class 'str'>


# 定義一個簡單的類
# 使用 class 關鍵字來定義類,語法和函式很像!
# class 類名([父類]):
#   程式碼塊
# <class '__main__.MyClass'>
class MyClass():  # 如果沒有父類,() 可以省略
    pass


print(MyClass)  # <class '__main__.MyClass'>

# 使用 MyClass 建立一個物件
# 使用類來建立物件,就像呼叫一個函式一樣
mc = MyClass()  # mc 就是透過 MyClass 建立的物件,mc 是 MyClass 的例項
print(mc, type(mc))  # <__main__.MyClass object at 0x000001B009813E50> <class '__main__.MyClass'>
mc_2 = MyClass()
mc_3 = MyClass()
mc_4 = MyClass()
# mc mc_2 mc_3 mc_4 都是 MyClass 的例項,它們都是一類物件
# isinstance() 用來檢查一個物件是否是一個類的例項
result = isinstance(mc_2, MyClass)
print(result)  # True
result = isinstance(mc_2, str)
print(result)  # False

# 類是一個 type 型別的物件
print(id(MyClass), type(MyClass))  # 1560257906784 <class 'type'>

# 現在我們透過 MyClass 這個類建立的物件都是一個空物件
# 也就是物件中實際上什麼都沒有,就相當於是一個空的盒子
# 可以向物件中新增變數,物件中的變數稱為屬性
# 語法:物件.屬性名 = 屬性值
mc.name = '孫悟空'
print(mc.name)  # 孫悟空
mc_2.name = '豬八戒'
print(mc_2.name)  # 豬八戒

類的定義

類和物件都是對現實生活中的事物或程式中的內容的抽象。

實際上所有的事物都由兩部分構成:

  • 資料(屬性)
  • 行為(方法)

在類的程式碼塊中,我們可以定義變數和函式:

  • 變數會成為該類例項的公共屬性,所有的該類例項都可以透過物件.屬性名的形式訪問。
  • 函式會成為該類例項的公共方法,所有該類例項都可以透過物件.方法名()的形式呼叫方法。

注意:方法呼叫時,預設第一個引數由解析器自動傳遞,所以定義方法時,至少要定義一個形參! 一般我們都會將這個引數命名為 self。(如果是函式呼叫,則呼叫時傳幾個引數,就會有幾個實參)

例項為什麼能訪問到類中的屬性和方法:

  • 類中定義的屬性和方法都是公共的,任何該類例項都可以訪問。
  • 屬性和方法查詢的流程:
    • 當我們呼叫一個物件的屬性時,解析器會先在當前物件中尋找是否含有該屬性,如果有,則直接返回當前的物件的屬性值;如果沒有,則去當前物件的類物件中去尋找,如果有,則返回類物件的屬性值,如果類物件中依然沒有,則報錯!
  • 類物件和例項物件中都可以儲存屬性(方法):
    • 如果這個屬性(方法)是所有的例項共享的,則應該將其儲存到類物件中。
    • 如果這個屬性(方法)是某個例項獨有,則應該儲存到例項物件中。
    • 比如,Person 類中,name 屬性每個物件都不同,應該儲存到各個例項物件中,而國籍假設都是中國人,是一樣的,則應該儲存到類物件中。
    • 一般情況下,屬性儲存到例項物件中,而方法需要儲存到類物件中。

示例:

# 嘗試定義一個表示人的類
class Person:
    # 在類的程式碼塊中,我們可以定義變數和函式
    # 在類中我們所定義的變數,將會成為所有的例項的公共屬性
    # 所有例項都可以訪問這些變數
    name = 'swk'  # 公共屬性,所有例項都可以訪問

    # 在類中也可以定義函式,類中的定義的函式,我們稱為方法
    # 這些方法可以透過該類的所有例項來訪問

    def say_hello(self):
        # 方法每次被呼叫時,解析器都會自動傳遞第一個實參
        # 第一個引數,就是呼叫方法的物件本身,
        #   如果是 p1 調的,則第一個引數就是 p1 物件
        #   如果是 p2 調的,則第一個引數就是 p2 物件
        # 一般我們都會將這個引數命名為 self

        # say_hello() 這個方法,可以顯示如下格式的資料:
        #   你好!我是 xxx
        #   在方法中不能直接訪問類中的屬性
        print('你好!我是 %s' % self.name) # 類似Java中的this


# 建立 Person 的例項
p1 = Person()
p2 = Person()

# 呼叫屬性:物件.屬性名
print(p1.name)  # swk
print(p2.name)  # swk

# 呼叫方法:物件.方法名()
# 方法呼叫和函式呼叫的區別
#   如果是函式呼叫,則呼叫時傳幾個引數,就會有幾個實參
#   但是如果是方法呼叫,預設傳遞一個引數,所以方法中至少要定義一個形參
p1.say_hello()  # 你好!我是 swk
p2.say_hello()  # 你好!我是 swk

# 修改p1的name屬性
p1.name = '豬八戒'
p2.name = '沙和尚'
print(p1.name)
print(p2.name)
p1.say_hello()  # 你好!我是 豬八戒
p2.say_hello()  # 你好!我是 沙和尚

del p2.name  # 刪除 p2 的 name 屬性
print(p2.name)  # swk

物件的初始化

類的基本結構:

class 類名([父類]) :

        公共的屬性... 

        # 物件的初始化方法
        def __init__(self, ...):
            ...

        # 其他的方法    
        def method_1(self, ...):
            ...

        def method_2(self, ...):
            ...

        ...

建立物件的流程,p1 = Person()

  • 第一步:建立一個變數。
  • 第二步:在記憶體中建立一個新物件。
  • 第三步:__init__(self)方法執行。
  • 第四步:將物件的 id 賦值給變數。

示例:

class Person:
    # 在類中可以定義一些特殊方法(魔術方法)
    # 特殊方法都是以 __ 開頭,__ 結尾的方法
    # 特殊方法不需要我們自己呼叫,不要嘗試去呼叫特殊方法
    # 特殊方法將會在特殊的時刻自動呼叫
    # 學習特殊方法:
    #   1. 特殊方法什麼時候呼叫
    #   2. 特殊方法有什麼作用
    # 建立物件的流程
    # p1 = Person() 的執行流程
    #   1. 建立一個變數
    #   2. 在記憶體中建立一個新物件
    #   3. __init__(self) 方法執行
    #   4. 將物件的 id 賦值給變數

    # init 會在物件建立以後立刻執行
    # init 可以用來向新建立的物件中初始化屬性
    # 呼叫類建立物件時,類後邊的所有引數都會依次傳遞到 init() 中
    def __init__(self, name):
        # print(self)
        # 透過 self 向新建的物件中初始化屬性
        self.name = name

    def say_hello(self):
        print('大家好,我是%s' % self.name)


# 目前來講,對於 Person 類來說 name 是必須的,並且每一個物件中的 name 屬性基本上都是不同
# 而我們現在是將 name 屬性在定義為物件以後,手動新增到物件中,這種方式很容易出現錯誤
# 我們希望,在建立物件時,必須設定 name 屬性,如果不設定物件將無法建立
#   並且屬性的建立應該是自動完成的,而不是在建立物件以後手動完成
# p1 = Person()
# 手動向物件新增 name 屬性
# p1.name = '孫悟空'

# p2 = Person()
# p2.name = '豬八戒'

# p3 = Person()
# p3.name = '沙和尚'

# p3.say_hello()

p1 = Person('孫悟空')
p2 = Person('豬八戒')
p3 = Person('沙和尚')
p4 = Person('唐僧')
# p1.__init__() 不要這麼做

# print(p1.name)
# print(p2.name)
# print(p3.name)
# print(p4.name)

p4.say_hello()
class Dog:
    '''
        表示狗的類
    '''

    def __init__(self, name, age, gender, height):
        self.name = name
        self.age = age
        self.gender = gender
        self.height = height

    def jiao(self):
        '''
            狗叫的方法
        '''
        print('汪汪汪~~~')

    def yao(self):
        '''
            狗咬的方法
        '''
        print('我咬你~~')

    def run(self):
        print('%s 快樂的奔跑著~~' % self.name)


d = Dog('小黑', 8, 'male', 30)
print(d.name, d.age, d.gender, d.height)

# 目前我們可以直接透過 物件.屬性 的方式來修改屬性的值,這種方式導致物件中的屬性可以隨意修改
#   非常的不安全,值可以任意修改,不論對錯
# 現在我們就需要一種方式來增強資料的安全性
#   1. 屬性不能隨意修改(我讓你改你才能改,不讓你改你就不能改)
#   2. 屬性不能修改為任意的值(年齡不能是負數)
d.name = '阿黃'
d.age = -10
d.run()

print(d.age)

封裝

# 封裝是物件導向的三大特性之一
# 封裝指的是隱藏物件中一些不希望被外部所訪問到的屬性或方法
# 如何隱藏一個物件中的屬性?
#   - 將物件的屬性名,修改為一個外部不知道的名字
# 如何獲取(修改)物件中的屬性?
#   - 需要提供一個 getter 和 setter 方法使外部可以訪問到屬性
#   - getter 獲取物件中的指定屬性(get_屬性名)
#   - setter 用來設定物件的指定屬性(set_屬性名)
# 使用封裝,確實增加了類的定義的複雜程度,但是它也確保了資料的安全性
#   1. 隱藏了屬性名,使呼叫者無法隨意的修改物件中的屬性
#   2. 增加了 getter 和 setter 方法,很好的控制的屬性是否是隻讀的
#       如果希望屬性是隻讀的,則可以直接去掉 setter 方法
#       如果希望屬性不能被外部訪問,則可以直接去掉 getter 方法
#   3. 使用 setter 方法設定屬性,可以增加資料的驗證,確保資料的值是正確的
#   4. 使用 getter 方法獲取屬性,使用 setter 方法設定屬性
#       可以在讀取屬性和修改屬性的同時做一些其他的處理
#   5. 使用 getter 方法可以表示一些計算的屬性

class Dog:
    '''
        表示狗的類
    '''

    def __init__(self, name, age):
        self.hidden_name = name
        self.hidden_age = age

    def say_hello(self):
        print('大家好,我是 %s' % self.hidden_name)

    def get_name(self):
        '''
            get_name()用來獲取物件的name屬性
        '''
        # print('使用者讀取了屬性')
        return self.hidden_name

    def set_name(self, name):
        # print('使用者修改了屬性')
        self.hidden_name = name

    def get_age(self):
        return self.hidden_age

    def set_age(self, age):
        if age > 0:
            self.hidden_age = age


d = Dog('旺財', 8)

# d.say_hello()

# 呼叫 setter 來修改 name 屬性 
d.set_name('小黑')
d.set_age(-10)

# d.say_hello()
print(d.get_age())
class Rectangle:
    '''
        表示矩形的類
    '''

    def __init__(self, width, height):
        self.hidden_width = width
        self.hidden_height = height

    def get_width(self):
        return self.hidden_width

    def get_height(self):
        return self.hidden_height

    def set_width(self, width):
        self.hidden_width = width

    def set_height(self, height):
        self.hidden_height = height

    def get_area(self):
        return self.hidden_width * self.hidden_height


# 測試
r = Rectangle(5, 2)
print(r.get_area())  # 10
r.set_width(10)
r.set_height(20)
print(r.get_area())  # 200


# 可以為物件的屬性使用雙下劃線開頭,__xxx
# 雙下劃線開頭的屬性,是物件的隱藏屬性,隱藏屬性只能在類的內部訪問,無法透過物件訪問
# 其實隱藏屬性只不過是 Python 自動為屬性改了一個名字
#   實際上是將名字修改為了,_類名__屬性名 比如 __name -> _Person__name
class Person:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name


p = Person('孫悟空')

# print(p.__name)  # __ 開頭的屬性是隱藏屬性,無法透過物件訪問
print(p._Person__name)  # 能直接訪問,孫悟空
p._Person__name = '豬八戒'
print(p.get_name())  # 也能直接更改,豬八戒


# 上面使用 __ 開頭的屬性,實際上依然可以在外部訪問,所以這種方式我們一般不用
#   一般我們會將一些私有屬性(不希望被外部訪問的屬性)以 _ 開頭(實際上也可以直接訪問和修改)
#   一般情況下,使用 _ 開頭的屬性都是私有屬性,沒有特殊需要不要修改私有屬性
class Person:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, name):
        self._name = name


p = Person('孫悟空')

print(p._name)  # 能直接訪問
p._name = '豬八戒'
print(p._name)  # 也能直接修改,豬八戒
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    # property 裝飾器,用來將一個 getter 方法,轉換為物件的屬性
    # 新增為 property 裝飾器以後,我們就可以像呼叫屬性一樣使用 getter 方法
    # 使用 property 裝飾的方法,必須和屬性名是一樣的
    @property
    def name(self):
        print('get方法執行了~~~')
        return self._name

    # setter 方法的裝飾器:@屬性名.setter
    @name.setter
    def name(self, name):
        print('setter方法呼叫了')
        self._name = name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age


p = Person('豬八戒', 18)

print(p.name, p.age)  # 呼叫的就是裝飾器裝飾的 setter 和 getter 方法

p.name = '孫悟空'
p.age = 28

print(p.name, p.age)

繼承

# 繼承

# 定義一個類 Animal(動物)
#   這個類中需要兩個方法:run() sleep()
class Animal:
    def run(self):
        print('動物會跑~~~')

    def sleep(self):
        print('動物睡覺~~~')

    # def bark(self):
    #     print('動物嚎叫~~~')


# 定義一個類 Dog(狗)
#   這個類中需要三個方法:run() sleep() bark()
# class Dog:
#     def run(self):
#         print('狗會跑~~~')

#     def sleep(self):
#         print('狗睡覺~~~')

#     def bark(self):
#         print('汪汪汪~~~')

# 有一個類,能夠實現我們需要的大部分功能,但是不能實現全部功能
# 如何能讓這個類來實現全部的功能呢?
#   ① 直接修改這個類,在這個類中新增我們需要的功能
#       - 修改起來會比較麻煩,並且會違反 OCP 原則
#   ② 直接建立一個新的類
#       - 建立一個新的類比較麻煩,並且需要大量的進行復制貼上,會出現大量的重複性程式碼
#   ③ 直接從 Animal 類中來繼承它的屬性和方法
#       - 繼承是物件導向三大特性之一
#       - 透過繼承我們可以使一個類獲取到其他類中的屬性和方法
#       - 在定義類時,可以在類名後的括號中指定當前類的父類(超類、基類、super)
#           子類(衍生類)可以直接繼承父類中的所有的屬性和方法
#
#  透過繼承可以直接讓子類獲取到父類的方法或屬性,避免編寫重複性的程式碼,並且也符合 OCP 原則
#   所以我們經常需要透過繼承來對一個類進行擴充套件

class Dog(Animal):
    def run(self):
        print('狗跑~~~~')

    def bark(self):
        print('汪汪汪~~~')


class Hashiqi(Dog):
    def fan_sha(self):
        print('我是一隻傻傻的哈士奇')


d = Dog()
d.run()  # 狗跑~~~~
d.sleep()  # 動物睡覺~~~
d.bark()  # 汪汪汪~~~
print(isinstance(d, Dog))  # True
print(isinstance(d, Animal))  # True

h = Hashiqi()
h.run()  # 狗跑~~~~
h.fan_sha()  # 我是一隻傻傻的哈士奇
print(isinstance(h, Hashiqi))  # True
print(isinstance(h, Dog))  # True
print(isinstance(h, Animal))  # True

print('######################################')


# 在建立類時,如果省略了父類,則預設父類為 object
#   object 是所有類的父類,所有類都繼承自 object
class Person(object):
    pass


# issubclass() 檢查一個類是否是另一個類的子類
print(issubclass(Animal, Dog))  # False
print(issubclass(Animal, object))  # True
print(issubclass(Person, object))  # True

# isinstance() 用來檢查一個物件是否是一個類的例項
#   如果這個類是這個物件的父類,也會返回 True
#   所有的物件都是 object 的例項
print(isinstance(print, object))  # True
class Animal:
    def __init__(self, name):
        self._name = name

    def run(self):
        print('動物會跑~~~')

    def sleep(self):
        print('動物睡覺~~~')

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name


# 父類中的所有方法都會被子類繼承,包括特殊方法,也可以重寫特殊方法
class Dog(Animal):

    def __init__(self, name, age):
        # 希望可以直接呼叫父類的 __init__ 來初始化父類中定義的屬性
        # super() 可以用來獲取當前類的父類,
        #   並且透過 super() 返回物件呼叫父類方法時,不需要傳遞 self
        super().__init__(name)
        self._age = age

    def run(self):
        print('狗跑~~~~')

    def bark(self):
        print('汪汪汪~~~')

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age


d = Dog('旺財', 18)

print(d.name)  # 旺財
print(d.age)  # 18

重寫

# 繼承

# 定義一個類 Animal(動物)
#   這個類中需要兩個方法:run() sleep()
class Animal:
    def run(self):
        print('動物會跑~~~')

    def sleep(self):
        print('動物睡覺~~~')


class Dog(Animal):
    def bark(self):
        print('汪汪汪~~~')

    def run(self):
        print('狗跑~~~~')


# 如果在子類中如果有和父類同名的方法,則透過子類例項去呼叫方法時,
#   會呼叫子類的方法而不是父類的方法,這個特點我們成為叫做方法的重寫(覆蓋,override)

# 建立 Dog 類的例項
d = Dog()

d.run()  # 狗跑~~~~


# 當我們呼叫一個物件的方法時,
#   會優先去當前物件中尋找是否具有該方法,如果有則直接呼叫
#   如果沒有,則去當前物件的父類中尋找,如果父類中有則直接呼叫父類中的方法,
#   如果沒有,則去父類的父類中尋找,以此類推,直到找到 object,如果依然沒有找到,則報錯
class A(object):
    def test(self):
        print('AAA')


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


class C(B):
    def test(self):
        print('CCC')


# 建立一個c的例項
c = C()
c.test()  # CCC

多重繼承

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


class B(object):
    def test(self):
        print('B中的test()方法~~')

    def test2(self):
        print('BBB')


# 在 Python 中是支援多重繼承的,也就是我們可以為一個類同時指定多個父類
#   可以在類名的 () 後邊新增多個類,來實現多重繼承
#   多重繼承,會使子類同時擁有多個父類,並且會獲取到所有父類中的方法
# 在開發中沒有特殊的情況,應該儘量避免使用多重繼承,因為多重繼承會讓我們的程式碼過於複雜
# 如果多個父類中有同名的方法,則會先在第一個父類中尋找,然後找第二個,然後找第三個。。。
#   前邊父類的方法會覆蓋後邊父類的方法
class C(A, B):
    pass


c = C()
c.test()  # AAA
c.test2()  # BBB

# 類名.__bases__ 這個屬性可以用來獲取當前類的所有父類,返回的是一個元組
print(B.__bases__)  # (<class 'object'>,)
print(C.__bases__)  # (<class '__main__.A'>, <class '__main__.B'>)

多型

# 多型是物件導向的三大特徵之一
# 多型從字面上理解是多種形態
# 狗(狼狗、藏獒、哈士奇、古牧 。。。)
# 一個物件可以以不同的形態去呈現

# 定義兩個類
class A:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name


class B:
    def __init__(self, name):
        self._name = name

    def __len__(self):
        return 10

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name


class C:
    pass


a = A('孫悟空')
b = B('豬八戒')
c = C()


# 定義一個函式
# 對於 say_hello() 這個函式來說,只要物件中含有name屬性,它就可以作為引數傳遞
#   這個函式並不會考慮物件的型別,只要有 name 屬性即可 ---> 多型的提現
def say_hello(obj):
    print('你好 %s' % obj.name)


# 在 say_hello_2 中我們做了一個型別檢查,也就是隻有 obj 是 A 型別的物件時,才可以正常使用,
#   其他型別的物件都無法使用該函式,這個函式就違反了多型
# 違反了多型的函式,只適用於一種型別的物件,無法處理其他型別物件,這樣導致函式的適應性非常的差
# 注意,像 isinstance() 這種函式,在開發中一般是不會使用的!(使用這個函式,就表示可能違反了多型)
def say_hello_2(obj):
    # 做型別檢查
    if isinstance(obj, A):
        print('你好 %s' % obj.name)
    # say_hello(b)


# say_hello_2(b)

# 鴨子型別(多型理論):
#   如果一個東西,走路像鴨子,叫聲像鴨子,那麼它就是鴨子

# len()
# 之所以一個物件能透過 len() 來獲取長度,是因為物件中具有一個特殊方法 __len__
# 換句話說,只要物件中具有 __len__ 特殊方法,就可以透過 len() 來獲取它的長度
# 這就是多型的體現
l = [1, 2, 3]
s = 'hello'

print(len(l))  # 3
print(len(s))  # 5
print(len(b))  # 10
# print(len(c))  # 報錯,object of type 'C' has no len()

# 物件導向的三大特徵:
#   封裝
#       - 確保物件中的資料安全
#   繼承
#       - 保證了物件的可擴充套件性
#   多型
#       - 保證了程式的靈活性

類中的屬性和方法

# 定義一個類
class A(object):
    # 類屬性---所有例項公用的
    # 例項屬性---每個例項私有的
    # 類方法
    # 例項方法
    # 靜態方法

    # 類屬性,直接在類中定義的屬性是類屬性
    #   類屬性可以透過類或類的例項訪問到
    #   但是類屬性只能透過類物件來修改,無法透過例項物件修改
    count = 0

    # __init__也是例項方法
    def __init__(self):
        # 例項屬性,透過例項物件新增的屬性屬於例項屬性
        #   例項屬性只能透過例項物件來訪問和修改,類物件無法訪問修改
        self.name = '孫悟空'  # name也是例項屬性

    # 例項方法
    #   在類中定義,以 self 為第一個引數的方法都是例項方法
    #   例項方法在呼叫時,Python 會將呼叫物件作為 self 傳入
    #   例項方法可以透過例項和類去呼叫
    #       當透過例項呼叫時,會自動將當前呼叫物件作為 self 傳入
    #       當透過類呼叫時,不會自動傳遞 self,此時我們必須手動傳遞 self
    def test(self):
        print('這是test方法~~~ ', self)

    # 類方法
    #   在類內部使用 @classmethod 來修飾的方法屬於類方法
    #   類方法的第一個引數是 cls,也會被自動傳遞,cls 就是當前的類物件
    #   類方法和例項方法的區別,例項方法的第一個引數是 self,而類方法的第一個引數是 cls
    #   類方法可以透過類去呼叫,也可以透過例項呼叫,沒有區別
    @classmethod
    def test_2(cls):
        print('這是test_2方法,他是一個類方法~~~ ', cls)
        print(cls.count)  # 這個訪問的是類屬性,與例項物件無關

    # 靜態方法
    #   在類中使用 @staticmethod 來修飾的方法屬於靜態方法
    #   靜態方法不需要指定任何的預設引數,靜態方法可以透過類和例項去呼叫
    #   靜態方法,基本上是一個和當前類無關的方法,它只是一個儲存到當前類中的函式
    #   靜態方法一般都是一些工具方法,和當前類無關(建議靜態方法不要放到某個類中,或者全部放到一個工具類中)
    @staticmethod
    def test_3():
        print('test_3執行了~~~')


print('A ', A.count)  # 類訪問類屬性:0
a = A()
print('a ', a.count)  # 類的例項訪問類屬性:0

a.count = 10  # 類的例項無法修改類屬性,此操作是給 a 這個例項物件,新增了一個例項屬性 count
print('A ', A.count)  # 0
print('a ', a.count)  # 10
A.count = 100  # 類可以修改類屬性,但不影響類的例項中已存在的同名屬性
print('A ', A.count)  # 100
print('a ', a.count)  # 10
b = A()  # b這個例項物件中,沒有count例項屬性,訪問的是A類的屬性
print('b ', b.count)  # 100

# print('A ', A.name) # 類無法訪問例項屬性,AttributeError: type object 'A' has no attribute 'name'
print('a ', a.name)  # 孫悟空

# 類和類的例項,都可以訪問例項方法
a.test()  # 等價於 A.test(a):這是test方法~~~  <__main__.A object at 0x000002631BC28310>

# 類和類的例項,都可以訪問類方法
A.test_2()  # 等價於 a.test_2():這是test_2方法,他是一個類方法~~~  <class '__main__.A'>

# 靜態方法,與類和類的例項無關
A.test_3()  # test_3執行了~~~
a.test_3()  # test_3執行了~~~
b.test_3()  # test_3執行了~~~

垃圾回收

# 就像我們生活中會產生垃圾一樣,程式在執行過程當中也會產生垃圾
# 程式執行過程中產生的垃圾會影響到程式的執行的執行效能,所以這些垃圾必須被及時清理
# 沒用的東西就是垃圾
# 在程式中沒有被引用的物件就是垃圾,這種垃圾物件過多以後會影響到程式的執行的效能
#   所以我們必須進行及時的垃圾回收,所謂的垃圾回收就是將垃圾物件從記憶體中刪除
# 在 Python 中有自動的垃圾回收機制,它會自動將這些沒有被引用的物件刪除,
#   所以我們不用手動處理垃圾回收

class A:
    def __init__(self):
        self.name = 'A類'

    # del 是一個特殊方法,它會在垃圾物件被回收前呼叫
    def __del__(self):
        print('A()物件被回收了~~~', self)


a = A()
print(a.name)
# a = None  # 將 a 設定為 None,此時沒有任何的變數對 A() 物件進行引用,A() 物件變成了垃圾
# 變成垃圾的 A() 物件會被回收,回收前呼叫 __del__()方法

# del a # del 會把 a 變數刪除,也會導致 A() 物件變成垃圾

input('Enter鍵退出程式...')  # 程式結束後,A() 物件即使還在被 a 變數引用,仍然會被回收

特殊方法

# 特殊方法,也稱為魔術方法
# 特殊方法都是使用 __ 開頭和結尾的
# 特殊方法一般不需要我們手動呼叫,需要在一些特殊情況下自動執行

# 定義一個 Person 類
class Person(object):
    """人類"""

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # __str__() 這個特殊方法會在嘗試將物件轉換為字串的時候呼叫
    # 它的作用可以用來指定物件轉換為字串的結果(print 函式)
    def __str__(self):
        return 'Person [name=%s , age=%d]' % (self.name, self.age)

    # __repr__() 這個特殊方法會在對當前物件使用 repr() 函式時呼叫
    # 它的作用是指定物件在 '互動模式' 中直接輸出的效果
    def __repr__(self):
        return 'Hello, this is repr'

    # 重寫以下方法,讓物件支援比較,以 __gt__() 為例說明
    # object.__lt__(self, other) 小於 <
    # object.__le__(self, other) 小於等於 <=
    # object.__eq__(self, other) 等於 ==
    # object.__ne__(self, other) 不等於 !=
    # object.__gt__(self, other) 大於 >
    # object.__ge__(self, other) 大於等於 >=

    # __gt__() 會在物件做大於比較的時候呼叫,該方法的返回值將會作為比較的結果
    # 它需要兩個引數,一個 self 表示當前物件,other 表示和當前物件比較的物件
    # self > other
    def __gt__(self, other):
        return self.age > other.age  # 以年齡作為比較的指標

    # __len__() # 獲取物件的長度

    # object.__bool__(self)
    # 可以透過 bool 來指定物件轉換為布林值的情況
    def __bool__(self):
        return self.age > 17

    # 運算的方法
    # object.__add__(self, other)
    # object.__sub__(self, other)
    # object.__mul__(self, other)
    # object.__matmul__(self, other)
    # object.__truediv__(self, other)
    # object.__floordiv__(self, other)
    # object.__mod__(self, other)
    # object.__divmod__(self, other)
    # object.__pow__(self, other[, modulo])
    # object.__lshift__(self, other)
    # object.__rshift__(self, other)
    # object.__and__(self, other)
    # object.__xor__(self, other)
    # object.__or__(self, other)


# 建立兩個 Person 類的例項
p1 = Person('孫悟空', 18)
p2 = Person('豬八戒', 28)

# 列印 p1
# 當我們列印一個物件時,實際上列印的是物件的中特殊方法 __str__() 的返回值
# print(p1)  # 不改寫 __str__() 方法的輸出結果:<__main__.Person object at 0x04E95090>
print(p1)  # 改寫 __str__() 方法後的輸出結果:Person [name=孫悟空 , age=18]

print(repr(p1))  # Hello, this is repr

# 大於比較方法
print(p1 > p2)  # False

print(bool(p1))  # True
# 條件不清晰,p1 呼叫的就是 __bool__() 方法,一般不這樣寫
# if p1:
#     print(p1.name, '已經成年了')
# else:
#     print(p1.name, '還未成年了')

模組化

簡介:

# 模組(module)
# 模組化,模組化指將一個完整的程式分解為一個一個小的模組
#   透過將模組組合,來搭建出一個完整的程式
# 不採用模組化:統一將所有的程式碼編寫到一個檔案中
# 採用模組化:將程式分別編寫到多個檔案中
# 模組化的優點:
#     ① 方便開發
#     ② 方便維護
#     ③ 模組可以複用!

# 在 Python 中一個 py 檔案就是一個模組,要想建立模組,實際上就是建立一個 python 檔案
# 注意:模組名要符號識別符號的規範

# 在一個模組中引入外部模組:
# ① import 模組名 (模組名,就是 python 檔案的名字,注意不要 .py 字尾)
# ② import 模組名 as 模組別名
#   - 可以引入同一個模組多次,但是模組的例項只會建立一個
#   - import 可以在程式的任意位置呼叫,但是一般情況下,import 語句都會統一寫在程式的開頭
#   - 在每一個模組內部都有一個 __name__ 屬性,透過這個屬性可以獲取到模組的名字
#   - __name__ 屬性值為 __main__ 的模組是主模組,一個程式中只會有一個主模組
#       主模組就是我們直接透過 python 執行的模組(當前程式所在的模組)

import test_module as test

print(__name__)  # 主模組:__main__
print(test.__name__)  # 引入的外部模組:test_module
  • m.py

    # 可以在模組中定義變數,在模組中定義的變數,在引入該模組後,就可以直接使用了
    a = 10
    b = 20
    
    # 新增了 _ 的變數,只能在模組內部訪問,在透過 import * 方式引入時,不會引入 _ 開頭的變數
    _c = 30
    
    
    # 可以在模組中定義函式,同樣可以透過模組訪問到
    def test():
        print('test')
    
    
    def test2():
        print('test2')
    
    
    # 也可以定義類
    class Person:
        def __init__(self):
            self.name = '孫悟空'
    
    
    # 編寫測試程式碼:
    #   這部分程式碼,只有當前模組作為主模組的時候才需要被執行
    #   而當前模組被其他模組引入時,不需要被執行
    #   此時,我們就必須要檢查當前模組是否是主模組
    if __name__ == '__main__':
        test()
        test2()
        p = Person()
        print(p.name)
    
  • main.py

    import m
    
    # 訪問模組中的變數:模組名.變數名
    print(m.a, m.b)  # 10 20.
    # print(m._c)  # 此方式可以訪問_c 屬性
    
    # 訪問模組中的方法:模組名.方法名
    m.test()  # test
    m.test2()  # test2
    
    # 訪問模組中的類:模組名.類名,建立類的例項
    p = m.Person()
    print(p.name)  # 孫悟空
    
    # 也可以只引入模組中的部分內容
    # 語法: from 模組名 import 變數, 變數....
    # from m import Person # 只引入 Person
    # from m import test # 只引入 test
    from m import Person, test  # 引入多個
    
    # 透過上面方式引入後,可以直接使用
    p1 = Person()
    print(p1)  # <m.Person object at 0x00000115DD088160>
    test()  # test
    
    
    # test2() # test2()沒有引入,不能直接使用
    
    # from m import *  # 引入模組中所有內容,一般不會使用
    
    # 當前模組中,會覆蓋被引入模組中的同名方法
    def test2():
        print('這是主模組中的test2')
    
    
    test2()  # 這是主模組中的 test2
    
    # 也可以為引入的變數使用別名
    # 語法:from 模組名 import 變數 as 別名
    from m import test2 as new_test2
    
    test2()  # 這是主模組中的 test2
    new_test2()  # test2
    
    # from m import *
    
    # print(_c) # _c 屬性無法訪問
    
    
    # 總結:
    # import xxx
    # import xxx as yyy
    # from xxx import yyy , zzz , fff
    # from xxx import *
    # from xxx import yyy as zz
    

結構:

image-20210929171722079

image-20210929171809531

image-20210929171835707

  • hello/__init__.py

    def test():
        print('test')
    
  • hello/a.py

    c = 30
    
  • hello/b.py

    d = 40
    
  • main.py

    # 包 Package
    # 包也是一個模組
    # 當我們模組中程式碼過多時,或者一個模組需要被分解為多個模組時,這時就需要使用到包
    # 普通的模組就是一個 py 檔案,而包是一個資料夾
    #   包中必須要有一個 __init__.py 檔案,這個檔案中可以包含有包中的主要內容
    from hello import a, b
    
    print(a.c)
    print(b.d)
    
    # __pycache__ 是模組的快取檔案
    # .py 程式碼在執行前,需要被解析器先轉換為機器碼,然後再執行
    #   所以我們在使用模組(包)時,也需要將模組的程式碼先轉換為機器碼,然後再交由計算機執行
    #   而為了提高程式執行的效能,python 會在編譯過一次以後,將程式碼儲存到一個快取檔案中
    #   這樣在下次載入這個模組(包)時,就可以不再重新編譯而是直接載入快取中編譯好的程式碼即可
    

Python 標準庫

# 思想:開箱即用
# 為了實現開箱即用的思想,Python 中為我們提供了一個模組的標準庫
# 在這個標準庫中,有很多很強大的模組我們可以直接使用,並且標準庫會隨 Python 的安裝一同安裝

# sys 模組:
#   它裡面提供了一些變數和函式,使我們可以獲取到 Python 解析器的資訊
#   或者透過函式來操作 Python 解析器
# 引入 sys 模組:
import sys

print(sys)  # <module 'sys' (built-in)>

# sys.argv:
#   命令列執行程式碼時,獲取命令列中所包含的引數
#   該屬性是一個列表,列表中儲存了當前命令的所有引數
#   參考 IDEA 中 Java 程式 main() 方法模組引數的引入,注意第一個引數
print(sys.argv)  # ['D:/JetBrainsWorkSpace/PycharmProjects/main.py', 'aaa', 'bbb']

# sys.modules:
#   獲取當前程式中引入的所有模組
#   modules 是一個字典,字典的 key 是模組的名字,字典的 value 是模組物件
print(sys.modules)  # {'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, ......}

# pprint 模組:
#   print() 列印不會格式化資料
#   它給我們提供了一個方法 pprint(),該方法可以用來對列印的資料做簡單的格式化
# 引入 pprint 模組:
import pprint

pprint.pprint(sys.modules)

# sys.path:
#   它是一個列表,列表中儲存的是模組的搜尋路徑,不要輕易更改
# ['D:\\JetBrainsWorkSpace\\PycharmProjects',
#  'D:\\JetBrainsWorkSpace\\PycharmProjects',
#  'D:\\Program Files\\PyCharm Professional Edition with Anaconda plugin '
#  '2020.1.2\\plugins\\python\\helpers\\pycharm_display',
#  'D:\\Program\\Miniconda3\\python38.zip',
#  'D:\\Program\\Miniconda3\\DLLs',
#  'D:\\Program\\Miniconda3\\lib',
#  'D:\\Program\\Miniconda3',
#  'D:\\Program\\Miniconda3\\lib\\site-packages',
#  'D:\\Program\\Miniconda3\\lib\\site-packages\\win32',
#  'D:\\Program\\Miniconda3\\lib\\site-packages\\win32\\lib',
#  'D:\\Program\\Miniconda3\\lib\\site-packages\\Pythonwin',
#  'D:\\Program Files\\PyCharm Professional Edition with Anaconda plugin '
#  '2020.1.2\\plugins\\python\\helpers\\pycharm_matplotlib_backend']
pprint.pprint(sys.path)

# sys.platform:
#   表示當前 Python 執行的平臺
print(sys.platform)  # win32

# sys.exit():
#   函式用來退出程式
# sys.exit('程式出現異常,結束!')  # 後面的 print('hello') 語句不再執行
# print('hello')

# os 模組:
#   讓我們可以對作業系統進行訪問
import os

# os.environ:
#   透過這個屬性可以獲取到系統的環境變數
pprint.pprint(os.environ)  # 所有的
pprint.pprint(os.environ['path'])  # 只檢視 path 環境變數

# os.system():
#   可以用來執行作業系統的命令
os.system('dir')  # dir 命令
os.system('notepad')  # 開啟記事本命令

命令列執行程式碼時的引數:

PS D:\JetBrainsWorkSpace\PycharmProjects> python main.py aaa bbb
['main.py', 'aaa', 'bbb']

image-20210930161242203

原文連結

https://github.com/ACatSmiling/zero-to-zero/blob/main/PythonLanguage/python.md

相關文章