1. 程式人生 > 其它 >程式碼複用技術—面向物件程式設計

程式碼複用技術—面向物件程式設計

技術標籤:Python

面向物件程式設計

類的定義與使用

  • Python使用class關鍵字來定義類,class關鍵字之後是一個空格,接下來時類的名字,如果派生自其他基類則需要把基類放到一對括號中並使用逗號分隔,然後是一個冒號,最後換行並定義類的內部實現
  • 類的首字母一般要大寫
 class Car(object): #定義一個類,派生自object類
	def infor(self)
: #定義成員方法 print("這是一個小汽車")
  • 定義類之後可以用來例項化物件,並可以通過“物件名.成員”的方式來訪問其中的資料成員方法。
>>> car=Car() #例項化物件
>>> car.infor() #呼叫物件的成員方法
這是一個小汽車
  • 在Python中,可以使用內建函式isinstance()來測試一個物件是否為某個類的例項
  • 或者使用內建type()函式檢視物件型別
>>> isinstance(car,Car)
True
>>> type(car)
<class '__main__.Car'
>
  • Python提供了一個關鍵字pass,執行時什麼也不會發生,表示空語句,可以使用pass來佔位
>>> class Test:
	'''這是一個測試'''
	pass

>>> Test.__doc__ #檢視類的幫助文件
'這是一個測試'

資料成員與成員方法

私有成員與公有成員

  • 私有成員在類的外部不能直接訪問,一般是在類的內部進行訪問和操作,或者在類的外部通過呼叫物件的公有成員方法來訪問
  • 而公有成員可以是公開使用的,既可以在類的內部進行訪問,也可以在外部程式中使用
  • 在定義類的成員時,如果成員名以兩個(或更多)下劃線開頭但不是以兩個(或更多)下劃線結束則表示是私有成員,否則就不是私有成員
  • Python沒有對私有成員提供嚴格的保護機制,通過一種特殊方式“物件名._類名__xxx”也可以在外部程式中訪問私有成員,但會破壞類的封裝性,不建議這樣做
>>> class A:
	def __init__(self,value1=0,value2=0): #構造方法
		self._value1=value1
		self.__value2=value2 #私有成員
	def setValue(self,value1,value2): #成員方法,公有成員
		self._value1=value1
		sele.__value2=value2 #在類內部可以直接訪問私有成員
	def show(self):
		print(self._value1)
		print(self.__value2)

		
>>> a=A()
>>> a._value1 #在類外部可以直接訪問非私有成員
0
>>> a._A__value2 #在類外部訪問物件的私有資料成員
0
  • 圓點“.”是成員訪問運算子,可以用來訪問名稱空間、模組或物件中的成員
  • 在物件或類名後面加上一個圓點“.”,都會自動列出其所有公開成員
  • 如果在圓點“.”後面再加一個下劃線,則會列出該物件或類的所有成員,包括私有成員
  • 也可以使用內建函式dir()來檢視指定物件、模組或名稱空間的所有成員

成員名定義的特殊性

  • _xxx:以一個下劃線開頭,保護成員,只有類物件和子類物件可以訪問這些成員,在類的外部一般不建議直接訪問,在模組中使用一個或多個下劃線開頭的成員不能用“from module import*”匯入,除非在模組中使用__all__變數明確指明這樣的成員可以被匯入
  • _xxx _ :前後兩個下劃線,系統定義的特殊成員
  • __xxx:以兩個或更多下劃線開頭但不以兩個或更多下劃線結束,表示私有成員,一般只有類物件自己能訪問,子類物件也不能訪問該成員,但在物件外部可以通過“物件名._類名__xxx”這樣的特殊方式來訪問

資料成員

  • 資料成員可以分為兩類:屬於物件的資料成員和屬於類的資料成員
  • 屬於物件的資料成員一般在構造方法__init__()中定義,當然也可以在其他成員方法中定義,在定義和在例項方法中訪問資料成員時以self作為字首,同一個類的不同物件(例項)的資料成員之間互不影響
  • 屬於類的資料成員是該類所有物件共享的,不屬於任何一個物件
  • 在主程式中或類的外部,物件資料成員屬於例項(物件),只能通過物件名訪問
  • 而資料成員屬於類,可以通過類名或物件名訪問
>>> class Demo(object):
	total=0
	def __new__(cls,*args,**kwargs): #該方法在__init__()之前被呼叫
		if cls.total>=2: #最多允許建立2個物件
			raise Exception('最多隻能建立2個物件')
		else:
			return object.__new__(cls)
	def __init__(self):
		Demo.total=Demo.total+1

		
>>> t1=Demo()
>>> t1
<__main__.Demo object at 0x0000000F646A4748>
>>> t2=Demo()
>>> t3=Demo()
Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    t3=Demo()
  File "<pyshell#36>", line 5, in __new__
    raise Exception('最多隻能建立2個物件')
Exception: 最多隻能建立2個物件
  • _new _():類的靜態方法,用於確定是否要建立物件
  • _init _():構造方法,建立物件時自動呼叫
  • _del _():析構方法,釋放物件時自動呼叫

成員方法、類方法、靜態方法、抽象方法

  • 在面向物件程式設計中,函式和方法這兩個概念是有本質區別的
  • 方法一般指與特定例項繫結的函式,通過物件呼叫方法是,物件本身將被作為第一個引數自動傳遞過去
  • 普通函式不具備這個特點
  • Python類的成員方法大致可以分為公有方法、私有方法、靜態方法、類方法和抽象方法
  • 公有方法、私有方法和抽象方法一般是指屬於物件的例項方法
  • 私有方法的名字以兩個或更多的下劃線開始
  • 抽象方法一般定義在抽象類中並且要求派生類必須重新實現
  • 每個物件都有自己的公有方法和私有方法,在這兩類方法中都可以訪問屬於類和物件的成員
  • 公有方法通過物件名直接呼叫
  • 私有方法不能通過物件名直接呼叫,只能在其他例項方法中通過字首self進行呼叫或在外部通過特殊的形式來呼叫

例項方法

  • 所有例項方法(包括公有方法、私有方法、抽象方法和某些特殊方法)都必須至少有一個名為self的引數,並且必須是方法的第一個形參(如果有多個形參),self引數代表當前物件
  • 例項方法中訪問例項成員時需要以self為字首
  • 在外部通過物件名呼叫物件方法時並不需要傳遞這個引數
  • 如果在外部通過類名呼叫屬於物件的公有方法,需要顯示為該方法的self引數傳遞一個物件名,用來明確指定訪問哪個物件的成員

靜態方法與類方法

  • 靜態方法與類方法都可以通過類名和物件名呼叫,但不能直接訪問屬於物件的成員,只能訪問屬於類的成員
  • 類方法一般以cls作為第一個引數表示該類自身,在呼叫類方法時不需要為該引數傳遞值
  • 靜態方法可以不接受任何引數
>>> class Root:
	__total=0
	def __init__(self,v): #構造方法
		self.__value=v
		Root.__total +=1
	def show(self): #普通例項方法,一般以self引數作為第一個引數名字
		print('self.__value:',self.__value)
		print('Root.__total:',Root.__total)
	@classmethod #修飾器 宣告類方法
	def classShowTotal(cls): #類方法,一般以cls作為第一個引數名字
		print(cls.__total)
	@staticmethod # 修飾器 宣告靜態方法
	def staticShowTotal(): #靜態方法 可以沒有引數
		print(Root.__total)

	
>>> r=Root(3)
>>> r.classShowTotal() #通過物件來呼叫類方法
1
>>> r.staticShowTotal() #通過物件來呼叫靜態方法
1
>>> rr=Root(5)
>>> Root.classShowTotal() #通過類名呼叫類方法
2
>>> Root.staticShowTotal() #通過類名呼叫靜態方法
2
>>> Root.show(r) #可以通過這種方法來呼叫方法並訪問例項物件
self.__value: 3
Root.__total: 2
>>> 

抽象方法

  • 抽象方法一般在抽象類中定義,並且要求在派生類中必須重新實現,否則不允許派生類建立例項
>>> import abc
>>> class Foo(metaclass=abc.ABCMeta): #抽象類
	def f1(self): #普通例項方法
		print(123)
	def f2(self): #普通例項方法
		print(456)
	@ abc.abstractmethod #抽象方法
	def f3(self): 
		raise Exception('你必須重寫這個方法')

>>> class Bar(Foo):
	def f3(self): #必須重新實現基類中的抽象方法
		print(3333)

		
>>> b=Bar()
>>> b.f3()
3333

屬性

  • 公開資料成員可以在外部隨意訪問和修改,資料很容易被破壞,而且不能保證合法性
  • 解決這類問題是定義私有成員,然後設計公開的成員方法來提供對私有資料成員的讀取和修改操作
  • 屬性是一種特殊形式的成員方法,結合了公開資料成員和成員方法的優點,既可以像成員方法那樣對值進行必要的檢查,又可以像資料成員一樣靈活的訪問
  • 可以將屬性設定為可讀、可修改、可刪除
>>> class Text:
	def __init__(self,value):
		self.__value=value
	def __get(self):
		return self.__value
	def __set(self,v):
		self.__value=v
	def __del(self): #刪除物件的私有資料成員
		del self.__value
	value=property(__get,__set,__del) #可讀、可寫、可刪除的屬性
	def show(self):
		print(self.__value)

		
>>> t=Text(3)
>>> t.show()
3
>>> t.value=5
>>> t.show()
5
>>> del t.value #刪除相應的私有資料成員
>>> t.value #已刪除,訪問失敗
Traceback (most recent call last):
  File "<pyshell#104>", line 1, in <module>
    t.value
  File "<pyshell#96>", line 5, in __get
    return self.__value
AttributeError: 'Text' object has no attribute '_Text__value'
>>> t.value=1 #動態增加屬性和對應的私有資料成員
>>> t.show()
1
>>> t.value
1

類與物件的動態性、混入機制

  • 在Python中可以動態的為自定義類和物件增加資料成員和成員方法的行為成為混入機制
>>> import types
>>> class Car(object):
	price=1000 #屬於類的資料成員
	def __init__(self,c):
		self.color=c #屬於物件的資料成員

	
>>> car1=Car("Red") #例項化物件
>>> print(car1.color,Car.price) #訪問物件和類的資料成員
Red 1000
>>> Car.price=100000 #修改類屬性
>>> Car.name='QQ' #動態增加類屬性
>>> car1.color='Yellow' #修改例項屬性
>>> print(car1.color,Car.price,Car.name)
Yellow 100000 QQ
>>> def setSpeed(self,s):
	self.speed=s
>>> car1.setSpeed=types.MethodType(setSpeed,car1) #動態為物件增加成員方法
>>> car1.setSpeed(50) #呼叫物件的成員方法
>>> print(car1.speed)
50

繼承和多型

繼承

  • 設計一個新類是,如果可以繼承一個已有的類進行二次開發,可以大大減少工作量
  • 在繼承關係中,有已經設計好的類稱為父類或基類
  • 新設計的類稱為子類或派生類
  • 派生類可以繼承父類的公有成員,但是不能繼承其私有成員
  • 如果需要在派生類中呼叫基類的方法,可以使用內建函式super()或者通過“基類名.方法名()”的方法實現
>>> class Person(object): #基類必須繼承於object,否則在派生類中將無法使用super()函式
	def __init__(self,name='',age=20,sex='man'): #通過呼叫方法進行初始化,這樣可以對引數進行更好的控制
		self.setName(name)
		self.setAge(age)
		self.setSex(sex)
	def setName(self,name):
		if not isinstance(name,str):
			raise Exception('名字必須是字串')
		self.__name=name
	def setAge(self,age):
		if type(age) != int:
			raise Exception('年齡必須是數字')
		self.__age=age
	def setSex(self,sex):
		if sex not in ('man','woman') :
			raise Exception('性別必須是"man"或"woman"')
		self.__sex=sex
	def show(self):
		print(self.__name,self.__age,self.__sex,sep='\n')

#派生類		
>>> class Teacher(Person):
	def __init__(self,name=' ',age=30,sex='man',department='computer'): 
		super(Teacher,self).__init__(name,age,sex) #呼叫基類夠著方法初始化基類的私有資料成員
		Person.__init__(self,name,age,sex) #也可以這樣初始化基類的私有資料成員
		self.setDepartment(department) #初始化派生類的資料成員
	def setDepartment(self,department):
		if type(department)!=str:
			raise Exception('department必須是字串')
		self.__department=department
	def show(self):
		super(Teacher,self).show()
		print(self.__department)

	
>>> if __name__=='__main__':
	zhangsan=Person('zhang san',19,'man') #建立基類物件
	zhangsan.show()
	print('='*30)
	lisi=Teacher('li si',32,'man','Math') #建立派生類物件
	lisi.show()
	lisi.setAge(40) #呼叫繼承的方法修改年齡
	lisi.show()

	
zhang san
19
man
==============================
li si
32
man
Math
li si
40
man
Math
>>> 

多型

  • 多型指基類的同一個方法在不同派生類物件中具有不同的表現和行為
  • 派生類繼承了基類的行為和屬性之後,還會增加某些特定的行為和屬性,同時還可能對繼承來的某些行為進行一定的改變
>>> class Animal(object): #定義基類
	def show(self):
		print('我是一隻動物')

		
>>> class Cat(Animal): #派生類,覆蓋了基類的show()方法
	def show(self):
		print('我是一隻貓')

		
>>> class Dog(Animal): #派生類
	def show(self):
		print('我是一隻狗')

		
>>> class Test2(Animal): #派生類,沒有覆蓋基類的show()方法
	pass

>>> x=[item() for item in (Animal,Cat,Dog,Test2)] 
>>> for item in x: #遍歷基類和派生類物件並呼叫show()方法
	item.show()

	
我是一隻動物
我是一隻貓
我是一隻狗
我是一隻動物
>>>