Python之路 - 面向對象初識
本章內容
- 編程範式介紹
- 類與實例
- 屬性與方法
- 構造函數
- 命名空間
- 屬性(靜態和動態) 與類的關系
- 對象交互與類的組合
一、編程範式
編程是程序員用 特定的語法 + 數據結構 + 算法組成的代碼來告訴計算機如何執行任務的過程 , 而實現一個任務的方式有很多種不同的方式 , 對這些不同的編程方式的特點進行歸納總結得出來的編程方式類別,即為編程範式
-
面向過程編程 Procedural Programming
面向過程編程就是程序從上到下一步步執行 , 基本設計思路就是程序一開始是要著手解決一個大的問題 , 然後把一個大問題分解成很多個小問題或子過程 , 這寫子過程再執行的過程再繼續分解直到小問題足夠簡單到可以在一個小步驟範圍內解決
在Python中 , 我們通過把大段代碼拆成函數 , 通過一層一層的函數調用 , 就可以把復雜任務分解成簡單的任務 , 這種分解可以稱之為面向過程的程序設計 . 函數就是面向過程的程序設計的基本單元
-
函數式編程 Functional Programming
函數式編程就是一種抽象程度很高的編程範式 , 純粹的函數式編程語言編寫的函數沒有變量 , 函數式編程的一個特點就是 , 允許把函數本身作為參數傳入另一個函數 , 還允許返回一個函數 , Python對函數式編程提供部分支持 . 由於Python允許使用變量 , 因此 , Python不是純函數式編程語言
-
面向對象編程 Object Oriented Programming
面向對象編程是利用"類"和"對象"來創建各種模型來實現對真實世界的描述 , 使用面向對象編程的原因一方面是因為它可以使程序的維護和擴展變得更簡單 , 並且可以大大提高程序開發效率 , 另外 , 基於面向對象的程序可以使它人更加容易理解你的代碼邏輯 , 從而使團隊開發變得更從容
二、類與實例
類的語法
class 類名: pass
一個栗子??
# 創建一個人的‘類‘,首字母要大寫 class Person(object): # 構造函數,初始化屬性 def __init__(self,name): self.name = name # 人可以吃飯 def eat(self): print("I am eatting") # 創造了一個叫做‘Lyon‘的人 p = Person(‘Lyon‘) # 執行吃飯功能 p.eat() # 執行結果: I am eatting
-
類 (class)
類就是 對現實生活中一類具有共同特征事物的抽象
類起到一個模板的作用 , 當我們創建一個類時 , 就相當於創建了一個初始的‘模型‘ , 我們可以通過這個‘模型‘ 來創建出一個個具有相同特征或功能的事物 , 來幫助我們更好的處理問題
在上述栗子中類名Person 後有一個(object)
, 這是新式類的寫法 , 而在python3.x 以上的版本中 , 默認為新式類 , 所以也可直接 class Person:
我們創建類時 , 都默認繼承了object類 , object詳解見後期文章
-
實例 (instance)
我們知道類是一個抽象 , 既然是抽象那就是不可操作的 , 所以我們如果進行操作 , 就需要將這一抽象的概念變成具體的事物 , 這個過程我們稱為實例化
實例化: 由抽象的類轉換成實際存在的對象的過程
實例: 由類進行實例化所得到的對象
, 上述栗子中的 p
就是一個實例
三、屬性與方法
屬性是實體的描述性質或特征 , 比如人有名字 , 年齡 , 性別等 . 當然還有人所能做的事情也是一種屬性 , 比如吃飯 , 睡覺 , 喝水等 . 對於這兩種屬性 , 一種是表示特征的 , 叫做靜態屬性 , 另一種則是表示功能的 , 叫做動態屬性
在Python中 , 我們將靜態屬性 就稱為屬性
, 將動態屬性 就稱為方法
, 並且以變量來表示屬性 , 以函數表示方法 , 見下圖:
PS:類中的函數已經不叫函數了 , 而叫做方法
屬性包括 : 實例變量 , 類變量
調用方式: 類名 . 屬性名
class Person: # 類變量 role = ‘student‘ # 構造函數 def __init__(self,name): # 實例變量 self.name = name
方法包括 : 普通方法 , 屬性方法 , 類方法 , 靜態方法
調用方式: 類名 . 方法名( )
class Person: # 普通方法 def eat(self): pass
特殊的類屬性
屬性名 |
說明 |
---|---|
__dict__ |
查看類或對象成員 , 返回一個字典 |
__name__ |
查看類的名字 |
__doc__ |
查看類的描述信息 , 即註釋部分 |
__base__ |
查看第一個父類 |
__bases__ |
查看所有父類 , 返回一個元組 |
__module__ |
查看類當前所在模塊 |
__class__ |
查看對象通過什麽類實例化而來 |
PS:對於屬性和方法 , 在網上分類各種各樣的都有 , 比如字段 , 還有菜鳥教程中的一些 , 其實本質上都是一個東西
四、構造函數
在上述例子中 , 可以看到有一個__init__ 方法 , 這個方法叫做構造方法 , 用於初始化屬性 , 所以如果我們要設置屬性 , 那麽構造方法是必須要的
self
我們直接通過實例來說明
class Foo: def __init__(self,name): self.name = name def func(self): print(id(self)) a = Foo(‘Lyon‘) # 打印實例a的內存地址 print(id(a)) # 調用類中的func方法,即打印self的內存地址 a.func() ‘‘‘ 執行結果: 1703689404544 1703689404544 結果分析: 我們發現a的內存地址和self的內存地址是一樣的,也就是說self其實就是實例本身 那麽在我們進行實例化的時候,self.name = name 就是給實例添加一個name屬性,該屬性的值就是我們在實例化時傳入的‘Lyon‘ 所以如果我們需要給對象添加屬性的話,可以直接通過 對象.屬性名 = 屬性值 的方式進行添加 ‘‘‘
將上栗子中的構造函數再換個姿勢看看
a = Foo(‘Lyon‘) # 等價於如下,用類名調用類中的方法 Foo.__init__(a,‘Lyon‘) # Python解釋器會幫我們自動觸發__init__方法,所以再如下 Foo(a,‘Lyon‘)
五、命名空間
在函數中 , Python解釋器在執行時 , 會將函數名稱依次加載到命名空間 , 類當然也一樣
我們創建一個類時 , Python解釋器一執行就會創建一個類的命名空間 , 用來存儲類中定義的所有名稱( 屬性和方法 ) , 而我們進行實例化時 , Python解釋器又會為我們創建一個實例命名空間 , 用來存放實例中的名稱
當我們利用 類名. 名稱
來訪問對象屬性 ( 靜態與動態 ) 時 , Python解釋器會先到該對象的命名空間中去找該名稱 , 找不到就再到類 ( 該對象實例化之前的類 ) 的命名空間中去找 , 最後如果都沒找到 , 那麽就拋出異常了
訪問屬性實例
class A(object): """ 這是一個類 """ pass a = A() # 訪問實例a的__doc__屬性 print(a.__doc__) #執行結果: 這是一個類
解釋說明: 對於實例a本身是肯定沒有__doc__ 屬性的 , 這毋庸置疑 , 因為我們根本就沒有使用構造函數來增加實例屬性 . 根據執行結果顯示 , 我們是訪問到了這個類中的__doc__ 屬性 , 那麽你會說這個類也沒看見 __doc__ 屬性啊 , 其實類A是有的 , 因為它繼承了object類 , 至於object類是什麽 , 它裏面有什麽 ? 看後續文章吧
六、屬性(靜態和動態)與類的關系
由於Python是動態語言 , 所以Python的賦值機制都是通過動態綁定來實現的
- 類屬性共享給所有對象
先實例後說明
class Foo: # 定義一個類變量,特意用的容器類型來說明 name = [‘Lyon‘] # 實例化 a = Foo() b = Foo() # 訪問a,b中的name屬性 print(‘實例a中的name屬性:‘, a.name) print(‘實例b中的name屬性:‘, b.name) # 查看a,b,Foo中name屬性的內存地址 print(id(a.name)) print(id(b.name)) print(id(Foo.name)) print(‘------------------‘) # 修改類變量 Foo.name = ‘a‘ # 再次訪問a,b中的name屬性 print(‘實例a中的name屬性:‘, a.name) print(‘實例b中的name屬性:‘, b.name) # 修改a中的name屬性? 不,是新增 a.name = [‘Lyon‘] # 再次查看a,b中name屬性的內存地址 print(id(a.name)) print(id(b.name)) ‘‘‘ 執行結果: 實例a中的name屬性: [‘Lyon‘] 實例b中的name屬性: [‘Lyon‘] 2247471754696 2247471754696 2247471754696 ------------------ 實例a中的name屬性: a 實例b中的name屬性: a 2247471792392 2247471754696 ‘‘‘
說明:
-
特意使用容器類型來進行實驗 , 因為在Python中容器類型內存地址一樣只有一個原因 , 那就是兩者作用的是同一個對象
-
我們第一步查看內存地址時 , a, b, Foo三者中的name屬性的內存地址是一樣的 , 實例可以通過
實例.類變量名
的方式進行訪問 , 並且所有實例都共享類屬性name -
a.name = [‘Lyon‘]
這一步其實並不是修改a中的name屬性 , 要知道name屬性是類的並不是實例的 , 執行這一步會為實例a加上一個新的同名name屬性 , 由於賦值綁定會將原來訪問類屬性name的通道破壞掉 , 但是並不會影響b對類屬性name的訪問
-
類中的方法是綁定到所有對象的
先實例後說明
class Foo: def func(self): pass a = Foo() b = Foo() # 打印a,b中func的內存地址 print(a.func) print(b.func) # id返回的是10進制表示的內存地址,轉換成16進制 print(hex(id(a))) print(hex(id(b))) ‘‘‘ 執行結果: <bound method Foo.func of <__main__.Foo object at 0x000001A7D2F74080>> <bound method Foo.func of <__main__.Foo object at 0x000001A7D3759898>> 0x1a7d2f74080 0x1a7d3759898 ‘‘‘
說明:
-
方法名與內存地址存在一個映射關系 , 通過執行結果我們可以發現 , a.func所在的內存地址與a的內存地址是一樣的 , 則說明func綁定到了a中
-
a.func 與b.func 的內存地址是不一樣的 , 因為每一個實例都開辟了自己內存空間 , func綁定進去的位置自然不一樣
-
實例本身的屬性是實例獨有的
我們通過類創建一個實例 , 就會在內存中新開辟一塊內存空間來存放這個實例的所有屬性 , 實例屬性一旦創建 , 基本跟類就沒有什麽太大的關系了 . 如果要修改實例屬性那麽就只能通過實例來進行修改了 , 並且實例與實例之間也是互不幹擾的
如下圖中 , 類與實例 , 實例與實例 都開辟了自己的內存空間
七、對象交互與類的組合
對象交互
class Person: def __init__(self, name): self.name = name def attack(self,per): print("{} attacked {}".format(self.name, per.name)) lyon = Person("Lyon") kenneth = Person("kenneth") lyon.attack(kenneth) # 執行結果: Lyon attacked kenneth
類的組合
傳參時組合
class BirthDate: def __init__(self, year, month, day): self.year = year self.month = month self.day = day class Person: def __init__(self, name, birthdate): self.name = name self.birthdate = birthdate p = Person(‘Lyon‘, BirthDate(2000, 1, 1))
定義時組合
class BirthDate: def __init__(self, year, month, day): self.year = year self.month = month self.day = day class Person: def __init__(self, name, year, month, day): self.name = name self.birthdate = BirthDate(year, month, day) p = Person(‘Lyon‘, 2000, 1, 1)
Python之路 - 面向對象初識