python物件導向程式設計——作用域,多繼承【重點難點】

2020-08-10 00:12:50

12.物件導向程式設計

物件導向技術簡介

  • 類(Class): 用來描述具有相同的屬性和方法的物件的集合。它定義了該集閤中每個物件所共有的屬性和方法。物件是類的範例。
  • **方法:**類中定義的函數。
  • **類變數:**類變數在整個範例化的物件中是公用的。類變數定義在類中且在函數體之外。類變數通常不作爲範例變數使用。
  • **數據成員:**類變數或者範例變數用於處理類及其範例物件的相關的數據。
  • **方法重寫:**如果從父類別繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱爲方法的重寫。
  • **區域性變數:**定義在方法中的變數,只作用於當前範例的類。
  • **範例變數:**在類的宣告中,屬性是用變數來表示的,這種變數就稱爲範例變數,範例變數就是一個用 self 修飾的變數。
  • **繼承:**即一個派生類(derived class)繼承基礎類別(base class)的欄位和方法。繼承也允許把一個派生類的物件作爲一個基礎類別物件對待。例如,有這樣一個設計:一個Dog型別的物件派生自Animal類,這是模擬"是一個(is-a)"關係(例圖,Dog是一個Animal)。
  • **範例化:**建立一個類的範例,類的具體物件。
  • **物件:**通過類定義的數據結構範例。物件包括兩個數據成員(類變數和範例變數)和方法。

類的定義

class Person:
    name = "張三"
    age = 3  # 定義在這裏的屬性叫類屬性,所有這個類的範例物件都會共有這個屬性相當於java中的靜態變數

    def sayHello(self):
        print("hello")


person = Person()


print(person.name)
print(person.age)
person.sayHello()

p2 = Person()
print(p2.name)
print(p2.age)

類屬性的存取與修改

class Person:
    name = "張三"
    age = 3

    def sayHello(self):
        print("hello")


# 類屬性可以通過類名.屬性名的方式來存取和修改,範例化物件要想對類屬性進行修改要通過
# 範例物件想要修改類屬性必須通過 person.__class__.name  的方式進行存取
Person.name = "諸葛亮"
person = Person()
person.__class__.name = "張飛"
print(Person.name)

範例的屬性

class Person:

    def __init__(self, name, age):  # __init__方法是一個魔術方法,當呼叫改造方法時會自動呼叫該方法
        self.name = name  # 通過self.定義的屬性都是範例屬性,self指範例物件本身相當於java中的this關鍵字
        self.age = age


person1 = Person("張三", 3)
person2 = Person("李四", 5)
person3 = Person("王五", 9)

print(person1.name, person1.age)
print(person2.name, person2.age)
print(person3.name, person3.age)

類方法

class Person:

    @classmethod  # 通過這個裝飾器裝飾的方法都是類方法,相當於Java中的靜態方法,可以通過 類名.方法 的形式呼叫
    def sayHello(self):
        print("hello")


Person.sayHello()

虛假的私有屬性與君子約束

class Person:

    def __init__(self, name, age):
        self.__name = name  # 通過 self.__變數名定義的屬性是私有屬性,無法直接通過.直接方法,需要定義set和get方法
        self.__age = age

    def getName(self):
        return self.__name

    def getAge(self):
        return self.__age

    def setName(self, name):
        self.__name = name

    def setAge(self, age):
        self.__age = age


person = Person("張三", 3)

print(person.getName())
print(person.getAge())

"""
其實在python中並沒有真正的私有屬性
雖然不能通過.屬性名的方式存取
但是可以通過 obj._類名__屬性名 的形式進行直接存取,所以python中的私有屬性只是一種君子約束,一般我們只用一個下劃線表示不希望被修改即可
"""
person._Person__name = "諸葛亮"
print(person.getName())

通過裝飾器對get方法和Set方法優化

class Person:

    def __init__(self):
        pass

    @property  # 通過這個裝飾器可以將方法屬性化
    def name(self):
        return self.__name

    @property
    def age(self):
        return self.__age

    @name.setter  # 可以設定 setter  deleter  getter ...
    def name(self, name):
        print("setName方法被呼叫")
        self.__name = name

    @age.setter
    def age(self, age):
        print("setAge方法被呼叫")
        self.__age = age


person = Person()
person.name = "張三"  # 這樣就可以通過像設定屬性一樣呼叫方法了
person.age = 3

print(person.name)
print(person.age)

繼承(重點)

  • Python 同樣支援類的繼承,如果一種語言不支援繼承,類就沒有什麼意義。
class Person:

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def func1(self):
        print("我是Person")


class Student(Person):

    def __init__(self, name, age, num):
        Person.__init__(self, name, age)  # 呼叫父類別構造器
        self._num = num  # 子類自己的屬性

    def study(self):  # 子類自己的方法
        print("好好學習天天向上")


student = Student("張三", 3, 123456)
print(student._name, student._age, student._num)
student.func1()
student.study()
  • 此外與java不同的是,python是一門多繼承的語言
  • python的多繼承,多繼承的構造方法可以根據需要進行重寫
class Person:

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def func1(self):
        print("我是Person")


class Son(Person):

    def __init__(self, name, age, gender):
        Person.__init__(self, name, age)
        self._gender = gender

    def son(self):
        print("在老子面前永遠是兒子")


class Student(Person):

    def __init__(self, name, age, num):
        Person.__init__(self, name, age)  # 呼叫父類別構造器
        self._num = num  # 子類自己的屬性

    def study(self):  # 子類自己的方法
        print("好好學習天天向上")


class Me(Son, Student):

    # 如果不寫__init__方法就會預設基礎第一個繼承類的構造
    def __init__(self, name, age, gender, num, gift):
        Son.__init__(self, name, age, gender)
        Student.__init__(self, name, age, num)
        self._gift = gift

    def me(self):
        print("我就是我顏色不一樣的煙火")


me = Me("孫悟空", 756, "男", 123, "七十二變")

print(isinstance(me, Person))  # isinstance(a,b) 判斷a是不是b的範例
print(isinstance(me, Student))
print(isinstance(me, Son))
print(me._name)
print(me._age)
print(me._gender)
print(me._gift)
me.func1()
me.study()
me.son()
me.me()

方法重寫

  • 如果你的父類別方法的功能不能滿足你的需求,你可以在子類重寫你父類別的方法,範例如下:
\#!/usr/bin/python3  
class Parent:        # 定義父類別    
    def myMethod(self):      
        print ('呼叫父類別方法')   
class Child(Parent): # 定義子類    
    def myMethod(self):       
        print ('呼叫子類方法')   
        c = Child()          # 子類範例 
        c.myMethod()         # 子類呼叫重寫方法 
        super(Child,c).myMethod() #用子類物件呼叫父類別已被覆蓋的方法

類的那些內建的魔術方法

類的專有方法:

  • _init_ : 建構函式,在生成物件時呼叫
  • _del_ : 解構函式,釋放物件時使用
  • _repr_ : 列印,轉換
  • _setitem_ : 按照索引賦值
  • _getitem_: 按照索引獲取值
  • _len_: 獲得長度
  • _cmp_: 比較運算
  • _call_: 函數呼叫
  • _add_: 加運算
  • _sub_: 減運算
  • _mul_: 乘運算
  • _truediv_: 除運算
  • _mod_: 求餘運算
  • _pow_: 乘方

重寫這些方法會在特定的條件下被呼叫,例如常用的_del_ 會在對生命週期結束時被回收時呼叫

_len_ 在呼叫len()函數時會被呼叫等

如果一個類同時繼承了多個類有多個方法相同應該繼承誰的呢?(難點)

在这里插入图片描述

如圖所示是一個非常複雜的類繼承關係圖,程式碼展示如下

class A:
    def __init__(self,a):
        self.a = a
        
    def Hello(self):
        print("helloA")

class B:
    def __init__(self,a):
        self.a = a
        
    def Hello(self):
        print("helloB")
        
        
class B:
    def __init__(self,a):
        self.a = a
        
    def Hello(self):
        print("helloC")
        
class D(A):
    def __init__(self,a,b):
        A.__init__(self,a)
        self.b = b  
        
    def Hai(self):
        print("HiD")
        
class E(B,C):
    def __init__(self,a,b):
        C.__init__(self,a)
        self.b = b
        
    def Hello(self):
        print("HelloC")
        
    def Hai(self):
        print("HiE")

class F(D,E,C):
    def __init__(self,a,b,c):
        D.__init__(self,a,b)
        self.c = c
        
  • 此時F的呼叫構造方法,首先它會在自己的類中找有沒有重寫 __init__ 方法,如果有則呼叫該方法
  • 然後範例分別呼叫Hello()和Hai() 方法該呼叫誰的方法呢?
    1. 首先這個範例會在F類裡找,看看有沒有重寫這兩個方法,發現沒有重寫這兩個方法,這時候會到D類裡找
    2. 如果D類裡恰巧有這兩個方法,則會直接使用D類的這兩個方法,如果D類不存在該重寫方法,則會去D類的父類別也就是A類中尋找,如果在A類中找到了對應的方法就會使用A類的方法,如果沒找到就會去object類裡找。
    3. 如果在Object類中沒有找到纔會去E類中找,如果E類中沒找到就會去E類的父類別中找,E類的第一個父類別中沒找到就會去找第二個父類別,然後一直找到Object
    4. 全部都沒找到纔會去C類中找,知道找到爲止,如果找完了所有的類都沒有找到該方法,則會報錯
    5. F類找方法的路線解析:(在尋找路線的任任何一個類中找到了匹配的方法將不會繼續往後邊尋找。)
      • F => D => A => Object => E => B => C => Object => C => Object