本篇文章主要介紹面向對象編程。
面向對象編程
幾個定義
- 面向過程編程:圍繞函數,編寫能處理數據的代碼塊的編程方式。
- 類、對象:面向對象編程的兩個主要方面。一個類(Class)能創建一種新的類型;對象(Object)是類的實例。可這樣類比:你可擁有類型 int 的變量,也即,存儲整數的變量是 int類 的對象實例。
- 字段、方法和屬性:從屬于對象或類的變量叫字段(Field);對象調用從屬于類的函數實現某些功能,這些函數叫類的方法(Method);字段和方法統稱為累的屬性(Attribute)。
Tips: 不同于靜態語言,Python 中即使是整數也會被視為對象(int類的對象),同 Java 中裝箱與拆箱概念頗為相似。
類和對象
最簡單的類結構如下所示:
class Student(object):
pass # 一個空的代碼塊
# 創建類對象實例
student = Student()
print(student)
# 打印:<__main__.Student object at 0x101985f28>
*Tips: *
- Python 中所有類均繼承自 object;
- Python 打印對象,默認可查看對象地址。Python 會在它找到的任何空間來存儲對象。
類屬性和實例屬性
方法(Method)
Python 對比于 Java 類方法屬性,有兩個特殊點:
- 類中定義的所有方法,第一個參數必須是 self;相當于 Java 中的 this 引用;
- _init_() 方法:在類的對象被實例化時立即運行;相當于 Java 中的構造方法。
Tips: Python中不存在方法的重載,方法的重載是對同一個方法使用可變參數來實現,這在后面函數參數這塊會提到。
示例代碼如下:
class Student(object):
count = 0 # 類變量
def __init__(self, name):
self.name = name # 實例變量
def say_hi(self):
print(r'Hello, I'm', self.name)
student = Student('Coral')
student.say_hi()
# 打印:Hello, I'm Coral
字段(Field)
Python 中字段有兩種類型——類變量與對象變量,對比上述示例代碼,兩者的區別點如下圖所示:
因為 Python 是動態語言,可以給類創建的實例動態綁定屬性,那么如何給 實例 綁定屬性?如何給 類 綁定屬性呢?
1)給實例綁定屬性
- 通過在 _init_ 方法中給 self 綁定屬性,這種屬性所有對象實例均可訪問;
- 直接給實例綁定,這種屬性只適用于該實例,其他實例不能訪問該屬性。如:
student = Student('Coral')
# grade是給實例動態綁定的屬性,name是通過 self綁定的屬性
student.grade = 90
print('{} grade = {} '.format(student.name, student.grade))
student2 = Student('Jane')
print(student2.grade)
# 這句會拋出:AttributeError: 'Student' object has no attribute 'grade'
2)給類綁定屬性
要讓同一個字段屬性對所有實例均適用,除了 self 綁定,還可以適用 類屬性。
- 類屬性,直接在 class 中定義屬性,歸整個 Student 類所有,如 count 字段。
Tips: 當 實例屬性 和 類屬性 同名時,實例屬性 優于 類屬性,也即,實力屬性會覆蓋類屬性,所以在項目中要避免使用這種同名變量。
訪問限制
Java 中有四種訪問修飾符,Python 中并沒有指定具體的訪問修飾符,僅是按照一種約定的寫法來,如下圖所示:
獲取對象信息
在 Java 中通常會判斷一個對象是什么類型,用 instanceOf 來判斷,Python 中也有判斷對象類型的方法,還可以獲取對象擁有的屬性和方法,這些都是 Python 內置的方法。常見的如下圖所示:
這里重點說下 isinstance() 方法,示例如下:
a = list() # a 是 list 類型
b = Animal() # b 是 Animal 類型
c = Dog() # c 是 Dog 類型
# 1.判斷一個變量是否是某個類型可以用 isinstance() 判斷:
print('a is list:', isinstance(a, list)) # True
print('b is Animal:', isinstance(b, Animal)) # True
print('c is Dog:', isinstance(c, Dog)) # True
print('c is Animal:', isinstance(c, Animal)) # True
# 2. 能用 type() 判斷的基本類型也可以用 isinstance() 判斷:
print(isinstance('a', str)) # True
print(isinstance(123, int)) # True
print(isinstance(b'a', bytes)) # True
# 3. 另外,isinstance() 還可以判斷一個變量是否是某些類型中的一種:
# 判斷變量是否是 list 或 tuple.
def is_list_or_tuple(x):
return isinstance(x, (list, tuple))
Tips: 區別于 type,對于 class 繼承關系,不便使用 type();要判斷 class 類型,使用 isinstance() 函數。因為 type() 只能返回對象本身的類型,比如 type(dog) != type(animal), 但是 isinstance(dog, Dog) = isinstance(dog, Animal)。
繼承和多態
Python 中允許多繼承,沒有接口實現的概念。繼承和多態總結點如下:
Python 中沒有方法的重載,但是存在方法的覆蓋,子類可以覆蓋父類方法,也適用于運行時綁定傳入的實例。
關于繼承的幾個問題
- 子類 能否繼承 父類綁定的 屬性?(每個類通過 self 或者 給實例動態綁定的 屬性都是該類實例 獨立擁有,跟子類沒有任何關聯?)
A: 子類不能繼承 父類中通過 self或者 動態綁定的屬性; - 子類 是否只能繼承 父類綁定的 類屬性?
A:子類能繼承 父類中的 類屬性,在操作該類屬性時,使用 ChildClass.property 訪問該類屬性; - 子類可以繼承 父類的所有方法?
A:Python 中方法沒有 訪問限制符修飾; - 每個類通過 self 或者 給實例動態綁定的 屬性都是該類實例 獨立擁有,跟子類沒有任何關聯?
A:是。 - 子類和父類有同名的 屬性,優先使用哪個?(避免這種情況,直接繼承使用父類的屬性)
A:子類優先,但最好不要讓子類屬性和父類屬性同名 。 - 子類能否繼承父類中 __xxx 隱藏屬性?
A:這個可以看做是每個類通過 self 綁定的屬性均屬于本類所有,父類中通過 self 綁定的屬性不管是否是隱藏屬性,子類直接訪問就會拋出:AttributeError: 'Dog' object has no attribute '_BaseClass__name' .