【Python】5.物件導向的Python

GWH_98發表於2020-10-05

Python是一種物件導向程式設計的語言,Python中幾乎都是物件,簡單數值型別,程式碼模組,可以說是萬物皆物件。例如對於一個數值物件:

>>> type(1)
<class 'int'>
>>> dir(1)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

一、物件導向程式設計

物件導向程式設計的思想將物件作為程式的基本單元,物件=資料(屬性)+一套訪問和操作這些資料的方法。就像在引言中說的,Python中所有的資料型別都可以被視為物件我們也可以自定義物件,即物件導向中類的概念。
從程式導向的程式設計到物件導向的程式設計,從語句到函式到類,抽象程度不斷變高,但是更符合我們日常中的概念。
以學生成績表為例,語句表達:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

通過函式列印:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

將物件資料:學生成績,對資料處理的方法:列印,整合在一起就是一個類。

class Student(object):

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

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

給物件發訊息實際上就是呼叫物件對應的關聯函式,即物件的方法:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

物件導向程式設計的好處主要有三個方面:
1)多型:可對不同型別的物件執行相同的操作。
2)封裝:對外部隱藏有關物件工作原理的細節。
3)繼承:可基於通用類建立出專用類。

二、類和物件

1.什麼是類和物件

類是一種物件的統稱,每個物件都屬於特定的類,物件是類的例項,是Python中程式的基本單元,要建立物件,就首先要建立一個類。
通過賦值語句可以給物件賦予名稱,名稱可以有很多個但是id只有一個。

a = complex(1,1)

2.自定義類:建立與呼叫

1)class語句和類的初始化

class <類名>:
  def _init_(self,<參數列>):
  def <方法名>(self,<參數列>)

_init_()是一個特殊的函式名,用於根據類的定義建立例項物件,第一個引數必須是self。
2)呼叫類和方法

obj = <類名>(<參數列>)
obj.<方法>()

類方法中的self就是obj物件例項本身

#在Python中約定,類名大寫字母開頭,函式小寫字母開頭
class Person: 
 def set_name(self, name): 
 self.name = name 
 def get_name(self): 
 return self.name 
 def greet(self): 
 print("Hello, world! I'm {}.".format(self.name))

>>> foo = Person() #先建立Person(),再將foo名稱與之關聯
>>> bar = Person() 
>>> foo.set_name('Luke Skywalker') 
>>> bar.set_name('Anakin Skywalker') 
>>> foo.greet() 
Hello, world! I'm Luke Skywalker. 
>>> bar.greet() 
Hello, world! I'm Anakin Skywalker.

3.屬性、函式和方法

方法和函式的區別表現在引數self上,方法中的第一個引數關聯到它所屬的例項,呼叫的時候無需這個引數,可以在類的外部將方法關聯到一個普通的函式,通過這種方法,也可以實現普通函式對類中self例項的訪問。

>>> class Class: 
... def method(self): 
... print('I have a self!') 
... 
>>> def function(): 
... print("I don't...") 
... 
>>> instance = Class() 
>>> instance.method() I have a self! 
>>> instance.method = function 
>>> instance.method() I don't...
>>> class Bird: 
... song = 'Squaawk!' 
... def sing(self): 
... print(self.song) 
... 
>>> bird = Bird() 
>>> bird.sing() 
Squaawk! 
>>> birdsong = bird.sing 
>>> birdsong() 
Squaawk!

4.類名稱空間

在class語句中定義的程式碼都是在一個特殊的名稱空間(類的名稱空間)內執行的,而類的所有成員都可訪問這個名稱空間,例如:

class MemberCounter: 
 members = 0 
 def init(self): 
 MemberCounter.members += 1 
>>> m1 = MemberCounter() 
>>> m1.init() 
>>> MemberCounter.members 
1 
>>> m2 = MemberCounter() 
>>> m2.init() 
>>> MemberCounter.members 
2

如果給例項中的屬性members賦值,那麼該值將被寫入m1的一個屬性中,這個屬性遮住了類級變數。m1中的屬性將被覆蓋為定值(類似於外部傳遞進來的實參值覆蓋類名稱空間內的全域性變數),類中的操作不在影響該變數,但m1中的方法仍會影響其他例項中的該屬性(類級變數)。

>>> class MemberCounter:
	members = 0
	def init(self):
		MemberCounter.members += 1
>>> m1 = MemberCounter()
>>> m1.init()
>>> m1.members
1
>>> m1.members=3
>>> m1.members
3
>>> m2= MemberCounter()
>>> m2.init()
>>> m2.members
2
>>> m1.init()
>>> m1.members
3
>>> m2.members
3
>>> m2.init()
>>> m2.members
4

三、繼承

1.繼承

1)定義一個class的時候,可以從某個現有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。
例如:Dog類和Cat類都繼承自Animal類。

class Animal(object):
    def run(self):
        print('Animal is running...')
class Dog(Animal):
    pass

class Cat(Animal):
    pass

2)通過這種方法,子類可以獲得父類全部的屬性和方法,並可以新增,或者重寫已有的方法。

class Filter: 
  def init(self): 
  self.blocked = [] 
  def filter(self, sequence): 
  return [x for x in sequence if x not in self.blocked] 
class SPAMFilter(Filter): # SPAMFilter是Filter的子類
  def init(self): # 重寫超類Filter的方法init 
  self.blocked = ['SPAM']
>>> f = Filter() 
>>> f.init() 
>>> f.filter([1, 2, 3]) 
[1, 2, 3]
>>> s = SPAMFilter() 
>>> s.init() 
>>> s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM']) 
['eggs', 'bacon']

2.繼承查詢

1)要確定一個類是否是另一個類的子類,可使用內建方法issubclass

>>> issubclass(SPAMFilter, Filter) 
True 
>>> issubclass(Filter, SPAMFilter) 
False

2)查詢基類,可訪問其特殊屬性 __bases__;查詢物件屬於哪個類,可使用屬性__class__:

>>> SPAMFilter.__bases__ 
(<class __main__.Filter at 0x171e40>,) 
>>> Filter.__bases__ 
(<class 'object'>,)

3)確定物件是否是特定類的例項,可使用isinstance:

>>> s = SPAMFilter() 
>>> isinstance(s, SPAMFilter) 
True 
>>> isinstance(s, Filter)
True

3.多個超類

1)多重繼承,繼承多個父類的屬性和方法:

class Calculator: 
 def calculate(self, expression): 
 self.value = eval(expression) 
class Talker: 
 def talk(self): 
 print('Hi, my value is', self.value) 
class TalkingCalculator(Calculator, Talker): 
 pass
>>> tc = TalkingCalculator() 
>>> tc.calculate('1 + 2 * 3') 
>>> tc.talk() 
Hi, my value is 7

2)注意:如果多個超類以不同的方式實現了同一個方法(同名方法),必須在class語句中小心排列這些超類,因為位於前面的類的方法將覆蓋位於後面的類的方法。因此,在前面的示例中,如果Calculator類包含方法talk,那麼這個方法將覆蓋Talker
類的方法talk(導致它不可訪問)。

四、多型

1.什麼是多型

我們首先要對資料型別再作一點說明。當我們定義一個class的時候,我們實際上就定義了一種資料型別。我們定義的資料型別和Python自帶的資料型別,比如str、list、dict沒什麼兩樣,子類物件的資料型別是子類本身,同時也是父類的資料型別
多型的用法例如,定義一個函式:

def run_twice(animal):
    animal.run()
    animal.run()
>>> run_twice(Animal())
Animal is running...
Animal is running...

>>> run_twice(Dog())
Dog is running...
Dog is running...

>>> run_twice(Cat())
Cat is running...
Cat is running..

2.開閉原則

多型的好處就是,由於Animal型別有run()方法,因此,傳入的任意型別,只要是Animal類或者子類,就會自動呼叫實際型別的run()方法,這就是多型的意思。呼叫方只管呼叫,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的程式碼是如何呼叫的。這就是著名的“開閉”原則:
1)對擴充套件開放:允許新增Animal子類;
2)對修改封閉:不需要修改依賴Animal型別的run_twice()等函式。

3.鴨子型別

一個物件只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。
對於靜態語言(例如Java)來說,如果需要傳入Animal型別,則傳入的物件必須是Animal型別或者它的子類,否則,將無法呼叫run()方法。對於Python這樣的動態語言來說,則不一定需要傳入Animal型別。我們只需要保證傳入的物件有一個run()方法就可以了,實際上對於Python,就是針對函式中呼叫的方法,只要有,不管定義的型別是什麼,傳遞進去的型別是什麼,都可以:

>>> def runtwice(anywithrun):
	anywithrun.run()
	anywithrun.run()

五、封裝

在Class內部,可以有屬性和方法,而外部程式碼可以通過直接 呼叫例項變數的方法(讀寫) 來運算元據,這樣,就隱藏了內部的複雜邏輯。

1.私有變數

如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,在Python中,例項的變數名如果以__開頭,就變成了一個私有變數(private),只有內部可以訪問,外部不能訪問。

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

注意:①如果變數名類似__xxx__的,也就是以雙下劃線開頭,並且以雙下劃線結尾的,是特殊變數,特殊變數是可以直接訪問的,不是private變數,所以儘量避免使用這種變數。
②如果變數名以一個下劃線開頭,這樣的例項變數外部是可以訪問的,但是,意思就是,請當作私有變數不要隨意訪問。
③即使是private變數,Python也提供了訪問的方法,因為Python直譯器對外把__xxx變數改成了_類名__xxx,所有通過下面這種方式也可以訪問,但是儘量不要這樣做。

>>> bart._Student__name
'Bart Simpson'

2.讀寫方法

例如上面的例子,如果外部程式碼要獲取name和score,可以給Student類增加get_name和get_score這樣的方法,如果要允許外部程式碼修改score,可以再給Student類增加set_score方法:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score
        
    def set_score(self, score):
        self.__score = score 

相關文章