1. 程式人生 > 其它 >Python語言之面向物件

Python語言之面向物件

Python語言之面向物件

前言

人生苦短,我用Python。

一、面向物件程式設計概述

在現實世界中存在各種不同形態的事物,這些事物之間存在著各種各樣的聯絡。在程式中使用物件來對映現實中的事物,使用物件間的關係來描述事物之間的聯絡,這種思想就是面向物件程式設計(OOP,Object Oriented Programming)。

例如,分別使用面向過程和麵向物件來實現五子棋:

二、類和物件

類和物件的關係

  • 面向物件程式設計有兩個非常重要的概念:類和物件。

  • 物件是面向物件程式設計的核心。

  • 具有相似特徵和行為的事物的集合統稱為類。

  • 物件是根據類建立的,一個類可以對應多個物件。

可以把玩具模型看作一個類,把每個玩具看作一個物件。

類的定義

類是由3部分組成的:

  • 類的名稱:類名,首字母必須大寫,比如Person。

  • 類的屬性:一組資料,比如姓名、性別。

  • 類的方法:允許進行操作的方法,比如說話。

使用class關鍵字來宣告一個類,基本格式如下:

class 類名: #類名首字母大寫
    類的屬性
    類的方法(self)

例:

class Car:
    price = 0
    def getSpeed(self):
        return 100

car = Car()
print(car.getSpeed())

根據類建立物件

根據類建立物件的語法格式如下:

物件名 = 類名()

要想給物件新增屬性,可以通過如下方式:

物件名.新的屬性名 = 值

例:

class Car:
    price = 0

car = Car()  #例項化
print(f'car.price:{car.price}')  #輸出價格
car.price = 10  #修改價格
print(f'car.price:{car.price}')  
car.color = 'blue'  #修改顏色
print(f'car.color:{car.color}')
Car.price = 100  #不例項化情況下修改價格
print(f'Car.price:{Car.price}')

缺點:難以對屬性進行管理,使用混亂

改進:區分類屬性和例項屬性,私有變數和公有變數

類、物件的屬性和方法

  • 類屬性:定義在所有函式外邊,不需要例項化直接進行使用,使用方法是 類名.屬性。

  • 物件屬性:定義在建構函式__init__中,只有例項化後才可使用,使用方法是 物件.屬性。

  • 私有屬性和私有方法:在屬性名或者函式名前加入2個下劃線__。

  • 定義成員方法(成員函式):在類內定義時必須有引數self,否則會報錯。

例:

class Fruit:
    price=0                                        #定義一個類屬性
    def __init__(self):                            #建構函式
        self.color="red"                           #例項屬性,以self為字首
        zone="China"                               #區域性變數,不以self為字首
        self.__buying_price=1                      #私有屬性,以self.__為字首
if __name__=="__main__":                           
    print(Fruit.price)                             #使用類名呼叫類變數   0
    apple=Fruit()                                  #例項化apple
    print(apple.color)                             #列印apple例項的顏色  red
    Fruit.price += 10                              #將類變數+10
    print("apple price:"+str(apple.price))         #改變類屬性將會影響物件屬性的值,列印apple例項的price   10
    banana=Fruit()                                 #例項化banana
    print("banana price:"+str(banana.price))       #列印banana例項的price    10       
    #print(banana.zone)    #無法訪問區域性變數
    #print("apple buying price:"+str(apple.__buying_price))         #禁止訪問私有變數
class Fruit:
    price=0                                        #定義一個類屬性
    def __init__(self):                            #建構函式
        self.__color="red"                           #例項屬性,以self為字首
        zone="China"                               #區域性變數,不以self為字首
        self.__buying_price=1                      #私有屬性,以self.__為字首
    def setColor(self, new_color):
        if new_color=="white":
            return
        else:
            self.__color=new_color
    def getColor(self):
        return self.__color
if __name__=="__main__":
    apple=Fruit()                                  #例項化apple
    apple.setColor("white")                  #修改apple例項的顏色  white
    print(apple.getColor())                   #檢視修改結果(失敗)
    apple.setColor("yellow")                #修改apple例項的顏色  yellow
    print(apple.getColor())                   #檢視修改結果(成功)

三、構造方法和析構方法

  • 構造方法指的是__init__方法。

  • 當建立類的例項的時候,系統會自動呼叫構造方法,從而實現對類進行初始化的操作。

class Car:
    def __init__(self, color):
        self.color = color
        print("%s的車在鳴笛..."%(self.color))
bmw = Car("雪山白")
  • 當刪除一個物件來釋放類所佔用資源的時候,Python直譯器預設會呼叫另外一個方法,這個方法就是__del__( )方法。

  • __del__方法被稱為析構方法。

class Car:
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print(f'{self.name} will be deleted')

car = Car("賓利")
while True:
    pass

四、self的使用

  • 在方法的列表中,第1個引數永遠都是self。

  • self的字面意思是自己,我們可以把它當做C++裡面的this指標理解,表示的是物件自身。

  • 當某個物件呼叫方法的時候,Python直譯器會把這個物件作為第1個引數傳給self,開發者只需要傳遞後面的引數就可以了。

class Dog:
    def __init__(self, color):
        self.color = color
    def printColor(self):
         print("顏色為:%s"%self.color)
dog1 =  Dog("白色")
dog1.printColor()

五、運算子過載

運算子過載

運算子過載是通過實現特定的方法使類的例項物件支援Python的各種內建操作 。例如:+運算子是類裡提供的__add__這個函式,當呼叫+實現加法運算的時候,實際上是呼叫了__add__方法。

 方法                 說明                何時呼叫方法
__add__             加法運算           物件加法:x+y,x+=y
__sub__             減法運算           物件減法:x-y,x-=y
__mul__             乘法運算           物件乘法:x*y,x*=y
__diy__             除法運算           物件除法:x/y,x/=y
__getitem__         索引,分片         x[i]、x[i:j]、沒有__iter__的for迴圈等
__setitem__         索引賦值           x[i]=值、x[i:j]=序列物件
__delitem__         索引和分片刪除      del x[i]、del x[i:j]

加法運算子過載

加法運算是通過呼叫__add__方法完成過載的,當兩個例項物件執行加法運算時,自動呼叫__add__方法。

z = x+y,執行加法運算,實質是呼叫__add__方法

class Cat:
    def __init__(self,name):
        self.name = name
    def __add__(self, other):
        print('運算子被過載 ')
        print(f'{self.name} and  {other.name}')
cat1 = Cat('mimi')
cat2 = Cat('miaomiao')
print(cat1 + cat2)

索引和分片過載

跟索引相關的過載方法包括如下3個:

  • getitem:索引、分片;

  • setitem:索引賦值;

  • delitem:索引和分片刪除。

1. __getitem__方法

在對例項物件執行索引、分片或者for迭代操作時,會自動呼叫__getitem__方法。

# 定義索引、分片運算子過載方法
    def __getitem__(self, index):
        return self.data[index]

2. __setitem__方法

通過賦值語句給索引或者分片賦值時,呼叫__ setitem __方法實現對序列物件的修改。

def __setitem__(self, index, value):
        self.data[index] = value

3. __delitem__方法

當呼叫del方法時,實質上會呼叫__delitem__方法實現刪除操作。

def __delitem__(self, index):
        del self.data[index]

六、封裝

面向物件三大特徵:封裝、繼承、多型

封裝可以被認為是一個保護屏障,防止該類的程式碼和資料被外部類定義的程式碼隨機訪問。

要訪問該類的程式碼和資料,必須通過嚴格的介面控制。

封裝最主要的功能在於我們能修改自己的實現程式碼,而不用修改那些呼叫我們程式碼的程式片段。

封裝的優點:

  1. 良好的封裝能夠減少耦合。

  2. 類內部的結構可以自由修改。

  3. 可以對成員變數進行更精確的控制。

  4. 隱藏資訊,實現細節。

為了保護類裡面的屬性,可以採用如下方式解決:

  1. 把類中變數定義為私有變數,即在屬性名的前面加上兩個下劃線;

  2. 由於私有變數只能被類中的成員函式修改和訪問,所以需要新增用於設定和獲取屬性值的兩個方法供外界呼叫。

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    def setAge(self, new_age):
        if new_age<0 or new_age>160:
            return
        else:
            self.__age = new_age
    def getAge(self):
        return self.__age
laowang = Person('老王', 30)
laowang.setAge(300)
print(laowang.getAge())
laowang.setAge(80)
print(laowang.getAge())

通過封裝可以實現對成員變數值的控制

laowang = Person('老王', 30)
laowang.__age=800
print(laowang.__age)

因為給laowang.__age賦值時,laowang.__age無法訪問,所以新建了一個公開變數laowang.__age,但是這個公開變數不被getAge函式承認,這也是getAge函式存在的意義。

七、繼承

繼承在程式中,繼承描述的是事物之間的所屬關係。

單繼承

類的繼承是指在一個現有類的基礎上構建一個新的類,構建出來的新類被稱作子類。

Python程式中,繼承使用如下語法格式標註:

class 子類名(父類名):

假設有一個類為A,A派生出來了子類B,示例如下:

class B(A):
class A(object):

預設是繼承自object的

class Animal(object):
    def __init__(self,food):
        self.eatFood = food
    def eat(self):
        print('It likes eating ' + self.eatFood)
c = Cat('fish')
c.eat()
m = Monkey('banana')
m.eat()
class Cat(Animal):
    pass

class Monkey(Animal):
    pass

注意:子類不重寫__init__,例項化子類時,會自動呼叫父類定義的__init__,但重寫了__init__時,例項化子類,就不會呼叫父類已經定義的__init__

多繼承

現實生活中,一個派生類往往會有多個基類。比如沙發床是沙發和床的功能的組合,這都是多重繼承的體現。

Python支援多繼承,多繼承就是子類擁有多個父類,並且具有它們共同的特徵,即子類繼承了父類的方法和屬性。

多繼承可以看做是單繼承的擴充套件,語法格式如下:

class 子類名(父類1,父類2…):

如果子類繼承的多個父類間是平行的關係,子類先繼承的哪個類就會呼叫哪個類的方法。

class Sofa():
    def __init__(self):
        self.sit = True
        print("繼承Sofa")
class Bed():
    def __init__(self):
        self.sleep = True
        print("繼承Bed")
class SofaBed(Sofa,Bed):
    def __init__(self):
        Sofa.__init__(self)
        Bed.__init__(self)
        print(f'I can sit on the sofa:{self.sit}')
        print(f'I can sleep in the bed:{self.sleep}')

s = SofaBed()

注意:如果想繼承父類的物件屬性,則需要在子類的構造方法中,顯示地呼叫父類的構造方法。

重寫父類方法和呼叫父類方法

  • 在繼承關係中,子類會自動擁有父類定義的方法,但是有時子類想要按照自己的方式實現方法,即對父類中繼承來的方法進行重寫,使得子類中的方法覆蓋掉跟父類同名的方法。

  • 需要注意的是,在子類中重寫的方法要和父類被重寫的方法具有相同的方法名和引數列表。

class Sofa():
    def sit(self):
        print("可以坐著")

class Bed():
    def sleep(self):
        print("可以躺著")
class SofaBed(Sofa,Bed):
    '''
    def sit(self):
        print("葛優躺")
    '''        
s = SofaBed()
s.sit()
s.sleep()

八、多型

多型的目的:一個介面,多種實現

class People(object):
    def say(self):
        pass
# A
class American(People):
    def say(self):
        print("Hello")
# B
class Chinese(People):
    def say(self):
        print("你好")
def greet(obj):
    obj.say()
a = American()
c = Chinese()
greet(a)
greet(c)

九、類屬性和例項屬性

類屬性是類所擁有的屬性,它需要在類中進行顯示地定義(位於類內部,方法的外面),當使用類.屬性名方式使用時它被所有類的例項物件所共有,在記憶體中只存在一個副本。

類屬性示例程式碼:

class Cat(object):
    #類屬性
    num = 0

通過“例項.屬性”新增屬性的屬性都是例項屬性。

例項屬性示例:

class Cat(object):
    num = 0
c = Cat()
#例項屬性
c.newNum = 1
class Cat(object):
    num = 0
    def __init__(self):
        # 例項屬性
        self.name = 'mimi'
class AAA():
    aaa = 10

# 情形1
obj1 = AAA()
obj2 = AAA()
print(obj1.aaa, obj2.aaa, AAA.aaa)

# 情形2
obj1.aaa += 2
print(obj1.aaa, obj2.aaa, AAA.aaa)

# 情形3
AAA.aaa += 3
print(obj1.aaa, obj2.aaa, AAA.aaa)

十、類方法和例項方法

類方法

class 類名:
    @classmethod
    def 類方法名(self):
        方法體

要想呼叫類方法,既可以通過物件名.函式名方式呼叫類方法,又可以通過類名.函式名呼叫類方法,這兩種方法沒有任何區別。

class Cat:
    name = 'mimi'
    @classmethod
    def fun(self):
        print(self.name)
c = Cat()
#物件名.函式名
c.fun()
#類名.函式名
Cat.fun()

注意:類方法只能使用類變數,因為不用例項化就可以使用。所以呼叫物件變數會報錯。

靜態方法

使用修飾器@staticmethod來標識靜態方法。

class 類名:
    @staticmethod
    def 靜態方法名():
        方法體
  • 靜態方法是沒有self引數,在靜態方法中無法訪問例項變數。

  • 靜態方法中不可以直接訪問類屬性,但是可以通過類名引用類屬性。

  • 靜態方法跟定義它的類沒有直接關係,只是起到了類似函式的作用。

class Cat:
    eyes = 2
    @staticmethod
    def look():
        #通過類名引用類屬性
        print(Cat.eyes)
c = Cat()
c.look()
#類名.函式名
Cat.look()

結尾