简明Python教程学习笔记_6_面向对象编程
?
?
- 面向?qū)ο?/strong>編程:https://www.liaoxuefeng.com/wiki/897692888725344/923030496738368
- 面向?qū)ο?/strong>高級(jí)編程:https://www.liaoxuefeng.com/wiki/897692888725344/923030538012640
?
?
1、類、對(duì)象
?
類?和?對(duì)象?是面向?qū)ο缶幊痰膬蓚€(gè)主要方面。
- 類 是創(chuàng)建一個(gè) 新類型,
- 而對(duì)象是這個(gè)類 的 實(shí)例 。
方法:在 Python 中,方法(method)專指 "類中定義的函數(shù)",即在類中的函數(shù)才叫方法。
方法?和?函數(shù)?在 python中是不同的概念,
- 方法是類中定義的函數(shù),即在類中的函數(shù)才叫方法。
- 函數(shù)是就是一般的函數(shù)
給 C/C++/Java/C#程序員的注釋
注意:Python 中即便是整數(shù)也被作為對(duì)象 ( 屬于int類 )。這和 C++、Java(1.5版之前)把整數(shù)純粹作為類型是不同的。通過 help(int) 了解更多這個(gè)類的詳情。?
? ? ?C#和Java 1.5程序員會(huì)熟悉這個(gè)概念,因?yàn)樗愃婆c封裝與解封裝 的概念。
? ? ?對(duì)象可以使用普通的 屬于 對(duì)象的變量存儲(chǔ)數(shù)據(jù)。屬于一個(gè)對(duì)象或類的變量被稱為域。
? ? ?對(duì)象也可以使用 屬于類的函數(shù)來具有功能。這樣的函數(shù)被稱為類的方法。
? ? ?這些術(shù)語幫助我們把它們與孤立的函數(shù)和變量區(qū)分開來。域和方法可以合稱為類的屬性。
? ? ?域有兩種類型——屬于每個(gè)實(shí)例/類的對(duì)象或?qū)儆陬惐旧怼K鼈兎謩e被稱為實(shí)例變量和類變量。
? ? ?類使用class關(guān)鍵字創(chuàng)建。類的域和方法被列在一個(gè)縮進(jìn)塊中。
在 Python類中定義的方法通常有三種:實(shí)例方法、類方法、靜態(tài)方法。這三者之間的區(qū)別是:
- 實(shí)例方法:一般都以 self 作為第一個(gè)參數(shù),必須和具體的對(duì)象實(shí)例進(jìn)行綁定才能訪問。實(shí)例方法的第一個(gè)參數(shù)是實(shí)例對(duì)象 self,那么通過 self 引用的可能是類屬性、也有可能是實(shí)例屬性(這個(gè)需要具體分析)。不過在存在相同名稱的類屬性和實(shí)例屬性的情況下,實(shí)例屬性優(yōu)先級(jí)更高。可以直接在類外通過對(duì)象名訪問,如果想定義成私有的,則需在前面加2個(gè)下劃線 ' __'
- 類方法 ? ?:以 cls 作為第一個(gè)參數(shù),cls表示類本身,定義時(shí)使用@classmethod,那么通過 cls 引用的必定是類對(duì)象的屬性和方法;
- 靜態(tài)方法:不需要默認(rèn)的任何參數(shù),跟一般的普通函數(shù)類似。定義的時(shí)候使用@staticmethod,靜態(tài)方法中不需要額外定義參數(shù),因此在靜態(tài)方法中引用類屬性的話,必須通過類對(duì)象來引用。
構(gòu)造方法 __init__() 和 析構(gòu)方法 __del__()
- __init__(self) 方法是一種特殊的方法,被稱為類的構(gòu)造函數(shù)或初始化方法,當(dāng)創(chuàng)建了這個(gè)類的實(shí)例時(shí)就會(huì)調(diào)用該方法。構(gòu)造方法支持重載,如果沒有構(gòu)造方法,系統(tǒng)就自動(dòng)執(zhí)行默認(rèn)的構(gòu)造方法。
-
__del__(self) 方法也是一種特殊的方法,叫做 析構(gòu)方法。在釋放對(duì)象時(shí)調(diào)用,支持重載,可以在里面進(jìn)行一些釋放資源的操作,不需要顯示調(diào)用。
?
創(chuàng)建 實(shí)例對(duì)象(?類的實(shí)例 )
?
要?jiǎng)?chuàng)建一個(gè) 類的實(shí)例,你可以使用類的名稱,并通過 __init__ 方法接受參數(shù)。
class Employee(object):def __init__(self, name, age):self.name = nameself.age = agepassdef __del__(self):passdef display_employee(self):print(f'name:{self.name}, age:{self.age}')emp1 = Employee("one", 2000) # "創(chuàng)建 Employee 類的第一個(gè)對(duì)象" emp2 = Employee("two", 5000) # "創(chuàng)建 Employee 類的第二個(gè)對(duì)象"?
訪問 屬性
# 使用點(diǎn)(.)來訪問對(duì)象的屬性。使用如下類的名稱訪問類變量: emp1.display_employee() # 你可以添加,刪除,修改類的屬性,如下所示: emp1.age = 7 # 添加一個(gè) 'age' 屬性 emp1.age = 8 # 修改 'age' 屬性 del emp1.age # 刪除 'age' 屬性示例:
class TestClass(object):name = 'TestClass'passobj = TestClass()t_name = getattr(obj, 'name') # 訪問對(duì)象的屬性。 print(t_name)is_have = hasattr(obj, 'name') # 檢查是否存在一個(gè)屬性。 print(is_have)setattr(obj, 'age', 100) # 設(shè)置一個(gè)屬性。如果屬性不存在,會(huì)創(chuàng)建一個(gè)新屬性。 # delattr(obj, 'name') # 刪除屬性。?
Python內(nèi)置的?類的屬性
- __dict__ : ?類的屬性(包含一個(gè)字典,由類的數(shù)據(jù)屬性組成)
- __doc__ ?: ?類的文檔字符串
- __name__ : ?類名
- __module__: 類定義所在的模塊(類的全名是'__main__.className',如果類位于一個(gè)導(dǎo)入模塊mymod中,那么className.__module__ 等于 mymod)
- __bases__ : 類的所有父類構(gòu)成元素(包含了以個(gè)由所有父類組成的元組) ?
?
Python對(duì)象 的 銷毀(垃圾回收)
在 Python 內(nèi)部記錄著所有使用中的對(duì)象各有多少引用。一個(gè)內(nèi)部跟蹤變量,稱為一個(gè)引用計(jì)數(shù)器。當(dāng)對(duì)象被創(chuàng)建時(shí), 就創(chuàng)建了一個(gè)引用計(jì)數(shù), 當(dāng)這個(gè)對(duì)象不再需要時(shí), 這個(gè)對(duì)象的引用計(jì)數(shù)變?yōu)? 時(shí),它被垃圾回收。但是回收不是"立即"的, 由解釋器在適當(dāng)?shù)臅r(shí)機(jī),將垃圾對(duì)象占用的內(nèi)存空間回收。
?
self 參數(shù)
?
類的方法與普通的函數(shù)只有一個(gè)特別的區(qū)別:即?類的方法?第一個(gè)參數(shù)必須是 self。但是在調(diào)用這個(gè)方法的時(shí)候你不為這個(gè)參數(shù)賦值,Python會(huì)提供這個(gè)值。這個(gè)特別的變量指對(duì)象本身,按照慣例它的名稱是self。雖然你可以給這個(gè)參數(shù)任何名稱,但是 強(qiáng)烈建議 你使用self這個(gè)名稱——其他名稱都是不贊成你使用的。使用一個(gè)標(biāo)準(zhǔn)的名稱有很多優(yōu)點(diǎn)——你的程序讀者可以迅速識(shí)別它,如果使用self的話,還有些IDE(集成開發(fā)環(huán)境)也可以幫助你。
給C/C++/Java/C#程序員的注釋
Python 中的 self 等價(jià)于 C++ 中的 this?指針。
Python 如何給 self 賦值以及為何你不需要給它賦值?舉一個(gè)例子會(huì)使此變得清晰:假如你有一個(gè)類稱為 MyClass 和這個(gè)類的一個(gè)實(shí)例MyObject。當(dāng)你調(diào)用這個(gè)對(duì)象的方法 MyObject.method(arg1, arg2) 的時(shí)候,這會(huì)由 Python 自動(dòng)轉(zhuǎn)為 MyClass.method(MyObject, arg1, arg2),這就是self的原理了。這也意味著如果你有一個(gè)不需要參數(shù)的方法,你還是得給這個(gè)方法定義一個(gè)self 參數(shù)。
?
創(chuàng)建一個(gè) 類
?
#!/usr/bin/python # Filename: simplestclass.pyclass Person:pass # An empty blockp = Person() print(p)# 結(jié)果 # <__main__.Person object at 0x0000023C03EA97F0>使用 class 后跟類名,創(chuàng)建一個(gè)新的類。這后面跟著一個(gè)縮進(jìn)的語句塊形成類體。這個(gè)例子中,使用 pass 語句表示空語句。然后使用類名后跟一對(duì)圓括號(hào)來創(chuàng)建一個(gè)對(duì)象/實(shí)例。為了驗(yàn)證,我們簡(jiǎn)單地打印了這個(gè)變量的類型。它告訴我們我們已經(jīng)在__main__模塊中有了一個(gè)Person類的實(shí)例。可以注意到存儲(chǔ)對(duì)象的計(jì)算機(jī)內(nèi)存地址也打印了出來。這個(gè)地址在你的計(jì)算機(jī)上會(huì)是另外一個(gè)值,因?yàn)镻ython可以在任何空位存儲(chǔ)對(duì)象。
?
使用 對(duì)象的方法
#!/usr/bin/python # Filename: method.pyclass Person:def sayHi(self):print('Hello, how are you?')p = Person() p.sayHi() # This short example can also be written as Person().sayHi()# 輸出 # $ python method.py # Hello, how are you?這里可以看到 self 的用法。注意 sayHi 方法沒有任何參數(shù),但仍然在函數(shù)定義時(shí)有 self。
?
__init__?方法
?
在 Python 的類中,有很多方法的名字有特殊的重要意義。現(xiàn)在看下 __init__方法的意義。__init__ 方法在類的一個(gè)對(duì)象被建立時(shí),馬上運(yùn)行。這個(gè)方法可以用來對(duì)你的對(duì)象做一些你希望的 初始化 。注意,這個(gè)名稱的開始和結(jié)尾都是雙下劃線 __init__
#!/usr/bin/python # Filename: class_init.pyclass Person:def __init__(self, name):self.name = namedef sayHi(self):print('Hello, my name is', self.name)p = Person('Swaroop') p.sayHi()Person('Swaroop').sayHi()# 輸出 # $ python class_init.py # Hello, my name is Swaroop # Hello, my name is Swaroop這個(gè)__init__方法有兩個(gè)參數(shù),分別是 self、name,方法里面是 self.name = name。注意它們是兩個(gè)不同的變量,盡管它們有相同的名字。點(diǎn)號(hào)使我們能夠區(qū)分它們。最重要的是,我們沒有專門調(diào)用__init__方法,只是在創(chuàng)建一個(gè)類的新實(shí)例的時(shí)候,把參數(shù)包括在圓括號(hào)內(nèi)跟在類名后面,從而傳遞給__init__方法。
給C/C++/Java/C#程序員的注釋:__init__方法類似于C++、C#和Java中的 constructor 。
?
?
2、類?與?對(duì)象的方法
?
已經(jīng)討論了類與對(duì)象的功能部分,現(xiàn)在來看一下它的數(shù)據(jù)部分。
事實(shí)上,它們只是與類和對(duì)象的名稱空間 綁定 的普通變量,即這些名稱只在這些類與對(duì)象的前提下有效。
兩種類型的 域 ——類的變量(屬于類 的作用域) 和 對(duì)象的變量(屬于實(shí)例的作用域),它們根據(jù)是類還是對(duì)象 擁有 這個(gè)變量而區(qū)分。
類的變量 由一個(gè)類的所有對(duì)象(實(shí)例)共享使用。只有一個(gè)類變量的拷貝,所以當(dāng)某個(gè)對(duì)象對(duì)類的變量做了改動(dòng)的時(shí)候,這個(gè)改動(dòng)會(huì)反映到所有其他的實(shí)例上。
對(duì)象的變量 由類的每個(gè)對(duì)象/實(shí)例擁有。因此每個(gè)對(duì)象有自己對(duì)這個(gè)域的一份拷貝,即它們不是共享的,在同一個(gè)類的不同實(shí)例中,雖然對(duì)象的變量有相同的名稱,但是是互不相關(guān)的。通過一個(gè)例子會(huì)使這個(gè)易于理解。
?
使用 類與對(duì)象?的 變量
?
#!/usr/bin/python # Filename: objvar.pyclass Person:"""Represents a person."""population = 0def __init__(self, name):"""Initializes the person's data."""self.name = nameprint('(Initializing %s)' % self.name)# When this person is created, he/she# adds to the populationPerson.population += 1def __del__(self):"""I am dying."""print('%s says bye.' % self.name)Person.population -= 1if Person.population == 0:print('I am the last one.')else:print('There are still %d people left.'% Person.population)def sayHi(self):"""Greeting by the person.Really, that's all it does."""print('Hi, my name is %s.' % self.name)def howMany(self):"""Prints the current population."""if Person.population == 1:print('I am the only person here.')else:print('We have %d persons here.'% Person.population)swaroop = Person('Swaroop') swaroop.sayHi() swaroop.howMany()kalam = Person('Abdul Kalam') kalam.sayHi() kalam.howMany()swaroop.sayHi() swaroop.howMany()結(jié)果:
(Initializing Swaroop) Hi, my name is Swaroop. I am the only person here. (Initializing Abdul Kalam) Hi, my name is Abdul Kalam. We have 2 persons here. Hi, my name is Swaroop. We have 2 persons here. Swaroop says bye. There are still 1 people left. Abdul Kalam says bye. I am the last one.這里,
- population 屬于 Person類,因此是一個(gè)類的變量。
- name變量?屬于?對(duì)象(它使用self賦值),因此是對(duì)象的變量。
在 __init__ 方法中,我們讓 population 增加1,這是因?yàn)槲覀冊(cè)黾恿艘粋€(gè)人。同樣可以發(fā)現(xiàn),self.name 的值根據(jù)每個(gè)對(duì)象指定,這表明了它作為對(duì)象的變量的本質(zhì)。
記住,你只能使用 self變量 來 引用 同一個(gè)對(duì)象的變量和方法。
在這個(gè)程序中,還可以看到 docstring 對(duì)于類和方法同樣有用。我們可以在運(yùn)行時(shí)使用 Person.__doc__ 和 Person.sayHi.__doc__來分別訪問類與方法的文檔字符串,就如同 __init__ 方法一樣,還有一個(gè)特殊的方法 __del__,它在對(duì)象消逝的時(shí)候被調(diào)用。對(duì)象消逝即對(duì)象不再被使用,它所占用的內(nèi)存將返回給系統(tǒng)作它用。在這個(gè)方法里面,我們只是簡(jiǎn)單地把 Person.population 減 1。
當(dāng)對(duì)象不再被使用時(shí),__del__方法運(yùn)行,但很難保證這個(gè)方法究竟在 什么時(shí)候 運(yùn)行。如果你想要指明它的運(yùn)行,你就得使用 del 語句。
?
給C/C++/Java/C#程序員的注釋
- Python 中所有的?類成員(包括數(shù)據(jù)成員)都是 公共的 ,所有的方法都是 有效的 。
- 只有一個(gè)例外:如果你使用的數(shù)據(jù)成員名稱以 "雙下劃線前綴", 比如 __privatevar,Python 的名稱管理體系會(huì)有效地把它作為私有變量。
- 這樣就有一個(gè)慣例,如果某個(gè)變量?只想在類或?qū)ο笾惺褂?/strong>,就應(yīng)該以 "單下劃線前綴"。而其他的名稱都將作為公共的,可以被其他類/對(duì)象使用。記住這只是一個(gè)慣例,并不是Python所要求的(與雙下劃線前綴不同)。
- 同樣,注意 __del__ 方法與 destructor 的概念類似。
?
?
3、繼承
?
面向?qū)ο蟮木幊處淼闹饕锰幹皇谴a的重用,實(shí)現(xiàn)這種重用的方法之一是通過 繼承 機(jī)制。繼承完全可以理解成:類之間的類型和子類型 關(guān)系。
假設(shè)寫個(gè)程序來記錄學(xué)校中教師和學(xué)生情況。他們有一些共同屬性,比如姓名、年齡和地址。他們也有專有的屬性,比如教師的薪水、課程和假期,學(xué)生的成績(jī)和學(xué)費(fèi)。
可以為教師和學(xué)生建立兩個(gè)獨(dú)立的類來處理它們,但這樣做的話,如果要增加一個(gè)新的共有屬性,就意味著要在這兩個(gè)獨(dú)立的類中都增加這個(gè)屬性。這很快就會(huì)顯得不實(shí)用。
一個(gè)比較好的方法是創(chuàng)建一個(gè)共同的類稱為SchoolMember然后讓教師和學(xué)生的類 繼承 這個(gè)共同的類。即它們都是這個(gè)類型(類)的子類型,然后我們?cè)贋檫@些子類型添加專有的屬性。使用這種方法有很多優(yōu)點(diǎn)。如果我們?cè)黾?改變了SchoolMember中的任何功能,它會(huì)自動(dòng)地反映到子類型之中。例如,你要為教師和學(xué)生都增加一個(gè)新的身份證域,那么你只需簡(jiǎn)單地把它加到SchoolMember類中。然而,在一個(gè)子類型之中做的改動(dòng)不會(huì)影響到別的子類型。另外一個(gè)優(yōu)點(diǎn)是你可以把教師和學(xué)生對(duì)象都作為SchoolMember對(duì)象來使用,這在某些場(chǎng)合特別有用,比如統(tǒng)計(jì)學(xué)校成員的人數(shù)。一個(gè)子類型在任何需要父類型的場(chǎng)合可以被替換成父類型,即對(duì)象可以被視作是父類的實(shí)例,這種現(xiàn)象被稱為多態(tài)現(xiàn)象。另外,我們會(huì)發(fā)現(xiàn)在 重用 父類的代碼的時(shí)候,我們無需在不同的類中重復(fù)它。而如果我們使用獨(dú)立的類的話,我們就不得不這么做了。
在上述的場(chǎng)合中,SchoolMember類被稱為 基本類 或 超類 。而Teacher和Student類被稱為導(dǎo)出類 或子類 。
現(xiàn)在,我們將學(xué)習(xí)一個(gè)例子程序。
?
使用繼承
?
# !/usr/bin/python # Filename: inherit.pyclass SchoolMember:"""Represents any school member."""def __init__(self, name, age):self.name = nameself.age = ageprint('(Initialized SchoolMember: %s)' % self.name)def tell(self):"""Tell my details."""print('Name:"%s" Age:"%s"' % (self.name, self.age))class Teacher(SchoolMember):"""Represents a teacher."""def __init__(self, name, age, salary):SchoolMember.__init__(self, name, age)self.salary = salaryprint('(Initialized Teacher: %s)' % self.name)def tell(self):SchoolMember.tell(self)print('Salary: "%d"' % self.salary)class Student(SchoolMember):"""Represents a student."""def __init__(self, name, age, marks):SchoolMember.__init__(self, name, age)self.marks = marksprint('(Initialized Student: %s)' % self.name)def tell(self):SchoolMember.tell(self)print('Marks: "%d"' % self.marks)t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 22, 75)print() # prints a blank linemembers = [t, s] for member in members:member.tell() # works for both Teachers and Students# 輸出 # (Initialized SchoolMember: Mrs. Shrividya) # (Initialized Teacher: Mrs. Shrividya) # (Initialized SchoolMember: Swaroop) # (Initialized Student: Swaroop) # # Name:"Mrs. Shrividya" Age:"40" # Salary: "30000" # Name:"Swaroop" Age:"22" # Marks: "75"為了使用繼承,我們把基本類的名稱作為一個(gè)元組跟在定義類時(shí)的類名稱之后。然后,我們注意到基本類的__init__方法專門使用self變量調(diào)用,這樣我們就可以初始化對(duì)象的基本類部分。這一點(diǎn)十分重要——Python不會(huì)自動(dòng)調(diào)用基本類的constructor,你得親自專門調(diào)用它。我們還觀察到我們?cè)诜椒ㄕ{(diào)用之前加上類名稱前綴,然后把self變量及其他參數(shù)傳遞給它。注意,在我們使用SchoolMember類的tell方法的時(shí)候,我們把Teacher和Student的實(shí)例僅僅作為SchoolMember的實(shí)例。另外,在這個(gè)例子中,我們調(diào)用了子類型的tell方法,而不是SchoolMember類的tell方法。可以這樣來理解,Python總是首先查找對(duì)應(yīng)類型的方法,在這個(gè)例子中就是如此。如果它不能在導(dǎo)出類中找到對(duì)應(yīng)的方法,它才開始到基本類中逐個(gè)查找。基本類是在類定義的時(shí)候,在元組之中指明的。一個(gè)術(shù)語的注釋——如果在繼承元組中列了一個(gè)以上的類,那么它就被稱作 多重繼承 。
?
繼承語法 :
基類名寫在括號(hào)里,基本類是在類定義的時(shí)候,在元組之中指明的。
class 派生類名( 基類1,[基類2, 基類3, ... 基類N] ):pass示例:
class SubClassName (ParentClass1[, ParentClass2, ...]):'Optional class documentation string'class_suite?
單繼承
?
示例:
# coding=utf-8 # !/usr/bin/python class Parent: # 定義父類parentAttr = 100def __init__(self):print("調(diào)用父類構(gòu)造函數(shù)")def parentMethod(self):print('調(diào)用父類方法')def setAttr(self, attr):Parent.parentAttr = attrdef getAttr(self):print("父類屬性 :", Parent.parentAttr)class Child(Parent): # 定義子類def __init__(self):print("調(diào)用子類構(gòu)造方法")def childMethod(self):print('調(diào)用子類方法 child method')c = Child() # 實(shí)例化子類 c.childMethod() # 調(diào)用子類的方法 c.parentMethod() # 調(diào)用父類方法 c.setAttr(200) # 再次調(diào)用父類的方法 c.getAttr() # 再次調(diào)用父類的方法執(zhí)行結(jié)果如下:
調(diào)用子類構(gòu)造方法 調(diào)用子類方法 child method 調(diào)用父類方法 父類屬性 : 200?
多繼承
?
class A: # 定義類 A
? ? .....
class B: # 定義類 B
? ? .....
class C(A, B): # 繼承類 A 和 B
? ? .....
你可以使用issubclass()或者isinstance()方法來檢測(cè)。
issubclass() - 布爾函數(shù)判斷一個(gè)類是另一個(gè)類的子類或者子孫類,語法:issubclass(sub,sup)
isinstance(obj, Class) 布爾函數(shù)如果obj是Class類的實(shí)例對(duì)象或者是一個(gè)Class子類的實(shí)例對(duì)象則返回true。
?
在 Python 中繼承中的一些特點(diǎn):
1:在繼承中基類的構(gòu)造(__init__()方法)不會(huì)被自動(dòng)調(diào)用,它需要在其派生類的構(gòu)造中親自專門調(diào)用。 2:在調(diào)用基類的方法時(shí),需要加上基類的類名前綴,且需要帶上self參數(shù)變量。區(qū)別于在類中調(diào)用普通函數(shù)時(shí)并不需要帶上self參數(shù) 3:Python總是首先查找對(duì)應(yīng)類型的方法,如果它不能在派生類中找到對(duì)應(yīng)的方法,它才開始到基類中逐個(gè)查找。(先在本類中查找調(diào)用的方法,找不到才去基類中找)。 如果在繼承元組中列了一個(gè)以上的類,那么它就被稱作"多重繼承" 。?
super()? 【繼承順序說明】
?
在 Python 類的方法(method)中,要調(diào)用父類的某個(gè)方法,在Python 2.2以前,通常的寫法如下:
class A:def __init__(self):print "enter A"print "leave A"class B(A):def __init__(self):print "enter B"A.__init__(self)print "leave B">>> b = B() enter B enter A leave A leave B使用非綁定的類方法(用類名來引用的方法),并在參數(shù)列表中,引入待綁定的對(duì)象(self),從而達(dá)到調(diào)用父類的目的。這樣做的缺點(diǎn)是,當(dāng)一個(gè)子類的父類發(fā)生變化時(shí)(如類B的父類由A變?yōu)镃時(shí)),必須遍歷整個(gè)類定義,把所有的通過非綁定的方法的類名全部替換過來,例如下面代碼,
class B(C): # A --> Cdef __init__(self):print "enter B"C.__init__(self) # A --> Cprint "leave B"如果代碼簡(jiǎn)單,這樣的改動(dòng)或許還可以接受。但如果代碼量龐大,這樣的修改可能是災(zāi)難性的。因此,自Python 2.2開始,Python 添加了一個(gè)關(guān)鍵字 super,來解決這個(gè)問題。
class A(object): # A must be new-style classdef __init__(self):print "enter A"print "leave A"class B(C): # A --> Cdef __init__(self):print "enter B"super(B, self).__init__()print "leave B"嘗試執(zhí)行上面同樣的代碼,結(jié)果一致,但修改的代碼只有一處,把代碼的維護(hù)量降到最低,是一個(gè)不錯(cuò)的用法。因此在開發(fā)過程中,super 關(guān)鍵字被大量使用,
在我們的印象中,對(duì)于 super(B, self).__init__() 是這樣理解的:super(B, self) 首先找到B的父類(就是類A),然后把類B的對(duì)象 self 轉(zhuǎn)換為類A的對(duì)象,然后 "被轉(zhuǎn)換"?的類A對(duì)象調(diào)用自己的__init__函數(shù)。考慮到 super 中只有指明子類的機(jī)制,因此,在多繼承的類定義中,通常我們保留使用類似代碼段1的方法。
有一天某同事設(shè)計(jì)了一個(gè)相對(duì)復(fù)雜的類體系結(jié)構(gòu)(我們先不要管這個(gè)類體系設(shè)計(jì)得是否合理,僅把這個(gè)例子作為一個(gè)題目來研究就好),代碼如下:
class A(object):def __init__(self):print "enter A"print "leave A"class B(object):def __init__(self):print "enter B"print "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"class E(B, C):def __init__(self):print "enter E"B.__init__(self)C.__init__(self)print "leave E"class F(E, D):def __init__(self):print "enter F"E.__init__(self)D.__init__(self)print "leave F"f = F()結(jié)果: enter F enter E enter B leave B enter C enter D enter A leave A leave D leave C leave E enter D enter A leave A leave D leave F明顯地,類A和類D的初始化函數(shù)被重復(fù)調(diào)用了2次,這并不是我們所期望的結(jié)果!我們所期望的結(jié)果是最多只有類A的初始化函數(shù)被調(diào)用2次——其實(shí)這是多繼承的類體系必須面對(duì)的問題。我們把代碼段4的類體系畫出來,如下圖:
按我們對(duì) super 的理解,從圖中可以看出,在調(diào)用類C的初始化函數(shù)時(shí),應(yīng)該是調(diào)用類A的初始化函數(shù),但事實(shí)上卻調(diào)用了類D的初始化函數(shù)。好一個(gè)詭異的問題!
也就是說,mro中記錄了一個(gè)類的所有基類的類類型序列。查看 mro 的記錄,發(fā)覺包含7個(gè)元素,7個(gè)類名分別為:F E B C D A object
從而說明了為什么在 C.__init__ 中使用 super(C, self).__init__() 會(huì)調(diào)用類D的初始化函數(shù)了。?
我們把代碼段改寫為:
class A(object):def __init__(self):print "enter A"super(A, self).__init__() # newprint "leave A"class B(object):def __init__(self):print "enter B"super(B, self).__init__() # newprint "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D" class E(B, C):def __init__(self):print "enter E"super(E, self).__init__() # changeprint "leave E"class F(E, D):def __init__(self):print "enter F"super(F, self).__init__() # changeprint "leave F"f = F()結(jié)果: enter F enter E enter B enter C enter D enter A leave A leave D leave C leave B leave E leave F明顯地,F 的初始化不僅完成了所有的父類的調(diào)用,而且保證了每一個(gè)父類的初始化函數(shù)只調(diào)用一次。再看類結(jié)構(gòu):
E-1,D-2 是 F 的父類,其中表示 E類在前,即 F(E,D)。所以初始化順序可以從類結(jié)構(gòu)圖來看出 : F --->?E ---> B --> C --> D --> A
由于C,D 有同一個(gè)父類,因此會(huì)先初始化 D 再是 A。
延續(xù)的討論
我們?cè)僦匦驴瓷厦娴念愺w系圖,如果把每一個(gè)類看作圖的一個(gè)節(jié)點(diǎn),每一個(gè)從子類到父類的直接繼承關(guān)系看作一條有向邊,那么該體系圖將變?yōu)橐粋€(gè)有向圖。不能發(fā)現(xiàn)mro的順序正好是該有向圖的一個(gè)拓?fù)渑判蛐蛄小?/p>
從而,我們得到了另一個(gè)結(jié)果——Python是如何去處理多繼承。支持多繼承的傳統(tǒng)的面向?qū)ο蟪绦蛘Z言(如C++)是通過虛擬繼承的方式去實(shí)現(xiàn)多繼承中父類的構(gòu)造函數(shù)被多次調(diào)用的問題,而Python則通過mro的方式去處理。
但這給我們一個(gè)難題:對(duì)于提供類體系的編寫者來說,他不知道使用者會(huì)怎么使用他的類體系,也就是說,不正確的后續(xù)類,可能會(huì)導(dǎo)致原有類體系的錯(cuò)誤,而且這樣的錯(cuò)誤非常隱蔽的,也難于發(fā)現(xiàn)。
小結(jié)
- 1. super 并不是一個(gè)函數(shù),是一個(gè)類名,形如super(B, self)事實(shí)上調(diào)用了super類的初始化函數(shù),產(chǎn)生了一個(gè)super對(duì)象;
- 2. super類的初始化函數(shù)并沒有做什么特殊的操作,只是簡(jiǎn)單記錄了類類型和具體實(shí)例;
- 3. super(B, self).func的調(diào)用并不是用于調(diào)用當(dāng)前類的父類的func函數(shù);
- 4. Python的多繼承類是通過mro的方式來保證各個(gè)父類的函數(shù)被逐一調(diào)用,而且保證每個(gè)父類函數(shù)只調(diào)用一次(如果每個(gè)類都使用super);
- 5. 混用super類和非綁定的函數(shù)是一個(gè)危險(xiǎn)行為,這可能導(dǎo)致應(yīng)該調(diào)用的父類函數(shù)沒有調(diào)用或者一個(gè)父類函數(shù)被調(diào)用多次。
示例:
class A(object):x = 1class B(A):passclass C(A):passclass D(B, C):passprint(A.x, B.x, C.x, D.x) # 1 1 1 1C.x = 2 print(A.x, B.x, C.x, D.x) # 1 1 2 2B.x = 3 print(A.x, B.x, C.x, D.x) # 1 3 2 3?
方法重寫
?
如果你的父類方法的功能不能滿足你的需求,你可以在子類重寫你父類的方法,示例:
#coding=utf-8 #!/usr/bin/python class Parent: # 定義父類def myMethod(self):print '調(diào)用父類方法'class Child(Parent): # 定義子類def myMethod(self):print '調(diào)用子類方法'c = Child() # 子類實(shí)例 c.myMethod() # 子類調(diào)用重寫方法輸出結(jié)果如下:
調(diào)用子類方法 基礎(chǔ)重載方法?
Python?運(yùn)算符重載
?
示例:
#!/usr/bin/python class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self,other):return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2,10) v2 = Vector(5,-2) print v1 + v2 以上代碼執(zhí)行結(jié)果如下所示: Vector(7,8)類 的 屬性與方法
- 類?的 私有屬性:__private_attrs:兩個(gè)下劃線開頭,聲明該屬性為私有,不能在類地外部被使用或直接訪問。在類內(nèi)部的方法中使用時(shí) self.__private_attrs。
- 類?方法 ? ?:以cls作為第一個(gè)參數(shù),cls表示類本身,定義時(shí)使用@classmethod,那么通過cls引用的必定是類對(duì)象的屬性和方法;
實(shí)例方法:一般都以self作為第一個(gè)參數(shù),必須和具體的對(duì)象實(shí)例進(jìn)行綁定才能訪問,
靜態(tài)方法:不需要默認(rèn)的任何參數(shù),跟一般的普通函數(shù)類似。定義的時(shí)候使用@staticmethod,靜態(tài)方法中不需要額外定義參數(shù),因此在靜態(tài)方法中引用類屬性的話,必須通過類對(duì)象來引用。
# coding=utf-8 # !/usr/bin/python class JustCounter:__secretCount = 0 # 私有變量publicCount = 0 # 公開變量def count(self):self.__secretCount += 1self.publicCount += 1print(self.__secretCount)counter = JustCounter() counter.count() counter.count() print(counter.publicCount) print(counter.__secretCount) # 報(bào)錯(cuò),實(shí)例不能訪問私有變量上面代碼最后一行報(bào)錯(cuò),因?yàn)?Python不允許 實(shí)例化的對(duì)象 訪問私有數(shù)據(jù),但你可以使用 object._className__attrName 訪問屬性。
將上面最后一行代碼換成:print(counter._JustCounter__secretCount)? 即可訪問私有屬性
?
python _、__和__xx__的區(qū)別
?
來源:http://www.cnblogs.com/coder2012/p/4423356.html
來源:https://segmentfault.com/a/1190000002611411
http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python
http://www.zhihu.com/question/19754941
?
"_"單下劃線
Python 中不存在真正的私有方法。為了實(shí)現(xiàn)類似于 C++ 中私有方法,可以在類的方法或?qū)傩郧凹右粋€(gè) "_"?單下劃線,意味著該方法或?qū)傩圆粦?yīng)該去調(diào)用,它并不屬于API。
在使用 property 時(shí),經(jīng)常出現(xiàn)這個(gè)問題:
class BaseForm(StrAndUnicode):...def _get_errors(self):"Returns an ErrorDict for the data provided for the form"if self._errors is None:self.full_clean()return self._errorserrors = property(_get_errors)上面的代碼片段來自于 django 源碼(django/forms/forms.py)。這里的 errors 是一個(gè)屬性,屬于 API 的一部分,但是 _get_errors 是私有的,是不應(yīng)該訪問的,但可以通過 errors 來訪問該錯(cuò)誤結(jié)果。
?
"__"雙下劃線
這個(gè)雙下劃線更會(huì)造成更多混亂,但它并不是用來標(biāo)識(shí)一個(gè)方法或?qū)傩允撬接械?#xff0c;真正作用是用來避免子類覆蓋其內(nèi)容。
讓我們來看一個(gè)例子:
class A(object): def __method(self): print "I'm a method in A" def method(self): self.__method() a = A() a.method()輸出是這樣的:
$ python example.py I'm a method in A很好,出現(xiàn)了預(yù)計(jì)的結(jié)果。 現(xiàn)在我們給A添加一個(gè)子類,并重新實(shí)現(xiàn)一個(gè)__method:
class B(A): def __method(self): print "I'm a method in B" b = B() b.method()現(xiàn)在,結(jié)果是這樣的:
$ python example.py I'm a method in A就像我們看到的一樣,B.method()不能調(diào)用B.__method的方法。實(shí)際上,它是"__"兩個(gè)下劃線的功能的正常顯示。
因此,在我們創(chuàng)建一個(gè)以"__"兩個(gè)下劃線開始的方法時(shí),這意味著這個(gè)方法不能被重寫,它只允許在該類的內(nèi)部中使用。
在Python中如是做的?很簡(jiǎn)單,它只是把方法重命名了,如下:?
a = A() a._A__method() # never use this!! please! $ python example.py I'm a method in A如果你試圖調(diào)用a.__method,它還是無法運(yùn)行的,就如上面所說,只可以在類的內(nèi)部調(diào)用__method。
?
"__xx__"前后各雙下劃線 (特殊的屬性和方法)
當(dāng)你看到"__this__"的時(shí),就知道不要調(diào)用它。為什么?因?yàn)樗囊馑际撬怯糜赑ython調(diào)用的,如下:
>>> name = "igor" >>> name.__len__() 4 >>> len(name) 4 >>> number = 10 >>> number.__add__(20) 30 >>> number + 20 30“__xx__”經(jīng)常是操作符或本地函數(shù)調(diào)用的magic methods。在上面的例子中,提供了一種重寫類的操作符的功能。
在特殊的情況下,它只是python調(diào)用的hook。例如,__init__()函數(shù)是當(dāng)對(duì)象被創(chuàng)建初始化時(shí)調(diào)用的;__new__()是用來創(chuàng)建實(shí)例。
class CrazyNumber(object):def __init__(self, n): self.n = n def __add__(self, other): return self.n - other def __sub__(self, other): return self.n + other def __str__(self): return str(self.n) num = CrazyNumber(10) print num # 10 print num + 5 # 5 print num - 20 # 30另一個(gè)例子
class Room(object):def __init__(self): self.people = [] def add(self, person): self.people.append(person) def __len__(self): return len(self.people)room = Room() room.add("Igor") print len(room) # 1?
結(jié)論
- 使用 _one_underline 來表示該方法或?qū)傩允撬接械?#xff0c;不屬于API;
- 當(dāng)創(chuàng)建一個(gè)用于 python 調(diào)用或一些特殊情況時(shí),使用 __two_underline__;
- 使用 __just_to_underlines,來避免子類的重寫!
?
?
4、Python中面向?qū)ο?/span>編程
?
一. 如何定義一個(gè)類
在進(jìn)行python面向?qū)ο缶幊讨?#xff0c;先來了解幾個(gè)術(shù)語:類,類對(duì)象,實(shí)例對(duì)象,屬性,函數(shù)和方法。
類是對(duì)現(xiàn)實(shí)世界中一些事物的封裝,定義一個(gè)類可以采用下面的方式來定義:
class className: // blockpass注意類名后面有個(gè)冒號(hào),在 block 塊里面就可以定義屬性和方法了。當(dāng)一個(gè)類定義完之后,就產(chǎn)生了一個(gè)類對(duì)象。類對(duì)象支持兩種操作:引用和實(shí)例化。引用操作是通過類對(duì)象去調(diào)用類中的屬性或者方法,而實(shí)例化是產(chǎn)生出一個(gè)類對(duì)象的實(shí)例,稱作實(shí)例對(duì)象。比如定義了一個(gè)people類:
class People:name = 'jack' # 定義一個(gè) 類 的 屬性 # 定義一個(gè) 實(shí)例 的 方法 def printName(self):print(self.name)people類定義完成之后就產(chǎn)生了一個(gè)全局的類對(duì)象,可以通過類對(duì)象來訪問類中的屬性和方法了。當(dāng)通過people.name(至于為什么可以直接這樣訪問屬性后面再解釋,這里只要理解類對(duì)象這個(gè)概念就行了)來訪問時(shí),people.name中的people稱為類對(duì)象,這點(diǎn)和C++中的有所不同。當(dāng)然還可以進(jìn)行實(shí)例化操作,p=people( ),這樣就產(chǎn)生了一個(gè)people的實(shí)例對(duì)象,此時(shí)也可以通過實(shí)例對(duì)象p來訪問屬性或者方法了(p.name).
理解了類、類對(duì)象和實(shí)例對(duì)象的區(qū)別之后,我們來了解一下Python中屬性、方法和函數(shù)的區(qū)別。
在上面代碼中注釋的很清楚了,name是一個(gè)屬性,printName( )是一個(gè)方法,與某個(gè)對(duì)象進(jìn)行綁定的函數(shù)稱作為方法。一般在類里面定義的函數(shù)與類對(duì)象或者實(shí)例對(duì)象綁定了,所以稱作為方法;而在類外定義的函數(shù)一般沒有同對(duì)象進(jìn)行綁定,就稱為函數(shù)。
?
二. 屬性
在類中我們可以定義一些屬性,比如: class People:name = 'jack'age = 12p = People() print(p.name, p.age)定義了一個(gè) People 類,里面定義了name和age屬性,默認(rèn)值分別為'jack'和12。在定義了類之后,就可以用來產(chǎn)生實(shí)例化對(duì)象了,這句p = People( )實(shí)例化了一個(gè)對(duì)象p,然后就可以通過p來讀取屬性了。這里的name和age都是公有的,可以直接在類外通過對(duì)象名訪問,如果想定義成私有的,則需在前面加2個(gè)下劃線 ' __'。
class People:__name = 'jack'__age = 12p = People() print(p.__name, p.__age)這段程序運(yùn)行會(huì)報(bào)錯(cuò):
Traceback?(most?recent?call?last):??File?"C:/PycharmProjects/FirstProject/oop.py",?line?6,?in?<module>??print?p.__name,p.__age?? AttributeError:?people?instance?has?no?attribute?'__name??提示找不到該屬性,因?yàn)樗接袑傩允遣荒軌蛟陬愅馔ㄟ^對(duì)象名來進(jìn)行訪問的。在Python中沒有像C++中public和private這些關(guān)鍵字來區(qū)別公有屬性和私有屬性,它是以屬性命名方式來區(qū)分,如果在屬性名前面加了2個(gè)下劃線'__',則表明該屬性是私有屬性,否則為公有屬性(方法也是一樣,方法名前面加了2個(gè)下劃線的話表示該方法是私有的,否則為公有的)。
?
三. 方法
在類中可以根據(jù)需要定義一些方法,定義方法采用def關(guān)鍵字,在類中定義的方法至少會(huì)有一個(gè)參數(shù),一般以名為'self'的變量作為該參數(shù)(用其他名稱也可以),而且需要作為第一個(gè)參數(shù)。下面看個(gè)例子: class People:__name = 'jack'__age = 12def getName(self):return self.__namedef getAge(self):return self.__agep = People() print(p.getName(), p.getAge())如果對(duì) self 不好理解的話,可以把它當(dāng)做C++中類里面的this指針一樣理解,就是對(duì)象自身的意思,在用某個(gè)對(duì)象調(diào)用該方法時(shí),就將該對(duì)象作為第一個(gè)參數(shù)傳遞給self。
?
四. 類中內(nèi)置的方法
在 Python 中有一些內(nèi)置的方法,這些方法命名都有比較特殊的地方(其方法名以2個(gè)下劃線開始然后以2個(gè)下劃線結(jié)束)。類中最常用的就是構(gòu)造方法和析構(gòu)方法。
- 構(gòu)造方法 __init__(self,....):在生成對(duì)象時(shí)調(diào)用,可以用來進(jìn)行一些初始化操作,不需要顯示去調(diào)用,系統(tǒng)會(huì)默認(rèn)去執(zhí)行。構(gòu)造方法支持重載,如果沒有構(gòu)造方法,系統(tǒng)就自動(dòng)執(zhí)行默認(rèn)的構(gòu)造方法。
- 析構(gòu)方法 __del__(self):在釋放對(duì)象時(shí)調(diào)用,支持重載,可以在里面進(jìn)行一些釋放資源的操作,不需要顯示調(diào)用。
還有其他的一些內(nèi)置方法,比如 __cmp__( ), __len( )__等。下面是常用的內(nèi)置方法:
| ?內(nèi)置方法 | ?說明 |
| ?__init__(self,...) | ?初始化對(duì)象,在創(chuàng)建新對(duì)象時(shí)調(diào)用 |
| ?__del__(self) | ?釋放對(duì)象,在對(duì)象被刪除之前調(diào)用 |
| ?__new__(cls,*args,**kwd) | ?實(shí)例的生成操作 |
| ?__str__(self) | ?在使用print語句時(shí)被調(diào)用 |
| ?__getitem__(self,key) | ?獲取序列的索引key對(duì)應(yīng)的值,等價(jià)于seq[key] |
| ?__len__(self) | ?在調(diào)用內(nèi)聯(lián)函數(shù)len()時(shí)被調(diào)用 |
| ?__cmp__(stc,dst) | ?比較兩個(gè)對(duì)象src和dst |
| ?__getattr__(s,name) | ?獲取屬性的值 |
| ?__setattr__(s,name,value) | ?設(shè)置屬性的值 |
| ?__delattr__(s,name) | ?刪除name屬性 |
| ?__getattribute__() | ?__getattribute__()功能與__getattr__()類似 |
| ?__gt__(self,other) | ?判斷self對(duì)象是否大于other對(duì)象 |
| ?__lt__(slef,other) | ?判斷self對(duì)象是否小于other對(duì)象 |
| ?__ge__(slef,other) | ?判斷self對(duì)象是否大于或者等于other對(duì)象 |
| ?__le__(slef,other) | ?判斷self對(duì)象是否小于或者等于other對(duì)象 |
| ?__eq__(slef,other) | ?判斷self對(duì)象是否等于other對(duì)象 |
| ?__call__(self,*args) | ?把實(shí)例對(duì)象作為函數(shù)調(diào)用 |
__init__():?__init__方法在類的一個(gè)對(duì)象被建立時(shí),馬上運(yùn)行。這個(gè)方法可以用來對(duì)你的對(duì)象做一些你希望的初始化。注意,這個(gè)名稱的開始和結(jié)尾都是雙下劃線。代碼例子:
# Filename: class_init.py class Person:def __init__(self, name):self.name = namedef sayHi(self):print('Hello, my name is', self.name)p = Person('Swaroop') p.sayHi()# 輸出: # Hello, my name is Swaroop__new__():?__new__() 在 __init__() 之前被調(diào)用,用于生成實(shí)例對(duì)象。利用這個(gè)方法和類屬性的特性可以實(shí)現(xiàn)設(shè)計(jì)模式中的單例模式。單例模式是指創(chuàng)建唯一對(duì)象嗎,單例模式設(shè)計(jì)的類只能實(shí)例化一個(gè)對(duì)象。
# -*- coding: UTF-8 -*- class Singleton(object):__instance = None # 定義實(shí)例 def __init__(self):passdef __new__(cls, *args, **kwd): # 在__init__之前調(diào)用 if Singleton.__instance is None: # 生成唯一實(shí)例 Singleton.__instance = object.__new__(cls, *args, **kwd)return Singleton.__instance__getattr__()、__setattr__() 和__getattribute__() :當(dāng)讀取對(duì)象的某個(gè)屬性時(shí),python會(huì)自動(dòng)調(diào)用__getattr__()方法。例如,fruit.color將轉(zhuǎn)換為fruit.__getattr__(color)。當(dāng)使用賦值語句對(duì)屬性進(jìn)行設(shè)置時(shí),python會(huì)自動(dòng)調(diào)用__setattr__()方法。__getattribute__()的功能與__getattr__()類似,用于獲取屬性的值。但是__getattribute__()能提供更好的控制,代碼更健壯。注意,python中并不存在__setattribute__()方法。代碼例子:
# -*- coding: UTF-8 -*- class Fruit(object):def __init__(self, color="red", price=0):self.__color = colorself.__price = pricedef __getattribute__(self, item): # 獲取屬性的方法return object.__getattribute__(self, item)def __setattr__(self, key, value):self.__dict__[key] = valueif __name__ == "__main__":fruit = Fruit("blue", 10)print(fruit.__dict__.get("_Fruit__color")) # 獲取color屬性fruit.__dict__["_Fruit__price"] = 5print(fruit.__dict__.get("_Fruit__price")) # 獲取price屬性Python 不允許實(shí)例化的類訪問私有數(shù)據(jù),但你可以使用 object._className__attrName 訪問這些私有屬性。
__getitem__():?如果類把某個(gè)屬性定義為序列,可以使用__getitem__()輸出序列屬性中的某個(gè)元素.假設(shè)水果店中銷售多鐘水果,可以通過__getitem__()方法獲取水果店中的沒種水果。代碼例子:
# -*- coding: UTF-8 -*- class FruitShop:def __getitem__(self, i): # 獲取水果店的水果return self.fruits[i]if __name__ == "__main__":shop = FruitShop()shop.fruits = ["apple", "banana"]print(shop[1])for item in shop: # 輸出水果店的水果print(item)# 結(jié)果 # banana # apple # banana__str__():?__str__()用于表示對(duì)象代表的含義,返回一個(gè)字符串.實(shí)現(xiàn)了__str__()方法后,可以直接使用print語句輸出對(duì)象,也可以通過函數(shù)str()觸發(fā)__str__()的執(zhí)行。這樣就把對(duì)象和字符串關(guān)聯(lián)起來,便于某些程序的實(shí)現(xiàn),可以用這個(gè)字符串來表示某個(gè)類。代碼例子:
# -*- coding: UTF-8 -*- class Fruit:"""''Fruit類""" # 為Fruit類定義了文檔字符串def __str__(self): # 定義對(duì)象的字符串表示return self.__doc__if __name__ == "__main__":fruit = Fruit()print(str(fruit)) # 調(diào)用內(nèi)置函數(shù)str()觸發(fā)__str__()方法,輸出結(jié)果為:Fruit類print(fruit) # 直接輸出對(duì)象fruit,返回__str__()方法的值,輸出結(jié)果為:Fruit類__call__():?在類中實(shí)現(xiàn)__call__()方法,可以在對(duì)象創(chuàng)建時(shí)直接返回__call__()的內(nèi)容。使用該方法可以模擬靜態(tài)方法。代碼例子:
# -*- coding: UTF-8 -*- class Fruit:class Growth: # 內(nèi)部類def __call__(self):print("grow ...")grow = Growth() # 調(diào)用Growth(),此時(shí)將類Growth作為函數(shù)返回,即為外部類Fruit定義方法grow(),grow()將執(zhí)行__call__()內(nèi)的代碼if __name__ == '__main__':fruit = Fruit()fruit.grow() # 輸出結(jié)果:grow ...Fruit.grow() # 輸出結(jié)果:grow ...?
五. 類屬性、實(shí)例屬性、類方法、實(shí)例方法以及靜態(tài)方法
在了解了類基本的東西之后,下面看一下python中這幾個(gè)概念的區(qū)別。
先來談一下類屬性和實(shí)例屬性
在前面的例子中我們接觸到的就是類屬性,顧名思義,類屬性就是類對(duì)象所擁有的屬性,它被所有類對(duì)象的實(shí)例對(duì)象所共有,在內(nèi)存中只存在一個(gè)副本,這個(gè)和C++中類的靜態(tài)成員變量有點(diǎn)類似。對(duì)于公有的類屬性,在類外可以通過類對(duì)象和實(shí)例對(duì)象訪問。
class people:name = 'jack' # 公有的類屬性 __age = 12 # 私有的類屬性 p = people()print(p.name) # 正確 print(people.name) # 正確 print(p.__age) # 錯(cuò)誤,不能在類外通過實(shí)例對(duì)象訪問私有的類屬性 print(people.__age) # 錯(cuò)誤,不能在類外通過類對(duì)象訪問私有的類屬性實(shí)例屬性是不需要在類中顯示定義的,比如:
class People:name = 'jack'p = People() p.age = 12 print(p.name) # 正確 print(p.age) # 正確print(People.name) # 正確 print(People.age) # 錯(cuò)誤對(duì)類對(duì)象People進(jìn)行實(shí)例化之后,產(chǎn)生了一個(gè)實(shí)例對(duì)象p,然后p.age = 12這句給p添加了一個(gè)實(shí)例屬性age,賦值為12。這個(gè)實(shí)例屬性是實(shí)例對(duì)象p所特有的,注意,類對(duì)象 People 并不擁有它(所以不能通過類對(duì)象來訪問這個(gè)age屬性)。當(dāng)然還可以在實(shí)例化對(duì)象的時(shí)候給age賦值。
class People:name = 'jack'# __init__()是內(nèi)置的構(gòu)造方法,在實(shí)例化對(duì)象時(shí)自動(dòng)調(diào)用 def __init__(self, age):self.age = agep = People(12) print(p.name) # 正確 print(p.age) # 正確 print(People.name) # 正確 print(People.age) # 錯(cuò)誤如果需要在類外修改類屬性,必須通過類對(duì)象去引用然后進(jìn)行修改。如果通過實(shí)例對(duì)象去引用,會(huì)產(chǎn)生一個(gè)同名的實(shí)例屬性,這種方式修改的是實(shí)例屬性,不會(huì)影響到類屬性,并且之后如果通過實(shí)例對(duì)象去引用該名稱的屬性,實(shí)例屬性會(huì)強(qiáng)制屏蔽掉類屬性,即引用的是實(shí)例屬性,除非刪除了該實(shí)例屬性。
class People:country = 'china'print(People.country) p = People() print(p.country) p.country = 'japan' print(p.country) # 實(shí)例屬性會(huì)屏蔽掉同名的類屬性 print(People.country) del p.country # 刪除實(shí)例屬性 print(p.country)下面來看一下類方法、實(shí)例方法 和 靜態(tài)方法 的區(qū)別。
類方法:是類對(duì)象所擁有的方法,需要用修飾器"@classmethod"來標(biāo)識(shí)其為類方法,對(duì)于類方法,第一個(gè)參數(shù)必須是類對(duì)象,一般以"cls"作為第一個(gè)參數(shù)(當(dāng)然可以用其他名稱的變量作為其第一個(gè)參數(shù),但是大部分人都習(xí)慣以'cls'作為第一個(gè)參數(shù)的名字,就最好用'cls'了),能夠通過實(shí)例對(duì)象和類對(duì)象去訪問。
class People:country = 'china'# 類方法,用classmethod來進(jìn)行修飾 @classmethoddef getCountry(cls):return cls.countryp = People() print(p.getCountry()) # 可以用過實(shí)例對(duì)象引用 print(People.getCountry()) # 可以通過類對(duì)象引用類方法還有一個(gè)用途就是可以對(duì)類屬性進(jìn)行修改:
class People:country = 'china'# 類方法,用classmethod來進(jìn)行修飾 @classmethoddef getCountry(cls):return cls.country@classmethoddef setCountry(cls, country):cls.country = countryp = People() print(p.getCountry()) # 可以用過實(shí)例對(duì)象引用 print(People.getCountry()) # 可以通過類對(duì)象引用 p.setCountry('japan')print(p.getCountry()) print(People.getCountry())運(yùn)行結(jié)果:
china?? china?? japan?? japan?結(jié)果顯示在用類方法對(duì)類屬性修改之后,通過類對(duì)象和實(shí)例對(duì)象訪問都發(fā)生了改變。
實(shí)例方法:在類中最常定義的成員方法,它至少有一個(gè)參數(shù)并且必須以實(shí)例對(duì)象作為其第一個(gè)參數(shù),一般以名為'self'的變量作為第一個(gè)參數(shù)(當(dāng)然可以以其他名稱的變量作為第一個(gè)參數(shù))。在類外實(shí)例方法只能通過實(shí)例對(duì)象去調(diào)用,不能通過其他方式去調(diào)用。
class People:country = 'china'# 實(shí)例方法 def getCountry(self):return self.countryp = People() print(p.getCountry()) # 正確,可以用過實(shí)例對(duì)象引用 print(People.getCountry()) # 錯(cuò)誤,不能通過類對(duì)象引用實(shí)例方法?靜態(tài)方法:?需要通過修飾器"@staticmethod"來進(jìn)行修飾,靜態(tài)方法不需要多定義參數(shù)。
class People:country = 'china'@staticmethod# 靜態(tài)方法 def getCountry():return People.countryprint(People.getCountry())對(duì)于類屬性和實(shí)例屬性,如果在類方法中引用某個(gè)屬性,該屬性必定是類屬性,而如果在實(shí)例方法中引用某個(gè)屬性(不作更改),并且存在同名的類屬性,此時(shí)若實(shí)例對(duì)象有該名稱的實(shí)例屬性,則實(shí)例屬性會(huì)屏蔽類屬性,即引用的是實(shí)例屬性,若實(shí)例對(duì)象沒有該名稱的實(shí)例屬性,則引用的是類屬性;如果在實(shí)例方法更改某個(gè)屬性,并且存在同名的類屬性,此時(shí)若實(shí)例對(duì)象有該名稱的實(shí)例屬性,則修改的是實(shí)例屬性,若實(shí)例對(duì)象沒有該名稱的實(shí)例屬性,則會(huì)創(chuàng)建一個(gè)同名稱的實(shí)例屬性。想要修改類屬性,如果在類外,可以通過類對(duì)象修改,如果在類里面,只有在類方法中進(jìn)行修改。
從類方法和實(shí)例方法以及靜態(tài)方法的定義形式就可以看出來,類方法的第一個(gè)參數(shù)是類對(duì)象cls,那么通過cls引用的必定是類對(duì)象的屬性和方法;而實(shí)例方法的第一個(gè)參數(shù)是實(shí)例對(duì)象self,那么通過self引用的可能是類屬性、也有可能是實(shí)例屬性(這個(gè)需要具體分析),不過在存在相同名稱的類屬性和實(shí)例屬性的情況下,實(shí)例屬性優(yōu)先級(jí)更高。靜態(tài)方法中不需要額外定義參數(shù),因此在靜態(tài)方法中引用類屬性的話,必須通過類對(duì)象來引用。
?
六. 繼承、多重繼承
上面談到了類的基本定義和使用方法,這只體現(xiàn)了面向?qū)ο缶幊痰娜筇攸c(diǎn)之一:封裝。下面就來了解一下另外兩大特征:繼承和多態(tài)。
在Python中,如果需要的話,可以讓一個(gè)類去繼承一個(gè)類,被繼承的類稱為父類或者超類、也可以稱作基類,繼承的類稱為子類。并且Python支持多繼承,能夠讓一個(gè)子類有多個(gè)父類。
Python中類的繼承定義基本形式如下:
# 父類 class superClassName: block # 子類 class subClassName(superClassName): block在定義一個(gè)類的時(shí)候,可以在類名后面緊跟一對(duì)括號(hào),在括號(hào)中指定所繼承的父類,如果有多個(gè)父類,多個(gè)父類名之間用逗號(hào)隔開。以大學(xué)里的學(xué)生和老師舉例,可以定義一個(gè)父類UniversityMember,然后類Student和類Teacher分別繼承類UniversityMember:
# -*- coding: UTF-8 -*- class UniversityMember:def __init__(self, name, age):self.name = nameself.age = agedef getName(self):return self.namedef getAge(self):return self.ageclass Student(UniversityMember):def __init__(self, name, age, sno, mark):UniversityMember.__init__(self, name, age) # 注意要顯示調(diào)用父類構(gòu)造方法,并傳遞參數(shù)self self.sno = snoself.mark = markdef getSno(self):return self.snodef getMark(self):return self.markclass Teacher(UniversityMember):def __init__(self, name, age, tno, salary):UniversityMember.__init__(self, name, age)self.tno = tnoself.salary = salarydef getTno(self):return self.tnodef getSalary(self):return self.salary在大學(xué)中的每個(gè)成員都有姓名和年齡,而學(xué)生有學(xué)號(hào)和分?jǐn)?shù)這2個(gè)屬性,老師有教工號(hào)和工資這2個(gè)屬性,從上面的代碼中可以看到:
1)在Python中,如果父類和子類都重新定義了構(gòu)造方法__init( )__,在進(jìn)行子類實(shí)例化的時(shí)候,子類的構(gòu)造方法不會(huì)自動(dòng)調(diào)用父類的構(gòu)造方法,必須在子類中顯示調(diào)用。
2)如果需要在子類中調(diào)用父類的方法,需要以”父類名.方法“這種方式調(diào)用,以這種方式調(diào)用的時(shí)候,注意要傳遞self參數(shù)過去。
對(duì)于繼承關(guān)系,子類繼承了父類所有的公有屬性和方法,可以在子類中通過父類名來調(diào)用,而對(duì)于私有的屬性和方法,子類是不進(jìn)行繼承的,因此在子類中是無法通過父類名來訪問的。
Python支持多重繼承。對(duì)于多重繼承,比如
class SubClass(SuperClass1,SuperClass2)
此時(shí)有一個(gè)問題就是如果SubClass沒有重新定義構(gòu)造方法,它會(huì)自動(dòng)調(diào)用哪個(gè)父類的構(gòu)造方法?這里記住一點(diǎn):以第一個(gè)父類為中心。如果SubClass重新定義了構(gòu)造方法,需要顯示去調(diào)用父類的構(gòu)造方法,此時(shí)調(diào)用哪個(gè)父類的構(gòu)造方法由你自己決定;若SubClass沒有重新定義構(gòu)造方法,則只會(huì)執(zhí)行第一個(gè)父類的構(gòu)造方法。并且若SuperClass1和SuperClass2中有同名的方法,通過子類的實(shí)例化對(duì)象去調(diào)用該方法時(shí)調(diào)用的是第一個(gè)父類中的方法。
?
七. 多態(tài)
多態(tài)即多種形態(tài),在運(yùn)行時(shí)確定其狀態(tài),在編譯階段無法確定其類型,這就是多態(tài)。Python中的多態(tài)和Java以及C++中的多態(tài)有點(diǎn)不同,Python中的變量是弱類型的,在定義時(shí)不用指明其類型,它會(huì)根據(jù)需要在運(yùn)行時(shí)確定變量的類型(個(gè)人覺得這也是多態(tài)的一種體現(xiàn)),并且Python本身是一種解釋性語言,不進(jìn)行預(yù)編譯,因此它就只在運(yùn)行時(shí)確定其狀態(tài),故也有人說Python是一種多態(tài)語言。在Python中很多地方都可以體現(xiàn)多態(tài)的特性,比如內(nèi)置函數(shù)len(object),len函數(shù)不僅可以計(jì)算字符串的長(zhǎng)度,還可以計(jì)算列表、元組等對(duì)象中的數(shù)據(jù)個(gè)數(shù),這里在運(yùn)行時(shí)通過參數(shù)類型確定其具體的計(jì)算過程,正是多態(tài)的一種體現(xiàn)。這有點(diǎn)類似于函數(shù)重載(一個(gè)編譯單元中有多個(gè)同名函數(shù),但參數(shù)不同),相當(dāng)于為每種類型都定義了一個(gè)len函數(shù)。這是典型的多態(tài)表現(xiàn)。有些朋友提出Python不支持多態(tài),我是完全不贊同的。
本質(zhì)上,多態(tài)意味著可以對(duì)不同的對(duì)象使用同樣的操作,但它們可能會(huì)以多種形態(tài)呈現(xiàn)出結(jié)果。len(object)函數(shù)就體現(xiàn)了這一點(diǎn)。在C++、Java、C#這種編譯型語言中,由于有編譯過程,因此就鮮明地分成了運(yùn)行時(shí)多態(tài)和編譯時(shí)多態(tài)。運(yùn)行時(shí)多態(tài)是指允許父類指針或名稱來引用子類對(duì)象,或?qū)ο蠓椒?#xff0c;而實(shí)際調(diào)用的方法為對(duì)象的類類型方法,這就是所謂的動(dòng)態(tài)綁定。編譯時(shí)多態(tài)有模板或范型、方法重載(overload)、方法重寫(override)等。而Python是動(dòng)態(tài)語言,動(dòng)態(tài)地確定類型信息恰恰體現(xiàn)了多態(tài)的特征。在Python中,任何不知道對(duì)象到底是什么類型,但又需要對(duì)象做點(diǎn)什么的時(shí)候,都會(huì)用到多態(tài)。
能夠直接說明多態(tài)的兩段示例代碼如下:
?
1、方法多態(tài)
# -*- coding: UTF-8 -*- _metaclass_ = type # 確定使用新式類class Calculator:def count(self, args):return 1calc = Calculator() # 自定義類型from random import choiceobj = choice(['hello,world', [1, 2, 3], calc]) # obj是隨機(jī)返回的 類型不確定 print(type(obj)) print(obj.count('a')) # 方法多態(tài)對(duì)于一個(gè)臨時(shí)對(duì)象 obj,它通過 Python 的隨機(jī)函數(shù)取出來,不知道具體類型(是字符串、元組還是自定義類型),都可以調(diào)用count方法進(jìn)行計(jì)算,至于count由誰(哪種類型)去做怎么去實(shí)現(xiàn)我們并不關(guān)心。
有一種稱為 "鴨子類型(?duck typing )" 的東西,講的也是多態(tài):
_metaclass_ = type # 確定使用新式類class Duck:def quack(self):print("Quaaaaaack!")def feathers(self):print("The duck has white and gray feathers.")class Person:def quack(self):print("The person imitates a duck.")def feathers(self):print("The person takes a feather from the ground and shows it.")def in_the_forest(duck):duck.quack()duck.feathers()def game():donald = Duck()john = Person()in_the_forest(donald)in_the_forest(john)game()就 in_the_forest 函數(shù)而言,參數(shù)對(duì)象是一個(gè)鴨子類型,它實(shí)現(xiàn)了方法多態(tài)。但是實(shí)際上我們知道,從嚴(yán)格的抽象來講,Person類型和 Duck 完全風(fēng)馬牛不相及。
?
2、運(yùn)算符多態(tài)
def add(x, y):return x + yprint(add(1, 2)) # 輸出3 print(add("hello,", "world")) # 輸出hello,world print(add(1, "abc")) # 拋出異常 TypeError: unsupported operand type(s) for +: 'int' and 'str'上例中,顯而易見,Python 的加法運(yùn)算符是 "多態(tài)"?的,理論上,我們實(shí)現(xiàn)的add方法支持任意支持加法的對(duì)象,但是我們不用關(guān)心兩個(gè)參數(shù)x和y具體是什么類型。
Python同樣支持運(yùn)算符重載,實(shí)例如下:
class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self, other):return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2, 10) v2 = Vector(5, -2) print(v1 + v2)一兩個(gè)示例代碼當(dāng)然不能從根本上說明多態(tài)。普遍認(rèn)為面向?qū)ο笞钣袃r(jià)值最被低估的特征其實(shí)是多態(tài)。我們所理解的多態(tài)的實(shí)現(xiàn)和子類的虛函數(shù)地址綁定有關(guān)系,多態(tài)的效果其實(shí)和函數(shù)地址運(yùn)行時(shí)動(dòng)態(tài)綁定有關(guān)。在C++, Java, C#中實(shí)現(xiàn)多態(tài)的方式通常有重寫和重載兩種,從上面兩段代碼,我們其實(shí)可以分析得出Python中實(shí)現(xiàn)多態(tài)也可以變相理解為重寫和重載。在Python中很多內(nèi)置函數(shù)和運(yùn)算符都是多態(tài)的。
?
參考文獻(xiàn):
http://www.cnblogs.com/dolphin0520/archive/2013/03/29/2986924.html
http://www.cnblogs.com/jeffwongishandsome/archive/2012/10/06/2713258.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的简明Python教程学习笔记_6_面向对象编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 输入输出重定向 2>/dev
- 下一篇: 小甲鱼 OllyDbg 教程系列 (十六