《python小白入門系列教程》html
有對象嗎?
python
沒有就new 一個
git
今天咱們要用python new 一個對象程序員
核心是過程(流水線式思惟),過程即解決問題的步驟,面向過程的設計就比如精心設計好一條流水線,考慮周全何時處理什麼東西。編程
優勢是:極大的下降了寫程序的複雜度,只須要順着要執行的步驟,堆疊代碼便可。函數
缺點是:一套流水線或者流程就是用來解決一個問題,代碼牽一髮而動全身。oop
應用場景:一旦完成基本不多改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。學習
核心是對象(上帝式思惟),==要理解對象爲什麼物,必須把本身當成上帝==,上帝眼裏世間存在的萬物皆爲對象,不存在的也能夠創造出來。this
面向對象的程序設計比如如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題須要四我的:唐僧,沙和尚,豬八戒,孫悟空,每一個人都有各自的特徵和技能(這就是對象的概念,特徵和技能分別對應對象的屬性和方法)lua
然而這並很差玩,因而如來又安排了一羣妖魔鬼怪,爲了防止師徒四人在取經路上被搞死,又安排了一羣神仙保駕護航,這些都是對象。而後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥着直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。
優勢是:解決了程序的擴展性。對某一個對象單獨修改,會馬上反映到整個體系中,如對遊戲中一我的物參數的特徵和技能修改都很容易。
缺點:可控性差,沒法向面向過程的程序設計流水線式的能夠很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即使是上帝也沒法預測最終結果。因而咱們常常看到一個遊戲人某一參數的修改極有可能致使陰霸的技能出現,一刀砍死3我的,這個遊戲就失去平衡。
應用場景:需求常常變化的軟件,通常需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方。
類:具備相同特徵的一類事物(人、狗、老虎,機器人)
對象/實例:具體的某一個事物(隔壁阿花、樓下旺財)
實例化:類——>對象的過程
==開始一本正經的寫教程==
類與對象是面向對象編程的兩個主要方面。一個類(Class)
可以建立一種新的類型 (Type)
,其中對象(Object)
就是類的實例(Instance)
。能夠這樣來類比:你能夠擁有 類型 int
的變量,也就是說存儲整數的變量是 int 類的實例(對象)。
對象可使用屬於它的普通變量來存儲數據。這種從屬於對象或類的變量叫做字段 (Field)。對象還可使用屬於類的函數來實現某些功能,這種函數叫做類的方法 (Method)。這兩個術語很重要,它有助於咱們區分函數與變量,哪些是獨立的,哪些又是 屬於類或對象的。總之,字段與方法通稱類的屬性(Attribute)
字段有兩種類型——它們屬於某一類的各個實例或對象,或是從屬於某一類自己。它們被分 別稱做實例變量(Instance Variables)與類變量(
Class Variables`)。
經過 class 關鍵字能夠建立一個類。這個類的字段與方法能夠在縮進代碼塊中予以列出。
類方法與普通函數只有一種特定的區別——前者必須多加一個參數在參數列表開頭,這個名 字必須添加到參數列表的開頭,可是你不用在你調用這個功能時爲這個參數賦值,Python 會 爲它提供。這種特定的變量引用的是對象自己,按照慣例,它被賦予 self
這一名稱。
儘管你能夠爲這一參數賦予任何名稱,可是強烈推薦你使用 self 這一名稱——其它的任何 一種名稱絕對會引人皺眉。使用一個標準名稱能帶來諸多好處——任何一位你的程序的讀者 可以當即認出它,甚至是專門的 IDE
(Integrated Development Environments,集成開發環 境)也能夠爲你提供幫助,只要你使用了 self 這一名稱。
針對 C++/Java/C# 程序員的提示 Python 中的 self 至關於 C++ 中的 this 指針以及 Java 與 C# 中的 this 引用。
你必定會在想 Python 是如何給 self 賦值的,以及爲何你沒必要給它一個值。一個例子或許 會讓這些疑問獲得解答。假設你有一個 MyClass 的類,這個類下有一個實例 myobject 。當 你調用一個這個對象的方法,如 myobject.method(arg1, arg2)
時,Python 將會自動將其轉 換成 MyClass.method(myobject, arg1, arg2)
——這就是 self 的所有特殊之處所在。
這同時意味着,若是你有一個沒有參數的方法,你依舊必須擁有一個參數—— self 。
最簡單的類(Class
)能夠經過下面的案例來展現(保存爲 oop_simplestclass.py ):
class Person : pass # 一個空的代碼塊 p = Person() print(p)
輸出:
python oop_simplestclass.py <__main__.Person instance at 0x10171f518> 它是如何工做的
咱們經過使用 class 語句與這個類的名稱來建立一個新類。在它以後是一個縮進的語句塊, 表明這個類的主體。在本案例中,咱們建立的是一個空代碼塊,使用 pass 語句予以標明。
而後,咱們經過採用類的名稱後跟一對括號的方法,給這個類建立一個對象。爲了驗證咱們的操做是否成功,咱們經過 直接將它們打印出來來確認變量的類型。結果告訴咱們咱們在 Person 類的 __main__
模塊 中擁有了一個實例。
要注意到在本例中還會打印出計算機內存中存儲你的對象的地址。案例中給出的地址會與你 在你的電腦上所能看見的地址不相同,由於 Python 會在它找到的任何空間來存儲對象。
咱們已經在前面討論過類與對象一如函數那般均可以帶有方法(Method),惟一的不一樣在於 咱們還擁有一個額外的 self 變量。如今讓咱們來看看下面的例子(保存爲 oop_method.py )。
class Person: def say_hi(self): print('Hello, 你們好?') p = Person() p.say_hi() # 前面兩行一樣能夠寫做 # Person().say_hi()
輸出:
python oop_method.py Hello, 你們好? 它是如何工做的
這裏咱們就能看見 self 是如何行動的了。要注意到 say_hi 這一方法不須要參數,可是依 舊在函數定義中擁有 self 變量。
在 Python 的類中,有很多方法的名稱具備着特殊的意義。如今咱們要了解的就是 __init__ 方法的意義。
__init__ 方法會在類的對象被實例化 時當即運行。這一方法能夠對任何你想 進行操做的目標對象進行初始化 操做。==這裏你要注意在 init 先後加上的雙下 劃線==。
案例(保存爲 oop_init.py ):
class Person: def __init__(self, name): self.name = name def say_hi(self): print('Hello, 個人名字是', self.name) p = Person('木木') p.say_hi() # 前面兩行同時也能寫做 # Person('Swaroop').say_hi()
輸出:
python oop_init.py Hello, 個人名字是木木 它是如何工做的
在本例中,咱們定義一個接受 name 參數(固然還有 self 參數)的 __init__ 方法。在這 裏,咱們建立了一個字段,一樣稱爲 name 。要注意到儘管它們的名字都是「name」,但這是 兩個不相同的變量。雖然說如此,但這並不會形成任何問題,由於 self.name 中的點號意味着 這個叫做「name」的東西是某個叫做「self」的對象的一部分,而另外一個 name 則是一個局部變 量。因爲咱們已經如上這般明確指出了咱們所指的是哪個名字,因此它不會引起混亂。
當咱們在 Person 類下建立新的實例 p 時,咱們採用的方法是先寫下類的名稱,後跟括在 括號中的參數,形如:p = Person('木木')
。
咱們不會顯式地調用 __init__ 方法。這正是這個方法的特殊之處所在。
如今,咱們可使用咱們方法中的 self.name 字段了,使用的方法在 say_hi 方法中已經做 過說明。
咱們已經討論過了類與對象的功能部分(即方法),如今讓咱們來學習它們的數據部分。數 據部分——也就是字段——只不過是綁定到類與對象的命名空間 的普通變量。這就表明着這些名稱僅在這些類與對象所存在的上下文中有效。這就是它們被 稱做「命名空間」的緣由。
字段(Field)有兩種類型——類變量與對象變量,它們根據到底是類仍是對象擁有這些變量 來進行分類。
類變量(Class Variable)是共享的(Shared)——它們能夠被屬於該類的全部實例訪問。該類變量只擁有一個副本,當任何一個對象對類變量做出改變時,發生的變更將在其它全部 實例中都會獲得體現
對象變量(Object variable)由類的每個獨立的對象或實例所擁有。在這種狀況下,每一個 對象都擁有屬於它本身的字段的副本,也就是說,它們不會被共享,也不會以任何方式與其 它不一樣實例中的相同名稱的字段產生關聯。下面一個例子能夠幫助你理解(保存爲 oop_objvar.py ):
# coding=UTF-8 class Robot: """表示有一個帶有名字的機器人。""" # 一個類變量,用來計數機器人的數量 population = 0 def __init__(self, name): """初始化數據""" self.name = name print("初始化 {})".format(self.name)) # 當有人被建立時,機器人 # 將會增長人口數量 Robot.population += 1 def die(self): """我掛了。""" print("{} 被銷燬了!".format(self.name)) Robot.population -= 1 if Robot.population == 0: print("{} 是最後一個機器人".format(self.name)) else: print("還有 {:d} 個機器人在工做.".format( Robot.population)) def say_hi(self): """來自機器人的誠摯問候 沒問題,你作獲得。""" print("你們好,個人主人叫我{}.".format(self.name)) @classmethod def how_many(cls): """打印出當前的機器人,人口數量""" print("如今還有{:d} 個機器人.".format(cls.population)) droid1 = Robot("R2-D2") droid1.say_hi() Robot.how_many() droid2 = Robot("C-3PO") droid2.say_hi() Robot.how_many() print("\n機器人正在努力工做.\n") print("機器人已經完成它的使命咱們要銷燬它.") droid1.die() droid2.die() Robot.how_many()
輸出
python oop_objvar.py (初始化 R2-D2) 你們好,個人主人叫我 R2-D2. 還有 1個機器人在工做. (初始化 C-3PO) 你們好,個人主人叫我 C-3PO. 還有 2 個機器人在工做. 機器人正在努力工做. 機器人已經完成它的使命咱們要銷燬它. R2-D2 被銷燬了! 還有 1 個機器人在工做. C-3PO 被銷燬了! C-3PO 是最後一個機器人 還有0個機器人在工做.
它是如何工做的
這是一個比較長的案例,可是它有助於展示類與對象變量的本質。在本例中, population
屬 於 Robot 類,所以它是一個類變量。name 變量屬於一個對象(經過使用 self 分配),因 此它是一個對象變量。
所以,咱們經過 Robot.population 而非 self.population 引用 population 類變量。咱們對 於 name 對象變量採用 self.name 標記法加以稱呼,這是這個對象中所具備的方法。要記住 這個類變量與對象變量之間的簡單區別。同時你還要注意當一個對象變量與一個類變量名稱 相同時,類變量將會被隱藏。
除了 Robot.popluation ,咱們還可使用 self.__class__.population ,由於每一個對象都經過 self.__class__ 屬性來引用它的類。
how_many 其實是一個屬於類而非屬於對象的方法。這就意味着咱們能夠將它定義爲一個 classmethod(類方法) 或是一個 staticmethod(靜態方法) ,這取決於咱們是否須要知道這一方 法屬於哪一個類。因爲咱們已經引用了一個類變量,所以咱們使用 classmethod(類方法) 。
咱們使用裝飾器(Decorator)將 how_many
方法標記爲類方法。
你能夠將裝飾器想象爲調用一個包裝器函數的快捷方式,所以啓用
@classmethod 裝飾器等價於調用:
how_many = classmethod(how_many)
你會觀察到 __init__ 方法會使用一個名字以初始化 Robot 實例。在這一方法中,咱們將 population 按 1 往上增加,由於咱們多增長了一臺機器人。你還會觀察到 self.name 的值 是指定給每一個對象的,這體現了對象變量的本質。
你須要記住你只能使用 self 來引用同一對象的變量與方法。這被稱做屬性引用。
在本程序中,咱們還會看見針對類和方法的 文檔字符串(DocStrings
) 的使用方式。咱們可 以在運行時經過 Robot.__doc__ 訪問類的 文檔字符串,對於方法的文檔字符串,則可使用 Robot.say_hi.__doc__ 。
在 die 方法中,咱們簡單地將 Robot.population 的計數按 1 向下減小。
全部的類成員都是公開的。但有一個例外:若是你使用數據成員並在其名字中使用雙下劃線 做爲前綴,造成諸如 __privatevar
這樣的形式,Python 會使用名稱調整使其有效地成爲一個私有變量。
所以,你須要遵循這樣的約定:任何在類或對象之中使用的變量其命名應如下劃線開頭,其 它全部非此格式的名稱都將是公開的,並能夠爲其它任何類或對象所使用。請記得這只是一 個約定,==Python 並不強制如此(除了雙下劃線前綴這點)==。
全部類成員(包括數據成員)都是公開的,而且 Python 中全部的方法都是虛擬的 (Virtual)。
面向對象編程的一大優勢是對代碼的重用,重用的一種實現方法就是經過繼承 機制。繼承最好是想象成在類之間實現類型與子類型
繼承是一種建立新類的方式,在python中,新建的類能夠繼承一個或多個父類,父類又可稱爲基類或超類,新建的類稱爲派生類或子類
如今假設你但願編寫一款程序來追蹤一所大學裏的老師和學生。有一些特徵是他們都具備 的,例如姓名、年齡和地址。另一些特徵是他們獨有的,一如教師的薪水、課程與假期, 學生的成績和學費。
你能夠爲每一種類型建立兩個獨立的類,並對它們進行處理。但增添一條共有特徵就意味着 將其添加進兩個獨立的類。這很快就會使程序變得笨重。
一個更好的方法是建立一個公共類叫做 SchoolMember ,而後讓教師和學生從這個類中繼承 ,也就是說他們將成爲這一類的子類型,而咱們就能夠向這些子類型中添 加某些該類獨有的特徵。
同時還須要注意的是咱們重用父類的代碼,但咱們不須要再在其它類中重複它們,當咱們使 用獨立類型時纔會必要地重複這些代碼。
在上文設想的狀況中, SchoolMember 類會被稱做基類
(Base Class)或是超類 (Superclass)。Teacher 和 Student 類會被稱做派生類或是子類 (Subclass)。
咱們將經過下面的程序做爲案例來進行了解(保存爲 oop_subclass.py ):
# coding=UTF-8 class SchoolMember: '''表明任何學校裏的成員。''' def __init__(self, name, age): self.name = name self.age = age print('(初始化學校成員: {})'.format(self.name)) def tell(self): '''告訴我有關個人細節。''' print('姓名:"{}" 年齡:"{}"'.format(self.name, self.age), end=" ") class Teacher(SchoolMember): '''表明一位老師。''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self.salary = salary print('(初始化教師: {})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('薪資: "{:d}"'.format(self.salary)) class Student(SchoolMember): '''表明一位學生。''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print('(初始化學生: {})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('分數: "{:d}"'.format(self.marks)) t = Teacher('曾老師', 30, 30000) s = Student('小明', 20, 75) # 打印一行空白行 print() members = [t, s] for member in members: # 對全體師生工做 member.tell()
輸出:
python oop_subclass.py (初始化學校成員: 曾老師) (初始化教師: 曾老師) (初始化學校成員: 小明) (初始化學生: 小明) 姓名:"曾老師" 年齡:"30" 薪資: "30000" 姓名:"小明" Age:"20" 分數: "75"
它是如何工做的
要想使用繼承,在定義類 時咱們須要在類後面跟一個包含基類名稱的元組。
而後,咱們會注 意到基類的 __init__ 方法是經過 self
變量被顯式調用的,所以咱們能夠初始化對象的基 類部分。
下面這一點很重要,須要牢記——由於咱們在 Teacher 和 Student 子類中定義了 __init__ 方法,Python 不會自動調用基類 SchoolMember 的構造函數,你必須本身顯式地 調用它。
相反,若是咱們沒有在一個子類中定義一個 __init__ 方法,Python 將會自動調用基類的構 造函數。
咱們會觀察到,咱們能夠經過在方法名前面加上基類名做爲前綴,再傳入 self 和其他變 量,來調用基類的方法。
在這裏你須要注意,當咱們使用 SchoolMember
類的 tell 方法時,咱們能夠將 Teacher 或 Student 的實例看做 SchoolMember 的實例。
同時,你會發現被調用的是子類型的 tell 方法,而不是 SchoolMember
的 tell 方法。理 解這一問題的一種思路是 Python
總會從當前的實際類型中開始尋找方法,在本例中便是如 此。若是它找不到對應的方法,它就會在該類所屬的基本類中依順序逐個尋找屬於基本類的 方法,這個基本類是在定義子類時後跟的元組指定的。
這裏有一條有關術語的註釋——若是繼承元組中有超過一個類,這種情 況就會被稱做多重繼承
。
end
參數用在超類的 tell()
方法的 print
函數中,目的是打印一行並容許下一次打印在 同一行繼續。這是一個讓 print 可以不在打印的末尾打印出 n (新行換行符)符號的小 竅門。
抽象指對現實世界問題和實體的本質表現,行爲和特徵建模,創建一個相關的子集,能夠用於 繪程序結構,從而實現這種模型。抽象不只包括這種模型的數據屬性,還定義了這些數據的接口。
對某種抽象的實現就是對此數據及與之相關接口的現實化(realization)。現實化這個過程對於客戶 程序應當是透明並且無關的。
封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。經過任何客戶端直接對數據的訪問,無視接口,與封裝性都是背道而馳的,除非程序員容許這些操做。做爲實現的 一部分,客戶端根本就不須要知道在封裝以後,數據屬性是如何組織的。在Python中,全部的類屬性都是公開的,但名字可能被「混淆」了,以阻止未經受權的訪問,但僅此而已,再沒有其餘預防措施了。這就須要在設計時,對數據提供相應的接口,以避免客戶程序經過不規範的操做來存取封裝的數據屬性。
注意:封裝毫不是等於「把不想讓別人看到、之後可能修改的東西用private隱藏起來」
真正的封裝是,通過深刻的思考,作出良好的抽象,給出「完整且最小」的接口,並使得內部細節能夠對外透明
(注意:對外透明的意思是,外部調用者能夠順利的獲得本身想要的任何功能,徹底意識不到內部細節的存在)
合成擴充了對類的 述,使得多個不一樣的類合成爲一個大的類,來解決現實問題。合成 述了 一個異常複雜的系統,好比一個類由其它類組成,更小的組件也多是其它的類,數據屬性及行爲, 全部這些合在一塊兒,彼此是「有一個」的關係。
派生描述了子類衍生出新的特性,新類保留已存類類型中全部須要的數據和行爲,但容許修改或者其它的自定義操做,都不會修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結構表示多「代」派生,能夠述成一個「族譜」,連續的子類,與祖先類都有關係。
基於繼承
泛化表示全部子類與其父類及祖先類有同樣的特色。
特化描述全部子類的自定義,也就是,什麼屬性讓它與其祖先類不一樣。
多態指的是同一種事物的多種狀態:水這種事物有多種不一樣的狀態:冰,水蒸氣
多態性的概念指出了對象如何經過他們共同的屬性和動做來操做及訪問,而不需考慮他們具體的類。
冰,水蒸氣,都繼承於水,它們都有一個同名的方法就是變成雲,可是冰.變雲(),與水蒸氣.變雲()是大相徑庭的過程,雖然調用的方法都同樣
自省也稱做反射,這個性質展現了某對象是如何在運行期取得自身信息的。若是傳一個對象給你,你能夠查出它有什麼能力,這是一項強大的特性。若是Python不支持某種形式的自省功能,dir和type內建函數,將很難正常工做。還有那些特殊屬性,像__dict__
,__name__
及__doc__
練習地址:www.520mg.com/it