程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

29.Python面向對象(二)【類:主要講初始化方法__init__,垃圾回收,繼承&多繼承,方法重寫,super()函數】

編輯:Python

目錄:

  • 每篇前言:
  • Python面向對象(二)
      • 1.1.1 初始化方法`__init__`
      • 1.1.2 python對象銷毀(垃圾回收)
      • 1.1.5 類的繼承
      • 1.1.6 多重繼承
      • 1.1.7 方法重寫
      • 1.1.8 結合一個代碼講一下
      • 1.1.9 類屬性與方法
      • 1.1.10 getattr、setattr及hasattr
      • 1.1.11 講一下super()函數
      • 1.1.12 導入類

每篇前言:

  • 作者介紹:【孤寒者】—CSDN全棧領域優質創作者、HDZ核心組成員、華為雲享專家Python全棧領域博主、CSDN原力計劃作者

  • 本文已收錄於Python全棧系列專欄:《Python全棧基礎教程》
  • 熱門專欄推薦:《Django框架從入門到實戰》、《爬蟲從入門到精通系列教程》、《爬蟲高級》、《前端系列教程》、《tornado一條龍+一個完整版項目》。
  • ​本專欄面向廣大程序猿,為的是大家都做到Python從入門到精通,同時穿插有很多很多習題,鞏固學習。
  • 訂閱專欄後可私聊進一千多人Python全棧交流群(手把手教學,問題解答);進群可領取Python全棧教程視頻 + 多得數不過來的計算機書籍:基礎、Web、爬蟲、數據分析、可視化、機器學習、深度學習、人工智能、算法、面試題等。
  • 加入我一起學習進步,一個人可以走的很快,一群人才能走的更遠!

Python面向對象(二)

1.1.1 初始化方法__init__

初始化方法,作用是在實例化的時候,自動調用。(它是一種魔法方法)

“初始化”特殊方法:

  • 在Python中有很多以雙下劃線開頭且以雙下劃線結尾的固定方法,它們會在特定的時機被觸發執行。
    __init__就是其中之一,它會在實例化之後自動被調用。以完成實例的初始化。

    可以在創建對象時,為對象實現一些初始化的操作,提供一些默認值,需要為類定義實例屬性的時候才會執行__init__方法。

# -*- coding: utf-8 -*-
""" __author__ = 孤寒者 """
class Person:
def __init__(self, name):
self.name = name # self.name是實例屬性
def sing(self):
print('孤寒者最帥~')
wumou = Person("吳某") # 實例化 自動調用我們的初始化方法,把吳某這個名字給了name
#(這句代碼意味著 wumou.name = "吳某"(定義一個實例屬性) wumou.name相當於self.name,"吳某"相當於給了name)

魔法方法:每一個魔法方法都有它特定的功能。

  • 小知識點:
    在python中,指向被刪除了,就會被回收。
    del刪除指向,一個變量指向沒有了就會被回收。
    del調用的就是__init__魔法方法。
    下面我們就來深入探討一下:

1.1.2 python對象銷毀(垃圾回收)

析構,也叫銷毀初始化方法(銷毀上面的那個),是在這個文件運行完然後觸發。(魔法方法)
__del__就是一個析構函數,當使用del刪除對象時,會調用它本身的析構函數。提示開發者,對象被銷毀了,方便調試,進行一些必要的清理工作
淺顯點理解就是:(當實例對象引用數為0的時候就會調用這個魔術方法)
即當類裡面使用了del魔法方法的時候,如果使用了del刪除,那麼就會調用__del__魔法方法;如果沒有使用del刪除,那麼在程序的最後,會自動回收內存地址,就會調用__del__魔法方法。

來兩個淺顯的例子講解一下什麼叫這個文件運行完然後觸發:

# -*- coding: utf-8 -*-
""" __author__ = 孤寒者 """
# python中一個程序執行完了,內存空間就會被回收,回收掉那麼指向就沒有了。
class Person:
def __del__(self): #相當於重寫了python的__del__方法。在列表裡使用del刪除時調用的是python默認的__del__方法。
print('好好學習')
guhanzhe = Person()
print(11111)
print(22222)


來個對比的例子仔細看看:

# -*- coding: utf-8 -*-
""" __author__ = 孤寒者 """
# 當指向刪除完了之後會調用__del__魔法方法。
class Person:
def __del__(self):
print('好好學習')
guhanzhe = Person()
print(11111)
del guhanzhe
print(22222)

  • Python 使用了引用計數這一簡單技術來跟蹤和回收垃圾。
  • 在 Python 內部記錄著所有使用中的對象各有多少引用。
  • 一個內部跟蹤變量,稱為一個引用計數器。
  • 當對象被創建時, 就創建了一個引用計數, 當這個對象不再需要時, 也就是說, 這個對象的引用計數變為0 時, 它被垃圾回收。但是回收不是"立即"的, 由解釋器在適當的時機,將垃圾對象占用的內存空間回收。
a = 40 # 創建對象 <40>
b = a # 增加引用, <40> 的計數
c = [b] # 增加引用. <40> 的計數
del a # 減少引用 <40> 的計數
b = 100 # 減少引用 <40> 的計數
c[0] = -1 # 減少引用 <40> 的計數
  • 垃圾回收機制不僅針對引用計數為0的對象,同樣也可以處理循環引用的情況。循環引用指的是,兩個對象相互引用,但是沒有其他變量引用他們。這種情況下,僅使用引用計數是不夠的。Python 的垃圾收集器實際上是一個引用計數器和一個循環垃圾收集器。作為引用計數的補充, 垃圾收集器也會留心被分配的總量很大(及未通過引用計數銷毀的那些)的對象。 在這種情況下, 解釋器會暫停下來, 試圖清理所有未引用的循環。

  • 析構函數 __del____del__在對象銷毀的時候被調用,當對象不再被使用時,__del__方法運行:
    實例:

# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __del__(self):
class_name = self.__class__.__name__
print("實例%s被銷毀,所屬類是%s" % (self, class_name))
pt1 = Point() # 創建一個對象
pt3 = pt2 = pt1
print(id(pt1), id(pt2), id(pt3)) # 打印對象的id
del pt1
del pt2
del pt3

  • 上例中,先創建了一個Point對象,然後將pt1、pt2、pt3的引用均指向該對象
    打印id,發現確實都是同一個對象。
  • 刪除這三個引用時,就是斷開對這個對象的引用
  • 當三個引用全部被斷開時,這個對象已經沒有被任何變量引用變成"垃圾"
  • 從而可能觸發垃圾回收,在垃圾回收的時候會運行對象的__del__方法

1.1.5 類的繼承

  • 面向對象的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過繼承機制。繼承完全可以理解成類之間的類型和子類型關系。

  • 繼承還可以一級一級地繼承下來,就好比從爺爺到爸爸、再到兒子這樣的關系。而任何類,最終都可以追溯到根類object,這些繼承關系看上去就像一顆倒著的樹。比如如下的繼承樹:

  • 繼承可以把父類的所有功能都直接拿過來,這樣就不必從零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫;

  • 這樣我們就可以處理功能的迭代更新,以及拓展重用代碼,方便代碼的管理和修改。

  • 有了繼承,才能有多態。在調用類實例方法的時候,盡量把變量視作父類類型,這樣,所有子類類型都可以正常被接收。

  • 小知識點(其實前面講過):
    類名.__bases__ 可以查看類的父類有哪些,注意要print。

  • 語法:

class SubClassName (ParentClass1[, ParentClass2, ...]):
'Optional class documentation string'
class_suite

在python中繼承中的一些特點:

  1. 在繼承中父類的構造(__init__()方法)不會被自動調用,它需要在其子類的構造中親自專門調用。
  2. 在調用父類的方法時,需要加上父類的類名前綴,且需要帶上self參數變量。區別於在類中調用普通函數時並不需要帶上self參數
  3. Python總是首先查找對應類型的方法,如果它不能在派生類中找到對應的方法,它才開始到基類中逐個查找。(先在本類中查找調用的方法,找不到才去基類中找)。
  4. 如果在繼承元組中列了一個以上的類,那麼它就被稱作"多重繼承" 。

代碼實戰:

# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
class Parent: # 定義父類
parentAttr = 100
def __init__(self):
print("調用父類構造函數")
def parentMethod(self):
print('調用父類方法')
def method(self):
print('調用父類method方法')
class Child(Parent): # 定義子類
def __init__(self):
super().__init__()
print("調用子類構造方法")
def childMethod(self):
print('調用子類方法 child method')
def method(self):
super().method()
print('調用子類method方法')
c = Child() # 實例化子類
c.childMethod() # 調用子類的方法
c.parentMethod() # 調用父類方法
c.method() # 調用覆寫的方法

1.1.6 多重繼承

class A: # 定義類 A
pass
class B: # 定義類 B
pass
class C(A, B): # 繼承類 A 和 B
pass

檢測函數:

  • issubclass() - 布爾函數判斷一個類是另一個類的子類或者子孫類,語法:issubclass(sub,sup)
  • isinstance(obj, Class) 布爾函數如果obj是Class類的實例對象或者是一個Class子類的實例對象則返回true。
print(issubclass(C,A))
print(issubclass(C,B))
print(issubclass(A,B))
print(issubclass(A,C))
True
True
False
False
objA, objB, objC = A(), B(), C()
print(isinstance(objA, C),isinstance(objA, B))
print(isinstance(objB, A),isinstance(objB, C))
print(isinstance(objC, A),isinstance(objC, B))
False False
False False
True True
  • 代碼實戰:
# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
# 類定義
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))
# 單繼承示例
class student(people):
grade = ''
def __init__(self, n, a, w, g):
# 調用父類的構函
people.__init__(self, n, a, w)
self.grade = g
# 覆寫父類的方法
def speak(self):
print("%s 說: 我 %d 歲了,我在讀 %d 年級" % (self.name, self.age, self.grade))
# 另一個類,多重繼承之前的准備
class speaker():
topic = ''
name = ''
def __init__(self, n, t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一個演說家,我演講的主題是 %s" % (self.name, self.topic))
# 多重繼承
class sample(speaker, student):
a = ''
def __init__(self, n, a, w, g, t):
student.__init__(self, n, a, w, g)
speaker.__init__(self, n, t)
test = sample("Tim", 25, 80, 4, "Python")
test.speak() # 方法名同,默認調用的是在括號中排前地父類的方法

  • 如果父類A和父類B中,有一個同名的方法,那麼通過子類去調用的時候,調用哪個?
# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
# 類定義
class base(object):
def test(self):
print('----base test----')
class A(base):
def test(self):
print('----A test----')
# 定義一個父類
class B(base):
def test(self):
print('----B test----')
# 定義一個子類,繼承自A、B
class C(A,B):
pass
obj_C = C()
obj_C.test()
print(C.__mro__) #可以查看C類的對象搜索方法時的先後順序

總結&&重點:

  1. 多繼承優先級:從左往右,優先級由高到低。
  2. 如果繼承的兩個父類有同樣的方法,那麼會選擇優先級高的方法,即子類會優先使用最先被繼承的方法。

1.1.7 方法重寫

  • 對於父類的方法,只要它不符合子類模擬的實物的行為,都可對其進行重寫。為此,可在子
    類中定義一個這樣的方法,即它與要重寫的父類方法同名。這樣,Python將不會考慮這個父類方
    法,而只關注你在子類中定義的相應方法。

  • 多繼承中super調用所有父類的被重寫的方法。super本質是用mor算法(後面會講這是啥玩意)的順序調用的。

  • 父類也稱為基類。

# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
class Parent: # 定義父類
def myMethod(self):
print('調用父類方法')
class Child(Parent): # 定義子類
def myMethod(self):
print('調用子類方法')
c = Child() # 子類實例
c.myMethod() # 子類調用重寫方法
super(Child, c).myMethod() # 用子類對象調用父類已被覆蓋的方法

1.1.8 結合一個代碼講一下

# -*- coding: utf-8 -*-
""" __author__ = 孤寒者 """
class Base:
def play(self):
print('這是Base')
class A(Base):
def play(self):
print('這是A')
super().play() # 這裡調用的是Base
class B(Base):
def play(self):
print('這是B')
super().play() # 這裡調用的是Base
class C(A, B):
# 當子類繼承父類之後,如果子類不想使用父類的方法,可以通過重寫來覆蓋父類的方法
def play(self):
print('這是C')
# #重寫父類方法之後,如果又需要使用父類的方法:
# B().play() #第一種方法 實例化,再使用方法
# A.play(self) #第二種方法 類名使用這個方法
super().play() # 這裡調用的是A
c = C()
c.play()
print(C.__mro__) # 可以通過調用類的__mro__屬性或者mro方法來查看類的繼承關系
# 輸出為(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
# super可以使用父類的方法;在父類中也可以使用super函數;要滿足mro算法規則
# 根據mro的順序來決定調用的是誰
# 在python3中,類被創建時會自動創建方法解析順序mro

1.1.9 類屬性與方法

類的私有屬性

  • __private_attrs:兩個下劃線開頭,聲明該屬性為私有,不能在類的外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs

類的方法

  • 在類的內部,使用def關鍵字可以為類定義一個方法,與一般函數定義不同,類方法必須包含參數self,且為第一個參數。

類的私有方法

  • __private_method:兩個下劃線開頭,聲明該方法為私有方法,不能在類地外部調用。在類的內部調用 self.__private_methods
# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
class JustCounter:
__secretCount = 0 # 私有變量
publicCount = 0 # 公開變量
def count(self):
self.__secretCount += 1
self.publicCount += 1
print(self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print(counter.publicCount)
print(counter.__secretCount) # 報錯,實例不能訪問私有變量

  • Python不允許實例化的類訪問私有數據,但你可以使用 object._className__attrName 訪問屬性,但是強烈建議不要這麼干,因為不同版本的Python解釋器可能會把__attrName改成不同的變量名。
  • 將如下代碼替換以上代碼的最後一行代碼:
print(counter._JustCounter__secretCount)

  • __foo__: 定義的是內置方法,類似 __init__() 之類的。

  • _foo: 以單下劃線開頭的表示的是 protected 類型的變量
    即保護類型只能允許其本身與子類進行訪問,不能用於 from module import *

  • __foo: 雙下劃線的表示的是私有類型(private)的變量, 只能是允許這個類本身進行訪問了。

1.1.10 getattr、setattr及hasattr

僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態:

# -*- coding: utf-8 -*-
""" __author__ = 小小明-代碼實體 """
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()

緊接著,可以測試該對象的屬性:

>>> hasattr(obj, 'x') # 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19) # 設置一個屬性'y'
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
True
>>> getattr(obj, 'y') # 獲取屬性'y'
19
>>> obj.y # 獲取屬性'y'
19

如果試圖獲取不存在的屬性,會拋出AttributeError的錯誤:

>>> getattr(obj, 'z') # 獲取屬性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

可以傳入一個default參數,如果屬性不存在,就返回默認值:

>>> getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認值404
404

也可以獲得對象的方法:

>>> hasattr(obj, 'power') # 有屬性'power'嗎?
True
>>> getattr(obj, 'power') # 獲取屬性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>>
>>> fn = getattr(obj, 'power') # 獲取屬性'power'並賦值到變量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>>
>>> fn() # 調用fn()與調用obj.power()是一樣的
81

通過內置的一系列函數,我們可以對任意一個Python對象進行剖析,拿到其內部的數據。要注意的是,只有在不知道對象信息的時候,我們才會去獲取對象信息。如果可以直接寫:

sum = obj.x + obj.y

就不要寫:

sum = getattr(obj, 'x') + getattr(obj, 'y')

一個正確的用法的例子如下:

def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
  • 假設我們希望從文件流fp中讀取圖像,我們首先要判斷該fp對象是否存在read方法,如果存在,則該對象是一個流,如果不存在,則無法讀取。hasattr()就派上了用場。
  • 請注意,在Python這類動態語言中,有read()方法,不代表該fp對象就是一個文件流,它也可能是網絡流,也可能是內存中的一個字節流,但只要read()方法返回的是有效的圖像數據,就不影響讀取圖像的功能。

1.1.11 講一下super()函數

這個玩意非常常見又非常常用,但是又不知道怎麼講更容易理解,如果我直接拿個項目代碼來講,大家可能有點接受不了,後來突然就想到了《Python編程從入門到實戰》這本書裡的這部分,講的很透徹,都是很基礎的知識來講解的,所以就貼出來:

1.1.12 導入類

  • Python允許你將類存儲在模塊中,然後在主程序中導入所需的模塊。

  • 將上述Car 類存儲在一個名為car.py的模塊中,使用該模塊的程序都必須使用更具體的文件名,如my_car.py,其中只包含 Car 類的代碼。
    我們包含了一個模塊級文檔字符串(在該模塊的最上面),對該模塊的內容做了簡要的描述。你應為自己
    創建的每個模塊都編寫文檔字符串。

下面來創建另一個文件——my_car.py,在其中導入 Car 類並創建其實例:

from car import Car #從一個只包含一個類的模塊中導入這個類
from car import ElectricCar #從一個包含多個類的模塊中導入一個類
from car import Car, ElectricCar #從一個有多個類的模塊中導入多個類
import car #導入整個模塊 我們使用語法 module_name.class_name 訪問需要的類
from module_name import * #導入模塊中的所有類 (不建議使用,如果要用,推薦使用導入整個模塊)

import 語句讓Python打開模塊 car ,並導入其中的 Car 類。這樣我們就可以使用 Car 類了,
就像它是在這個文件中定義的一樣。


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved