一文掌握Python面向物件
Python是一種面向物件程式設計的語言,Python中幾乎都是物件,簡單數值型別,程式碼模組,可以說是萬物皆物件。例如對於一個數值物件:
很多人學習python,不知道從何學起。
很多人學習python,掌握了基本語法過後,不知道在哪裡尋找案例上手。
很多已經做案例的人,卻不知道如何去學習更加高深的知識。
那麼針對這三類人,我給大家提供一個好的學習平臺,免費領取視訊教程,電子書籍,以及課程的原始碼!
QQ群:961562169
>>> 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']
目錄
- 一、面向物件程式設計
- 二、類和物件
- 1.什麼是類和物件2.自定義類:建立與呼叫3.屬性、函式和方法4.類名稱空間
- 三、繼承
- 1.繼承2.繼承查詢3.多個超類
- 四、多型
- 1.什麼是多型2.開閉原則3.鴨子型別
- 五、封裝
- 1.私有變數2.讀寫方法
一、面向物件程式設計
面向物件程式設計的思想將物件作為程式的基本單元,物件=資料(屬性)+一套訪問和操作這些資料的方法。就像在引言中說的,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