作者介紹:【孤寒者】—CSDN全棧領域優質創作者、HDZ核心組成員、華為雲享專家Python全棧領域博主、CSDN原力計劃作者
- 本文已收錄於Python全棧系列專欄:《Python全棧基礎教程》
- 熱門專欄推薦:《Django框架從入門到實戰》、《爬蟲從入門到精通系列教程》、《爬蟲高級》、《前端系列教程》、《tornado一條龍+一個完整版項目》。
- 本專欄面向廣大程序猿,為的是大家都做到Python從入門到精通,同時穿插有很多很多習題,鞏固學習。
- 訂閱專欄後可私聊進一千多人Python全棧交流群(手把手教學,問題解答);進群可領取Python全棧教程視頻 + 多得數不過來的計算機書籍:基礎、Web、爬蟲、數據分析、可視化、機器學習、深度學習、人工智能、算法、面試題等。
- 加入我一起學習進步,一個人可以走的很快,一群人才能走的更遠!
__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)
魔法方法:每一個魔法方法都有它特定的功能。
__init__
魔法方法。析構,也叫銷毀初始化方法(銷毀上面的那個),是在這個文件運行完然後觸發。(魔法方法)
__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)
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
__del__
方法面向對象的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過繼承機制。繼承完全可以理解成類之間的類型和子類型關系。
繼承還可以一級一級地繼承下來,就好比從爺爺到爸爸、再到兒子這樣的關系。而任何類,最終都可以追溯到根類object,這些繼承關系看上去就像一顆倒著的樹。比如如下的繼承樹:
繼承可以把父類的所有功能都直接拿過來,這樣就不必從零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫;
這樣我們就可以處理功能的迭代更新,以及拓展重用代碼,方便代碼的管理和修改。
有了繼承,才能有多態。在調用類實例方法的時候,盡量把變量視作父類類型,這樣,所有子類類型都可以正常被接收。
小知識點(其實前面講過):類名.__bases__
可以查看類的父類有哪些,注意要print。
語法:
class SubClassName (ParentClass1[, ParentClass2, ...]):
'Optional class documentation string'
class_suite
在python中繼承中的一些特點:
__init__()
方法)不會被自動調用,它需要在其子類的構造中親自專門調用。代碼實戰:
# -*- 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() # 調用覆寫的方法
class A: # 定義類 A
pass
class B: # 定義類 B
pass
class C(A, B): # 繼承類 A 和 B
pass
檢測函數:
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() # 方法名同,默認調用的是在括號中排前地父類的方法
# -*- 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類的對象搜索方法時的先後順序
總結&&重點:
對於父類的方法,只要它不符合子類模擬的實物的行為,都可對其進行重寫。為此,可在子
類中定義一個這樣的方法,即它與要重寫的父類方法同名。這樣,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() # 用子類對象調用父類已被覆蓋的方法
# -*- 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
類的私有屬性
__private_attrs
:兩個下劃線開頭,聲明該屬性為私有,不能在類的外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs
。類的方法
類的私有方法
__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) # 報錯,實例不能訪問私有變量
object._className__attrName
訪問屬性,但是強烈建議不要這麼干,因為不同版本的Python解釋器可能會把__attrName
改成不同的變量名。print(counter._JustCounter__secretCount)
__foo__
: 定義的是內置方法,類似 __init__()
之類的。
_foo
: 以單下劃線開頭的表示的是 protected 類型的變量
即保護類型只能允許其本身與子類進行訪問,不能用於 from module import *
__foo
: 雙下劃線的表示的是私有類型(private)的變量, 只能是允許這個類本身進行訪問了。
僅僅把屬性和方法列出來是不夠的,配合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
這個玩意非常常見又非常常用,但是又不知道怎麼講更容易理解,如果我直接拿個項目代碼來講,大家可能有點接受不了,後來突然就想到了《Python編程從入門到實戰》這本書裡的這部分,講的很透徹,都是很基礎的知識來講解的,所以就貼出來:
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 類了,
就像它是在這個文件中定義的一樣。