Author: ACatSmiling
Since: 2024-09-27
什麼是物件
物件
:是記憶體中專門用來儲存資料的一塊區域。
物件中可以存放各種資料,比如:數字、布林值、程式碼。
物件由三部分組成:
- 物件的標識(id)
- 物件的型別(type)
- 物件的值(value)
物件導向(oop)
Python 是一門物件導向的程式語言。所謂的物件導向的語言,簡單理解就是語言中的所有操作都是透過物件來進行的。
程序導向的程式設計的語言
:- 程序導向指將我們的程式的邏輯分解為一個一個的步驟,透過對每個步驟的抽象,來完成程式。
- 例子:孩子上學,可能有以下過程。
- 媽媽起床。
- 媽媽洗漱。
- 媽媽做早飯。
- 媽媽叫孩子起床。
- 孩子要洗漱。
- 孩子吃飯。
- 孩子揹著書包上學校。
- 程序導向的程式設計思想將一個功能分解為一個一個小的步驟,我們透過完成一個一個的小的步驟來完成一個程式。
- 這種程式設計方式,符合我們人類的思維,編寫起來相對比較簡單。
- 但是這種方式編寫程式碼的往往只適用於一個功能,如果要在實現別的功能,即使功能相差極小,也往往要重新編寫程式碼,所以它可複用性比較低,並且難於維護 。
物件導向的程式語言
:- 物件導向的程式語言,關注的是物件,而不關注過程。
- 對於物件導向的語言來說,一切都是物件。
- 物件導向的程式設計思想,將所有的功能統一儲存到對應的物件中。比如,媽媽的功能儲存到媽媽的物件中,孩子的功能儲存到孩子物件中,要使用某個功能,直接找到對應的物件即可。
- 這種方式編寫的程式碼,比較容易閱讀,並且比較易於維護,容易複用。
- 但是這種方式編寫,不太符合常規的思維,編寫起來稍微麻煩一點。
- 簡單歸納一下,物件導向的思想:
- 第一步:建立物件。
- 第二步:處理物件。
類的簡介
我們目前所學習的物件都是 Python 內建的物件,但是內建物件並不能滿足所有的需求,所以我們在開發中經常需要自定義一些物件。
類
:簡單理解它就相當於一個圖紙,在程式中我們需要根據類來建立物件。
-
類就是物件的圖紙!
-
我們也稱物件是類的
例項(instance)
。 -
如果多個物件是透過一個類建立的,我們稱這些物件是一類物件。
-
像 int(),float(),bool(),str(),list(),dict()等,這些都是類。
a = int(10) # 建立一個 int 類的例項
等價於a = 10
。
-
我們自定義的類都需要使用大寫字母開頭,使用大駝峰命名法(帕斯卡命名法)來對類命名。
-
類也是一個物件!
-
類就是一個用來建立物件的物件!
-
類是 type 型別的物件,定義類實際上就是定義了一個 type 型別的物件。
-
使用類建立物件的流程:
- 第一步:建立一個變數。
- 第二步:在記憶體中建立一個新物件。
- 第三步:將物件的 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
包
結構:
-
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']
原文連結
https://github.com/ACatSmiling/zero-to-zero/blob/main/PythonLanguage/python.md