(python)面向物件
一、面向物件概述
要了解面向物件,就需要先了解面向過程的概念,那麼什麼是面向過程程式設計呢?最具代表性的就是C語言了,所謂面向過程程式設計就是在做一件事的時候,需要按步驟進行,第一步幹什麼,第二步幹什麼,這種程式設計方式適合問題規模較小,需要步驟化處理邏輯的業務。
瞭解了面向過程程式設計,那麼就容易理解面向物件程式設計了,官方解釋是一種認識世界、分析世界的方法論,將萬事萬物都抽象為類的方法。這種概念較為抽象不方便理解,以生活中的示例為例,所謂面向物件就是你吃魚,你屬於一個物件(例項),魚也屬於一個物件(例項),那麼吃就是動作(方法)了。我們可以將具體的事物抽象成物件(例項)(人、魚是有無數具體的個體抽象出來的),將動作抽象成方法,這種抽象方式就是面向物件程式設計,而python使用的正是這種程式設計方式。
之前面向物件有兩個概念,類和物件。那麼什麼是類呢,類是一種抽象的概念,是萬事萬物的抽象,是一類事物共同特徵的集合,也就是屬性和方法的集合。現在又出來了屬性和方法,所謂屬性就是物件狀態的抽象,用資料結構來描述,通俗來講每個人都有名字、身高體重等資訊,這些資訊是個人的屬性,但是,這些資訊不屬於特定的某一個人,它是抽象的概念,不能保留具體的值;所謂方法就是物件行為的抽象,用操作名和實現該操作的方法來描述,通俗來講就是類中解決某一實現方法的功能。什麼是物件呢?物件就是抽象的類的具體的例項,一個類可以有多個物件(例項),每個物件都有其特有的屬性,可以通過具體的例項來實現自己需要實現的方法。
二、面向物件的三要素
1、封裝
將資料和操作組裝到一起,對外只暴露一些介面,通過介面去訪問物件。通俗來講就是你駕駛一輛汽車,不需要去了解汽車的構造細節,只需要知道使用什麼部件怎麼駕駛就可以了。
2、繼承
可以繼承父類的某些方法和屬性,也可以重寫父類的某些方法和屬性實現個性化,多複用性。通俗來講就是人類繼承自動物類,孩子繼承父母的特徵。有單一繼承和多繼承。
3、多型
繼承自動物類的人類,人類的“吃”操作和貓類的不同。這是面向物件程式設計最靈活的地方。
三、類的抽象
一、類的定義
1、語法:
1 class ClassName: 2 語句塊
- 使用class關鍵字來定義一個類
- 類名必須使用大駝峰的方式來進行命名(每個單詞的首字母大寫)
- 類在定義完成後,就產生了一個類物件,繫結到了識別符號ClassName上
2、舉例
1 class MyClass: 2 """A example class""" 3 x = "abc" # 類屬性,類變數 4 5 def foo(self): # 類屬性,類方法 6 return "My Class" 7 8 9 print(MyClass.x) # abc 10 print(MyClass.foo) # <function MyClass.foo at 0x000000E545931840> 11 print(MyClass.__doc__) # A example class
- 類物件:類的定義就會生成一個類物件。
- 類屬性:類中的變數和方法都是類的屬性。
- 可以通過類名直接呼叫其中的屬性,x、foo都是類的屬性,__doc__也是屬性。
- foo是方法物件method,不是普通的函式物件function了,它一般要求至少有一個引數,第一個引數就是self(識別符號,可以換其他名字,但是不建議換,規範),這個引數位置就留給了self,self指代當前例項本身。
四、例項化
抽象出來的類,可以通過具體的例項來實現特有的屬性和方法,下面來演示實現類的例項化。
a = ClassName() # 例項化
使用上面的語法,就呼叫了類的例項化方法,完成例項化,建立了一個該類的物件(例項),例如:
tom = Person()
jerry = Person()
上面的tom、jerry都是類Person的例項,它們兩個是不同的例項,即使使用同樣的引數例項化,得到的也是不同的物件。
在例項化之後,會自動呼叫__init__方法進行初始化。
1、__init__方法
對例項進行初始化,MyClass()實際上呼叫的就是__init__(self)方法,可以不去定義,它會隱式去呼叫這個方法。演示如下:
class MyClass: def __init__(self): print("例項化之後呼叫init方法") MyClass() # 例項化之後呼叫init方法
init可以傳遞多個引數,注意第一個位置必須是self,並且不能有返回值。可以通過例項化後的物件去呼叫類中的屬性:
class Person: def __init__(self, name, age): self.name = name # 將例項化傳遞進來的引數賦值給類屬性 self.age = age def showage(self): print("{} is {}".format(self.name, self.age)) # 通過self可以獲取該類的屬性 tom = Person("tom", 22) # 例項化 jerry = Person("jerry", 23) # 通過例項化後的物件可以呼叫其屬性 print(tom.name, jerry.name) # tom jerry jerry.age += 1 print(jerry.age) # 24 jerry.showage() # jerry is 24
2、例項變數和類變數
例項變數是每一個例項自己獨有的,通過類名是訪問不到的;類變數是所有例項共享的屬性和方法,其例項都可以訪問:
class Person: age = 3 # 類變數 def __init__(self, name): self.name = name # 例項變數 tom = Person("tom") jerry = Person("jerry") print(tom.name, tom.age) # tom 3 print(jerry.name, jerry.age) # jerry 3 Person.age = 30 print(Person.age, tom.age, jerry.age) # 30 30 30
3、物件的特殊屬性
每一個物件都擁有不同的屬性,函式、類都是物件,類的例項也是物件。
特殊屬性 | 含義 |
__name__ | 物件名 |
__class__ | 物件的型別 |
__dict__ | 物件的屬性的字典 |
__qualname__ | 類的限定名 |
舉例如下:
class Person: age = 3 # 類變數 def __init__(self, name): self.name = name # 例項變數 # 類的屬性 print(Person.__class__.__name__) # type print(sorted(Person.__dict__.items())) # [('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x0000008C5F541840>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)] # 例項的屬性 tom = Person("tom") print(tom.__class__.__name__) # Person print(sorted(tom.__dict__.items())) # [('name', 'tom')] # 通過例項訪問類的屬性 print(tom.__class__.__name__) # Person print(sorted(tom.__class__.__dict__.items())) # [('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x0000008C5F541840>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
例項屬性的查詢順序:例項會先找自己的__dict__,如果沒有然後通過屬性__class__找到自己的類,再去類的__dict__中去找。
五、類裝飾器
本質上是為類物件動態增加了一個屬性,而Person這個識別符號指向這個類物件。
def add_name(name): def wrapper(cls): cls.name = name return cls return wrapper @add_name("tom") class Person: age = 3 print(Person.name) # tom
六、類方法和靜態方法
靜態方法,不需要例項。靜態方法主要是用來存放邏輯性的程式碼,主要是一些邏輯屬於類,但是和類本身沒有互動,即在靜態方法中,不會涉及到類中的方法和屬性的操作。可以理解為將靜態方法存在此類的名稱空間中。
類方法是將類本身作為物件進行操作的方法。他和靜態方法的區別在於:不管這個方式是從例項呼叫還是從類呼叫,它都用第一個引數把類傳遞過來。
class Person: AGE = 20 @classmethod def class_method(cls): print("class method") cls.AGE = 170 @staticmethod def static_method(): print("static method") Person.class_method() # class method Person.static_method() # static method print(Person.__dict__) # {'__doc__': None, 'static_method': <staticmethod object at 0x0000008CC02BAEF0>, 'AGE': 170, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Person' objects>, 'class_method': <classmethod object at 0x0000008CC029D470>}
七、訪問控制
1、私有變數
使用雙下劃線開頭的屬性名,就是私有屬性。將屬性設為私有,讓外部訪問不到,其本質就是將該屬性名改名,改為(_類名__變數名),使用原來的屬性名就訪問不到了。那麼如何去訪問這個私有屬性呢,可以通過方法來訪問該私有屬性。
class Person: def __init__(self, name, age=18): self.name = name self.__age = age def growup(self, i=1): if i>0 and i<100: self.__age += 1 # 私有屬性通過方法去訪問 def getage(self): return self.__age tom = Person("tom") tom.growup(20) # print(tom.__age) # AttributeError: 'Person' object has no attribute '__age' print(tom.__dict__) # {'name': 'tom', '_Person__age': 19} print(tom.getage()) # 19
2、私有方法
私有方法的本質和私有變數的本質一樣,只是將方法的名稱改變(_類名__方法名)
class Person: def __init__(self, name, age=18): self.name = name self.__age = age # 保護方法,約定,沒有改名 def _getage(self): return self.__age # 私有方法,改名 def __getage(self): return self.__age tom = Person("tom") print(tom._getage()) # 18 # print(tom.__getage()) # AttributeError: 'Person' object has no attribute '__getage' print(tom.__class__.__dict__) # {'__dict__': <attribute '__dict__' of 'Person' objects>, 'growup': <function Person.growup at 0x0000000935BC1A60>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, '__init__': <function Person.__init__ at 0x0000000935BC1840>, '_Person__getage': <function Person.__getage at 0x0000000935BC1AE8>, '__module__': '__main__'} print(tom._Person__getage()) # 18
3、保護變數、方法
在變數或者方法名前面加上單下劃線,就是保護變數、方法。這種變數或者方法可以直接訪問,並沒有改名,這只是開發者共同的約定,看到這種變數就如同私有變數,不要直接使用。
class Person: def __init__(self, name, age=18): self.name = name self._age = age # 保護變數 # 保護方法,約定,沒有改名 def _getage(self): return self._age tom = Person("tom") print(tom._age) # 18 print(tom._getage()) # 18 print(tom.__dict__) # {'_age': 18, 'name': 'tom'} print(Person.__dict__) # {'__module__': '__main__', '__doc__': None, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__init__': <function Person.__init__ at 0x000000E3DB071840>, '_getage': <function Person._getage at 0x000000E3DB071A60>}
八、補丁
在執行時,對屬性、方法、函式等進行動態替換。補丁往往是為了通過替換,修改來增強、擴充套件原有程式碼的能力。
# t1(原始碼) class Person: def get_score(self): ret = {"English": 78, "Chinese": 100, "History": 89} return ret
# t2(打補丁) from t_class.t1 import Person # 要修改的內容 def get_score(self): return dict(name=self.__class__.__name__, English=88, Chinese=38, History=100) # 打補丁 def monkeypatchPerson(): Person.get_score = get_score if __name__ == '__main__': monkeypatchPerson() print(Person().get_score())
九、屬性裝飾器
一般好的設計是將例項的屬性保護起來,不讓外部去直接訪問,外部使用getter和setter方法去獲取和設定屬性。
class Person: def __init__(self, name, age): self.name = name self.__age = age @property def age(self): return self.__age @age.setter def age(self, age): self.__age = age @age.deleter def age(self): del self.__age print("del age") tom = Person("tom", 22) print(tom.age) # 22 tom.age = 18 print(tom.age) # 18 del tom.age # del age
- 使用property裝飾器的時候這三個方法同名
- property裝飾器:它就是getter,這個必須有,有了它至少是隻讀屬性
- setter裝飾器:接收兩個引數,第一個是self,第二個是要修改的值,有了它屬性可寫
- deleter裝飾器:刪除屬性,很少用