天天看點

Python面向對象程式設計(Object Oriented Programming,OOP)之結構、成員和屬性

面向對象結構

面向對象類中的資料大緻分為兩塊區塊:

class Demo:
    name = '張三'  # 第一部分:靜态字段(靜态變量)
    age = 18  # 第一部分:靜态字段(靜态變量)
    address = '北京'  # 第一部分:靜态字段(靜态變量)

    def __init__(self):  # 第二部分:動态字段(方法)部分
        pass

    def func(self):  # 第二部分:動态字段(方法)部分
        pass
           
結論:分為靜态字段(變量)和動态字段(方法)兩部分

每個部分還可以分為多個小部分:

class Demo:
    idNum = '111111111'  # 靜态變量
    __address = '北京'  # 私有靜态變量

    def __init__(self, name, age, height):  # 構造方法,也屬于普通方法
        self.name = name
        self.__age = age
        self.__height = height

    def func1(self):  # 普通方法
        pass

    def __func2(self):  # 私有方法
        pass

    @classmethod
    def class_func(cls):  # 類方法,至少含有一個cls參數
        print("類方法")

    @staticmethod  # 靜态方法,無預設參數
    def static_func():
        print("靜态方法")

    @property  # 屬性
    def property_func(self):
        return "屬性"
           

類有這麼多的成員,那麼我們先從那些地方研究呢? 可以從私有與公有部分,方法的詳細分類兩個方向去研究。

私有與公有

對于每一個類的成員而言都有兩種形式:

  • 公有成員,在任何地方都能通路。
  • 私有成員,隻有在類的内部才能方法。

私有和公有成員通路權限不同:

靜态字段(靜态變量)

  • 公有靜态字段:類可以通路;内部類可以通路;派生類(子類)可以通路。
  • 私有靜态字段:僅内部類可以通路。
class DemoA:
    name1 = '公有字段'
    __name2 = '私有字段'

    def func1(self):  # 内部類通路
        print((DemoA.name1))  # 可以通路
        print(DemoA.__name2)  # 可以通路


class DemoB(DemoA):
    def show1(self):  # 派生類通路
        print(DemoA.name1)  # 可以通路
        # print(DemoA.__name2) # 不可以通路


c1 = DemoA()
c1.func1()
c2 = DemoB()
c2.show1()
           

結果:

公有字段

私有字段

公有字段

Process finished with exit code 0

普通字段(對象屬性)

  • 公有普通字段:對象可以通路;類内部可以通路;派生類中可以通路。
  • 私有普通字段:僅類内部可以通路。
class DemoA:
    def __init__(self):
        self.foo1 = '公有字段'
        self.__foo2 = '私有字段'

    def func1(self):  # 内部類通路
        print(self.foo1)  # 可以通路
        print(self.__foo2)  # 可以通路


class DemoB(DemoA):
    def show1(self):  # 派生類通路
        print(self.foo1)  # 可以通路
        # print(self.__foo2) # 不可以通路,報錯


c1 = DemoA()
c1.func1()

c2 = DemoB()
c2.show1()
           

結果:

公有字段

私有字段

公有字段

Process finished with exit code 0

方法:

  • 公有方法:對象可以通路;類内部可以通路;派生類中可以通路。
  • 私有方法:僅類内部可以通路。
class DemoA:
    def __init__(self):
        pass

    def add1(self):
        print("超類公有方法")

    def __add2(self):
        print("超類私有方法")

    def add3(self):
        self.__add2()  # 本類調用自己的私有方法


class DemoB(DemoA):
    def show1(self):
        print("派生類公有方法")

    def __show2(self):
        print("派生類私有方法")

    # def show3A_add2(self):
    #     self.__add2  # 派生類不能引用超類私有方法,報錯

    def show4B_show2(self):
        self.__show2()  # 派生類可以引用自身的私有方法,屬于自己調用


c1 = DemoA()
c1.add1()  # 調用自身公有方法
# c1.add2()  # 外部無法調用私有方法,報錯
c1.add3()  # 外部間接調用類中私有方法

c2 = DemoB()
c2.add1()  # 派生類調用超類公有方法
# c2.add2()  # 派生類無法調用超類私有方法,報錯
c2.add3()  # 派生類調用自身公有方法
c2.show1()  # 派生類調用自身公有方法
# c2.show2()  # 派生類在外部無法調用自身私有方法,報錯
# c2.show3A_add2()  # 派生類不能引用超類私有方法,報錯
c2.show4B_show2()  # 派生類間接調用父類私有方法
           

結果:

超類公有方法

超類私有方法

超類公有方法

超類私有方法

派生類公有方法

派生類私有方法

Process finished with exit code 0

總結:

對于這些私有成員來說,他們隻能在類的内部使用,不能再類的外部以及派生類中使用。

成員

字段

字段包括:普通字段和靜态字段,他們在定義和使用中有所差別,而最本質的差別是記憶體中儲存的位置不同。

普通字段屬于對象,靜态字段屬于類。
class Demo:
    country = '中國'  # 靜态字段

    def __init__(self, name):
        self.name = name  # 普通字段


obj = Demo('北京')

# 直接通路普通字段
print(obj.name)
print(obj.country)

# 直接通路靜态字段
print(Demo.country)
           

結果:

北京

中國

中國

Process finished with exit code 0

結論:

普通字段需要通過對象來通路,也可以使用使用對象來通路。

靜态字段通過類通路,類不能直接通路普通字段。

注意:靜态字段在記憶體中隻儲存一份,普通字段在每個對象中都要儲存一份。

方法

方法包括:普通方法、靜态方法和類方法,三種方法在記憶體中都歸屬于類,差別在于調用方式不同。

  • 普通方法:由對象調用;至少一個self參數;執行普通方法時,自動将調用該方法的對象指派給self。
  • 類方法:由類調用; 至少一個cls參數;執行類方法時,自動将調用該方法的類複制給cls。
  • 靜态方法:由類調用,無預設參數。
class Foo:

    def __init__(self, name):
        self.name = name

    def ord_func(self):
        """ 定義普通方法,至少有一個self參數 """
        print('普通方法')

    @classmethod
    def class_func(cls):
        """ 定義類方法,至少有一個cls參數,cls表示這個類 """
        print('類方法')

    @staticmethod
    def static_func():
        """ 定義靜态方法 ,無預設參數"""
        print('靜态方法')


# 調用普通方法
f = Foo(1)  # 随便寫一個實參,無實際意義
f.ord_func()

# 調用類方法
Foo.class_func()

# 調用靜态方法
Foo.static_func()
           

結果:

普通方法

類方法

靜态方法

Process finished with exit code 0

這些方法的差異:

相同點:對于所有的方法而言,均屬于類(非對象)中,是以,在記憶體中也隻儲存一份。

不同點:方法調用者不同、調用方法時自動傳入的參數不同。

屬性(property)

具有通路它時會執行一段功能(函數)然後傳回值的特性。

例:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們将其做成一個屬性,更便于了解) 成人的BMI數值:

  • 過輕:低于18.5
  • 正常:18.5-23.9
  • 過重:24-27
  • 肥胖:28-32
  • 非常肥胖, 高于32   

體質指數(BMI)=體重(kg)÷身高^2(m),例:70kg÷(1.75×1.75)=22.86。

class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)


p1 = People('張三', 70, 1.75)
print(p1.bmi)
           

結果:

22.857142857142858

Process finished with exit code 0

ps:這是本人的BIM值,唉~又胖了啊。

使用屬性的好處:

将一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後計算出來的,這種特性的使用方式遵循了統一通路的原則。

由于新式類(Python3中全是新式類)中具有三種通路方式,我們可以根據他們幾個屬性的通路特點,分别将三個方法定義為對同一個屬性:擷取、修改、删除。

class people:  # 定義一個人的類
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex  # p1.sex = "male",遇到property,優先用property

    @property  # 檢視sex的值
    def sex(self):
        return self.__sex  # 傳回正真存值的地方

    @sex.setter  # 修改sex的值
    def sex(self, value):
        if not isinstance(value, str):  # 在設定值之前進行類型檢查
            raise TypeError("性别必須是字元串類型")  # 不是str類型時,主動抛出異常
        self.__sex = value  # 類型正确的時候,直接修改__sex的值,這是值正真存放的地方
        # 這裡sex前加"__",對sex變形,隐藏。

    @sex.deleter  # 删除sex
    def sex(self):
        del self.__sex


p1 = people("張三", "男")  # 執行個體化對象p1
print(p1.sex)  # 檢視p1的sex,此時要注意self.sex的優先級
p1.sex = "女"  # 修改sex的值
print(p1.sex)  # 檢視修改後p1的sex
print(p1.__dict__)  # 檢視p1的名稱空間,此時裡面有sex
del p1.sex  # 删除p1的sex
print(p1.__dict__)  # 檢視p1的名稱空間,此時發現裡面已經沒有sex了
           

結果:

{'name': '張三', '_people__sex': '女'}

{'name': '張三'}

Process finished with exit code 0