1. 程式人生 > >(python)面向物件

(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裝飾器:刪除屬性,很少用