Python入门学习---第四天
面向對象編程
類和實例
概念與其他面向對象編程語言類似。
以Student類為例,在Python中,定義類是通過class關鍵字:
class Student(object):pass(object)表示繼承自 object 類。
定義好了Student類,就可以根據Student類創建出Student的實例,創建實例是通過類名+()實現的:
>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>bart指向的是Student的實例,0x10a67a590是這個實例的內存地址。
Student是一個類。
與靜態語言(JAVA)不同,動態語言(Python)可以自由地給一個實例變量綁定屬性,比如,給實例bart綁定一個name屬性:
>>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson'由于類可以起到模板的作用,因此,可以在創建實例的時候,把一些我們認為必須綁定的屬性強制填寫進去。通過定義一個特殊的init方法,在創建實例的時候,就把name,score等屬性綁上去:
class Student(object):def __init__(self, name, score):self.name = nameself.score = score_ _ init _ _是一個特殊方法(創建對象時,這個方法會被調用)。
和普通的函數相比,在類中定義的函數只有一點不同,就是第一個參數永遠是實例變量self,并且,調用時,不用傳遞self參數。除此之外,與普通函數沒什么區別。
數據封裝
面向對象編程的一個重要特點就是數據封裝。
我們可以通過函數來訪問這些數據,比如打印一個學生的成績:
>>> def print_score(std): ... print('%s: %s' % (std.name, std.score)) ... >>> print_score(bart) Bart Simpson: 59但是,既然Student實例本身就擁有這些數據,要訪問這些數據,就沒有必要從外面的函數去訪問,可以直接在Student類的內部定義訪問數據的函數,這樣,就把“數據”給封裝起來了。這些封裝數據的函數是和Student類本身是關聯起來的,我們稱之為類的方法:
class Student(object):def __init__(self, name, score):self.name = nameself.score = scoredef print_score(self):print('%s: %s' % (self.name, self.score))記住第一個參數永遠都是 self 參數。
調用時,除了 self 不用傳遞,其他參數正常傳入:
>>> bart.print_score() Bart Simpson: 59這些數據和邏輯被“封裝”起來了,調用很容易,不用知道內部實現的細節。
封裝的另一個好處是可以給Student類增加新的方法,比如get_grade:
class Student(object):...def get_grade(self):if self.score >= 90:return 'A'elif self.score >= 60:return 'B'else:return 'C'內部細節就被封裝起來。
從上面看到,Python不僅僅有靜態語言的特點,還有和靜態語言不同的地方。比如:Python允許對實例變量綁定任何數據,也就是說,對于兩個實例變量,雖然它們都是同一個類的不同實例,但 擁有 的 變量名 可能 不同 :
>>> bart = Student('Bart Simpson', 59) >>> lisa = Student('Lisa Simpson', 87) >>> bart.age = 8 >>> bart.age 8 >>> lisa.age Traceback (most recent call last):File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age'訪問限制
從前面Student類的定義來看,外部代碼還是可以自由地修改一個實例的name、score**屬性**:
>>> bart = Student('Bart Simpson', 59) >>> bart.score 59 >>> bart.score = 99 >>> bart.score 99如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線 _ _ ,在Python中,實例的變量名如果以 _ _ 開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問。
所以,我們把Student類改一改:
class Student(object):def __init__(self, name, score):self.__name = nameself.__score = scoredef print_score(self):print('%s: %s' % (self.__name, self.__score))無法從外部訪問 實例變量. _ _ name 和 實例變量 . _ _ 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'這樣就確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。
但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加 get_name 和 get_score 這樣的方法:
class Student(object):...def get_name(self):return self.__namedef get_score(self):return self.__score如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:
class Student(object):...def set_score(self, score):self.__score = score如此一來,Python與靜態語言有著類似的功能了。
但是我們需要注意的是,在Python中,變量名類似 _ _ xxx _ _ 的,也就是以雙下劃線開頭,并且以雙下劃線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用 _ _ name _ _ 、 _ _ score _ _ 這樣的變量名。
有些時候,你會看到以一個下劃線開頭的實例變量名,比如 _ name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
不能直接訪問 _ _ name是因為Python解釋器對外把 _ _ name變量改成了 _ Student _ _ name,所以,仍然可以通過 _ Student _ _ name來訪問 _ _ name變量:
>>> bart._Student__name 'Bart Simpson'但是強烈建議你不要這么干,因為不同版本的Python解釋器可能會把 _ _ name**改成不同的變量名**。
總的來說就是,Python本身沒有任何機制阻止你干壞事,一切全靠自覺。
最后,我們看一個 陷阱 例子:
>>> bart = Student('Bart Simpson', 59) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 設置__name變量! >>> bart.__name 'New Name'從表面上看,我們成功的創建了一個外部private的變量。但實際上class內部的private的變量已經被Python解釋器改為其他變量名。所以,我們只是增加了一個public的變量。嘗試下面代碼:
>>> bart.get_name() # get_name()內部返回self.__name 'Bart Simpson'我們的練習題:
請把下面的 Student 對象的 gender 字段對外隱藏起來,用 get _ gender() 和 set _ gender() 代替,并檢查參數有效性:
class Student(object):def __init__(self, name, gender):self.__name = nameself.__gender = genderdef get_name(self):return self.__namedef set_name(self,name):self.__name = namedef get_gender(self):return self.__genderdef set_gender(self,gender):self.__gender = gender# 測試如下 bart = Student('Bart', 'male') if bart.get_gender() != 'male':print('測試失敗!') else:bart.set_gender('female')if bart.get_gender() != 'female':print('測試失敗!')else:print('測試成功!')繼承和多態
與其他面向對象語言的 繼承 類似。
只介紹語法:
# 基類 class Animal(object):def run(self):print('Animal is running...')# 子類 class Dog(Animal):passclass Cat(Animal):pass# 測試 dog = Dog() dog.run()cat = Cat() cat.run()# 輸出 Animal is running... Animal is running...多態 也與其他語言類似。
以下的一個例子:
# Tortoise繼承自Animal class Tortoise(Animal):def run(self):print('Tortoise is running slowly...')# 定義一個函數,以Animal類型為參數。 # 注意:這里的animal并不是一定為Animal類型。 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...>>> run_twice(Tortoise()) Tortoise is running slowly... Tortoise is running slowly...對于一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat還是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
- 對擴展開放:允許新增Animal子類;
- 對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
靜態語言 VS 動態語言
對于靜態語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。
對于Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:
# 定義一個類 class Timer(object):def run(self):print('Start...')# 測試 >>> run_twice(Timer()) Start... Start...這就是動態語言的“鴨子類型”,它并不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。
Python的“file-like object“就是一種鴨子類型。對真正的文件對象,它有一個read()方法,返回其內容。但是,許多對象,只要有read()方法,都被視為“file-like object“。許多函數接收的參數就是“file-like object“,你不一定要傳入真正的文件對象,完全可以傳入任何實現了read()方法的對象。
只要記住:看起來像鴨子,走起路來像鴨子。就可以把它當做是一只鴨子。
動態語言的鴨子類型特點決定了繼承不像靜態語言那樣是必須的。
獲取對象信息
當我們拿到一個對象的引用時,如何知道這個對象是什么類型、有哪些方法呢?
第一種辦法,使用type()函數:
基本類型 都可以用 type() 判斷:
>>> type(123) <class 'int'> >>> type('str') <class 'str'> >>> type(None) <type(None) 'NoneType'>如果一個變量指向 函數 或者 類 ,也可以用 type() 判斷:
>>> type(abs) <class 'builtin_function_or_method'> >>> type(a) <class '__main__.Animal'>但是type()函數返回的是什么類型呢?它返回對應的Class類型。如果我們要在if語句中判斷,就需要比較兩個變量的type類型是否相同:
>>> type(123)==type(456) True >>> type(123)==int True >>> type('abc')==type('123') True >>> type('abc')==str True >>> type('abc')==type(123) False判斷基本數據類型可以直接寫int,str等,但如果要判斷一個對象是否是函數怎么辦?可以使用types模塊中定義的常量:
>>> import types >>> def fn(): ... pass ... >>> type(fn)==types.FunctionType True >>> type(abs)==types.BuiltinFunctionType True >>> type(lambda x: x)==types.LambdaType True >>> type((x for x in range(10)))==types.GeneratorType True第二種辦法,使用isinstance():
對于class的繼承關系來說,使用type()就很不方便。我們要判斷class的類型,可以使用isinstance()函數。
我們回顧上次的例子,如果繼承關系是:
object -> Animal -> Dog -> Husky那么,isinstance()就可以告訴我們,一個對象是否是某種類型。
# 先創建3種類型的對象: >>> a = Animal() >>> d = Dog() >>> h = Husky()# 判斷: >>> isinstance(h, Husky) True>>> isinstance(h, Dog) True>>> isinstance(h, Animal) True>>> isinstance(d, Dog) and isinstance(d, Animal) True>>> isinstance(d, Husky) False我們看到,isinstance可以判斷出繼承關系。h本身和h父類的結果都為True。同理,d本身和父類也一樣,但d不是它的子類Husky。
能用type()判斷的基本類型也可以用isinstance()判斷:
>>> isinstance('a', str) True >>> isinstance(123, int) True >>> isinstance(b'a', bytes) True并且還可以判斷一個變量是否是某些類型中的一種,比如下面的代碼就可以判斷是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple)) True >>> isinstance((1, 2, 3), (list, tuple)) True第三種方式,使用dir():
如果要獲得一個對象的所有屬性和方法,可以使用dir()函數,它返回一個包含字符串的list,比如,獲得一個str對象的所有屬性和方法:
僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態:
# 定義一個類 >>> class MyObject(object): ... def __init__(self): ... self.x = 9 ... def power(self): ... return self.x * self.x ... >>> obj = MyObject()# 測試 >>> hasattr(obj, 'x') # 有屬性'x'嗎? True >>> obj.x 9 >>> hasattr(obj, 'y') # 有屬性'y'嗎? False >>> setattr(obj, 'y', 19) # 設置一個屬性'y' >>> hasattr(obj, 'y') # 有屬性'y'嗎? True >>> getattr(obj, 'y') # 獲取屬性'y' 19 >>> obj.y # 獲取屬性'y' 19# 如果試圖獲取不存在的屬性,會拋出AttributeError的錯誤: >>> getattr(obj, 'z') # 獲取屬性'z' Traceback (most recent call last):File "<stdin>", line 1, in <module> AttributeError: 'MyObject' object has no attribute 'z'# 可以傳入一個default參數,如果屬性不存在,就返回默認值: >>> getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認值404 404# 也可以獲得對象的方法: >>> hasattr(obj, 'power') # 有屬性'power'嗎? True >>> getattr(obj, 'power') # 獲取屬性'power' <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn = getattr(obj, 'power') # 獲取屬性'power'并賦值到變量fn >>> fn # fn指向obj.power <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn() # 調用fn()與調用obj.power()是一樣的 81最后一個測試,由于返回的是一個內存地址。所以我們知道它不是屬性,即power是一個方法。
實例屬性和類屬性
由于Python是動態語言,根據類創建的實例可以任意綁定屬性。
給實例綁定屬性的方法是通過實例變量,或者通過self變量:
class Student(object):def __init__(self, name):self.name = names = Student('Bob') s.score = 90但是,如果Student類本身需要綁定一個屬性呢?可以直接在class中定義屬性,這種屬性是類屬性,歸Student類所有:
class Student(object):name = 'Student'當我們定義了一個類屬性后,這個屬性雖然歸類所有(有點想靜態成員),但類的所有實例都可以訪問到。來測試一下:
>>> class Student(object): ... name = 'Student' ... >>> s = Student() # 創建實例s >>> print(s.name) # 打印name屬性,因為實例并沒有name屬性,所以會繼續查找class的name屬性 Student >>> print(Student.name) # 打印類的name屬性 Student >>> s.name = 'Michael' # 給實例綁定name屬性 >>> print(s.name) # 由于實例屬性優先級比類屬性高,因此,它會屏蔽掉類的name屬性 Michael >>> print(Student.name) # 但是類屬性并未消失,用Student.name仍然可以訪問 Student >>> del s.name # 如果刪除實例的name屬性 >>> print(s.name) # 再次調用s.name,由于實例的name屬性沒有找到,類的name屬性就顯示出來了 Student從上面的例子可以看出,在編寫程序的時候,千萬不要對實例屬性和類屬性使用相同的名字,因為相同名稱的實例屬性將屏蔽掉類屬性,但是當你刪除實例屬性后,再使用相同的名稱,訪問到的將是類屬性。
練習練習:
為了統計學生人數,可以給Student類增加一個類屬性,每創建一個實例,該屬性自動增加:
# -*- coding: utf-8 -*- class Student(object):count = 0def __init__(self, name):self.name = nameStudent.count += 1# 測試: if Student.count != 0:print('測試失敗!') else:bart = Student('Bart')if Student.count != 1:print('測試失敗!')else:lisa = Student('Bart')if Student.count != 2:print('測試失敗!')else:print('Students:', Student.count)print('測試通過!')總結
Python入門學習到此為止,接下來將展開對Python高級特性的了解甚至是深入學習。
轉載處:廖雪峰Python教程
總結
以上是生活随笔為你收集整理的Python入门学习---第四天的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互联网日报 | 华为Mate40系列国内
- 下一篇: 需求分析三层境界,你到哪层了?