面向對象編程和函數式編程(面向過程編程)都是程序設計的方法,不過稍有區別。
導入各種外部庫設計各種全局變量 寫一個函數完成某個功能寫一個函數完成某個功能寫一個函數完成某個功能寫一個函數完成某個功能寫一個函數完成某個功能.寫一個 main 函數作為程序入口在多函數程序中,許多重要的數據被放置在全局數據區,這樣它們可以被所有的函數訪問。每個函數都可以具有它們自己的局部數據,將某些功能代碼封裝到函數中,日後便無需重複編寫,僅調用函數即可。從代碼的組織形式來看就是根據業務邏輯從上到下壘代碼 。
導入各種外部庫設計各種全局變量決定你要的類給每個類提供完整的一組操作明確地使用繼承來表現不同類之間的共同點根據需要,決定是否寫一個 main 函數作為程序入口面向對象編程中,將函數和變量進一步封裝成類,類才是程序的基本元素,它將數據和操作緊密地連結在一起,並保護數據不會被外界的函數意外地改變。類和和類的實例(也稱對象)是面向對象的核心概念,是和面向過程編程、函數式編程的根本區別。
並不是非要用面向對象編程,要看你的程序怎麼設計方便,但是就目前來說,基本上都是在使用面向對象編程。
面向對象是通過定義class類來定義,這麼說面向對象編程就是只使用 class 類,在 class 類中有封裝,繼承的功能,並且還可以構造要傳入的參數,方便控制。
import sysimport timereload(sys)sys.setdefaultencoding('utf-8')
class studetn: def __init__(self,idx): self.idx=idx def runx(self): print self.idx time.sleep(1)a=studetn('a')a.runx()一些專業術語概念,既然有面向對象編程這個高大上的定義了,自然要搭配一些高大上的概念。
1.類 (Class) : 用來描述具有相同屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。其中的對象被稱作類的實例。
2.實例:也稱對象。通過類定義的初始化方法,賦予具體的值,成為一個」有血有肉的實體」。
3.實例化:創建類的實例的過程或操作。
4.實例變量:定義在實例中的變量,只作用於當前實例。
5.類變量:類變量是所有實例公有的變量。類變量定義在類中,但在方法體之外。
6.數據成員:類變量、實例變量、方法、類方法、靜態方法和屬性等的統稱。
7.方法:類中定義的函數。
8.靜態方法:不需要實例化就可以由類執行的方法
9.類方法:類方法是將類本身作為對象進行操作的方法。
10.方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對父類的方法進行改寫,這個過程也稱 override 。
11.封裝:將內部實現包裹起來,對外透明,提供 api 接口進行調用的機制
12.繼承:即一個派生類(derived class)繼承父類(base class)的變量和方法。
13.多態:根據對象類型的不同以不同的方式進行處理。
import sysimport timeimport requestsreload(sys)sys.setdefaultencoding('utf-8')class cc: ccc = 'ccc' def __init__(self,a,b,c): self.a=a self.b=b self.c=c def runx(self): print self.a*10 print self.b*5 print self.c*2 def runy(self): print requests.get('http://www.langzi.fun').headerse = cc('AAA','CCC','EEE')e.runx()e.runy()print e.cprint e.ccc調用類的三種方法實例方法import sysimport timeimport requestsreload(sys)sys.setdefaultencoding('utf-8')class dd: def __init__(self,url): self.url=url def runx(self): print requests.get(self.url).status_codea = dd('http://www.langzi.fun')a.runx()靜態方法由類調用,無默認參數。將實例方法參數中的 self 去掉,然後在方法定義上方加上 @staticmethod,就成為靜態方法。它屬於類,和實例無關。建議只使用類名.靜態方法的調用方式。(雖然也可以使用實例名.靜態方法的方式調用)
import sysimport requestsreload(sys)sys.setdefaultencoding('utf-8')class ff: @staticmethod def runx(): print requests.get('http://www.langzi.fun').status_codeff.runx()經常有一些跟類有關係的功能但在運行時又不需要實例和類參與的情況下需要用到靜態方法. 比如更改環境變量或者修改其他類的屬性等能用到靜態方法. 這種情況可以直接用函數解決, 但這樣同樣會擴散類內部的代碼,造成維護困難。
類方法類方法由類調用,採用@classmethod裝飾,至少傳入一個cls(代指類本身,類似self)參數。執行類方法時,自動將調用該方法的類賦值給cls。建議只使用類名.類方法的調用方式。(雖然也可以使用實例名.類方法的方式調用)
如果要構造一個類,接受一個網站和這個網站的狀態碼,然後列印出來。就像這樣:
import sysimport requestsreload(sys)sys.setdefaultencoding('utf-8')class gg: def __init__(self,url,stat): self.url=url self.stat=stat def outer(self): print self.url print self.stata = gg('langzi',200)a.outer()這樣就是使用實例方法,雖然可以實現,但是有的時候傳入的參數並不是 (『langzi』,200) 這樣的格式,而是 (『langzi-200』) 這樣的,那該怎麼做?首先要把這個拆分,但是要使用實例方法實現起來很麻煩,這個時候就可以使用類方法。
import sysimport requestsreload(sys)sys.setdefaultencoding('utf-8')class gg: url = 0 stat = 0 def __init__(self,url=0,stat=0): self.url=url self.stat=stat @classmethod def split(cls,info): url,stat=map(str,info.split('-')) data = cls(url,stat) return data def outer(self): print self.url print self.stat
r = gg.split(('langzi-200'))r.outer()封裝是指將數據與具體操作的實現代碼放在某個對象內部,外部無法訪問。必須要先調用類的方法才能啟動。
案例
class cc: ccc = 'ccc' def __init__(self,a,b,c): self.a=a self.b=b self.c=cprint e.cccprint ccc當我們定義一個 class 的時候,可以從某個現有的 class 繼承,新的 class 稱為子類(Subclass),而被繼承的 class 稱為基類、父類或超類(Base class、Super class)。
比如,我們已經編寫了一個名為 Animal 的 class ,有一個 run() 方法可以直接列印:class Animal(object): def run(self): print 'Animal is running...'當我們需要編寫 Dog 和 Cat 類時,就可以直接從 Animal 類繼承:
class Dog(Animal): passclass Cat(Animal): pass繼承有什麼好處?最大的好處是子類獲得了父類的全部功能。由於 Animial 實現了 run() 方法,因此, Dog 和 Cat 作為它的子類,什麼事也沒幹,就自動擁有了 run() 方法:
dog = Dog()dog.run()cat = Cat()cat.run()當子類和父類都存在相同的 run() 方法時,我們說,子類的 run() 覆蓋了父類的 run() ,在代碼運行的時候,總是會調用子類的 run() 。這樣,我們就獲得了繼承的另一個好處:多態。
要理解多態的好處,我們還需要再編寫一個函數,這個函數接受一個 Animal 類型的變量:
def run_twice(animal): animal.run() animal.run()當我們傳入 Animal 的實例時,run_twice() 就列印出:
run_twice(Animal())運行結果:Animal is running...Animal is running...當我們傳入 Dog 的實例時,run_twice() 就列印出:
run_twice(Dog())運行結果:Dog is running...Dog is running...當我們傳入 Cat 的實例時,run_twice() 就列印出:
run_twice(Cat())運行結果:Cat is running...Cat is running...看上去沒啥意思,但是仔細想想,現在,如果我們再定義一個 Tortoise 類型,也從 Animal 派生:
class Tortoise(Animal): def run(self): print 'Tortoise is running slowly...'當我們調用 run_twice() 時,傳入 Tortoise 的實例:
run_twice(Tortoise())運行結果:Tortoise is running slowly...Tortoise is running slowly...你會發現,新增一個 Animal 的子類,不必對 run_twice() 做任何修改,實際上,任何依賴 Animal 作為參數的函數或者方法都可以不加修改地正常運行,原因就在於多態。
多態的好處就是,當我們需要傳入 Dog、Cat、Tortoise…… 時,我們只需要接收 Animal 類型就可以了,因為 Dog、Cat、Tortoise…… 都是Animal類型,然後,按照 Animal 類型進行操作即可。由於 Animal 類型有 run() 方法,因此,傳入的任意類型,只要是 Animal 或者子類,就會自動調用實際類型的 run() 方法,這就是多態的意思:
對於一個變量,我們只需要知道它是 Animal 類型,無需確切地知道它的子類型,就可以放心地調用 run() 方法,而具體調用的 run() 方法是作用在 Animal、Dog、Cat 還是 Tortoise 對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種 Animal 的子類時,只要確保 run() 方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的「開閉」原則:
對擴展開放:允許新增 Animal 子類;
對修改封閉:不需要修改依賴 Animal 類型的 run_twice() 等函數。
總結:繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫;
有了繼承,才能有多態。在調用類實例方法的時候,儘量把變量視作父類類型,這樣,所有子類類型都可以正常被接收;
舊的方式定義 Python 類允許不從 object 類繼承,但這種編程方式已經嚴重不推薦使用。任何時候,如果沒有合適的類可以繼承,就繼承自 object 類。在上面有提到除了 init 之外還有 iter , reverse 的方法,這裡就詳細說下除了 init 初始化還有哪些別的方法。
__init__ : 構造函數,在生成對象時調用__del__ : 析構函數,釋放對象時使用__repr__ : 列印,轉換__setitem__ : 按照索引賦值__getitem__: 按照索引獲取值__len__: 獲得長度__cmp__: 比較運算__call__: 調用__add__: 加運算__sub__: 減運算__mul__: 乘運算__div__: 除運算__mod__: 求餘運算__pow__: 冪說明性文檔和信息。Python 自建,無需自定義。
class Foo: """ 描述類信息,可被自動收集 """ def func(self): passprint(Foo.__doc__)class Foo: def __init__(self, name): self.name = name self.age = 18obj = Foo(jack') # 自動執行類中的 __init__ 方法module 表示當前操作的對象在屬於哪個模塊。
class 表示當前操作的對象屬於哪個類。
這兩者也是 Python 內建,無需自定義。class Foo: passobj = Foo()print(obj.__module__)print(obj.__class__)運行結果:main析構方法,當對象在內存中被釋放時,自動觸發此方法。
註:此方法一般無須自定義,因為 Python 自帶內存分配和釋放機制,除非你需要在釋放的時候指定做一些動作。析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。
class Foo: def __del__(self): print("我被回收了!")
obj = Foo()del obj如果為一個類編寫了該方法,那麼在該類的實例後面加括號,可會調用這個方法。
註:構造方法的執行是由類加括號執行的,即:對象 = 類名(),而對於 call() 方法,是由對象後加括號觸發的,即:對象() 或者 類()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__')obj = Foo() obj()可以用 內建的 callable() 函數進行測試,判斷一個對象是否可以被執行。
列出類或對象中的所有成員!非常重要和有用的一個屬性,Python 自建,無需用戶自己定義。
class Province: country = 'China' def __init__(self, name, count): self.name = name self.count = count def func(self, *args, **kwargs): print('func')print(Province.__dict__)obj1 = Province('HeBei',10000)print(obj1.__dict__)obj2 = Province('HeNan', 3888)print(obj2.__dict__)如果一個類中定義了 str() 方法,那麼在列印對象時,默認輸出該方法的返回值。這也是一個非常重要的方法,需要用戶自己定義。
下面的類,沒有定義 str() 方法,列印結果是:
class Foo: passobj = Foo()print(obj)定義了__str__()方法後,列印結果是:'jack'。class Foo: def __str__(self): return 'jack'obj = Foo()print(obj)8、getitem__()、_setitem_()、__delitem()
取值、賦值、刪除這「三劍客」的套路,在 Python 中,我們已經見過很多次了,比如前面的 @property 裝飾器。
Python 中,標識符後面加圓括號,通常代表執行或調用方法的意思。而在標識符後面加中括號[],通常代表取值的意思。
Python 設計了 getitem()、setitem()、delitem() 這三個特殊成員,用於執行與中括號有關的動作。它們分別表示取值、賦值、刪除數據。
a = 標識符[] : 執行__getitem__方法標識符[] = a : 執行__setitem__方法del 標識符[] : 執行__delitem__方法如果有一個類同時定義了這三個魔法方法,那麼這個類的實例的行為看起來就像一個字典一樣,如下例所示:
class Foo: def __getitem__(self, key): print('__getitem__',key) def __setitem__(self, key, value): print('__setitem__',key,value) def __delitem__(self, key): print('__delitem__',key)obj = Foo()result = obj['k1'] obj['k2'] = 'jack' del obj['k1']這是迭代器方法!列表、字典、元組之所以可以進行 for 循環,是因為其內部定義了 iter() 這個方法。如果用戶想讓自定義的類的對象可以被迭代,那麼就需要在類中定義這個方法,並且讓該方法的返回值是一個可迭代的對象。當在代碼中利用 for 循環遍歷對象時,就會調用類的這個 iter() 方法。
class Foo: passobj = Foo()for i in obj: print(i)添加一個 __iter__(),但什麼都不返回:class Foo: def __iter__(self): passobj = Foo()for i in obj: print(i)class Foo: def __init__(self, sq): self.sq = sq def __iter__(self): return iter(self.sq)obj = Foo([11,22,33,44])for i in obj: print(i)class Foo: def __init__(self): pass def __iter__(self): yield 1 yield 2 yield 3obj = Foo()for i in obj: print(i)在 Python 中,如果你調用內置的 len() 函數試圖獲取一個對象的長度,在後臺,其實是去調用該對象的 len() 方法,所以,下面的代碼是等價的:
len('ABC')3'ABC'.__len__()3Python 的 list、dict、str 等內置數據類型都實現了該方法,但是你自定義的類要實現 len方法需要好好設計。
這個方法的作用和 str() 很像,兩者的區別是 str() 返回用戶看到的字符串,而 repr() 返回程序開發者看到的字符串,也就是說,repr() 是為調試服務的。通常兩者代碼一樣。
class Foo: def __init__(self, name): self.name = name def __str__(self): return "this is %s" % self.name __repr__ = __str__12. add__ : 加運算 _sub_ : 減運算 _mul_ : 乘運算 _div_ : 除運算 _mod_ : 求餘運算 __pow : 冪運算
這些都是算術運算方法,需要你自己為類設計具體運算代碼。有些 Python 內置數據類型,比如int就帶有這些方法。Python 支持運算符的重載,也就是重寫。
class Vector: def __init__(self, a, b): self.a = a self.b = b def __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)__author__ = "Jack"def show(): print(__author__)show()Python 作為一種動態語言,可以在類定義完成和實例化後,給類或者對象繼續添加隨意個數或者任意類型的變量或方法,這是動態語言的特性。例如:
def print_doc(self): print("haha")class Foo: passobj1 = Foo()obj2 = Foo()obj1.name = "jack"obj2.age = 18Foo.show = print_docobj1.show()obj2.show()但是!如果我想限制實例可以添加的變量怎麼辦?可以使 slots 限制實例的變量,比如,只允許 Foo 的實例添加 name 和 age 屬性。
def print_doc(self): print("haha")class Foo: __slots__ = ("name", "age") passobj1 = Foo()obj2 = Foo()obj1.name = "jack"obj2.age = 18obj1.sex = "male" Foo.show = print_docobj1.show()obj2.show()由於'sex'不在__slots__的列表中,所以不能綁定 sex 屬性 ,試圖綁定 sex 將得到 AttributeError 的錯誤。Traceback (most recent call last): File "F:/Python/pycharm/201705/1.py", line 14, in <module> obj1.sex = "male"AttributeError: 'Foo' object has no attribute 'sex'需要提醒的是,slots 定義的屬性僅對當前類的實例起作用,對繼承了它的子類是不起作用的。想想也是這個道理,如果你繼承一個父類,卻莫名其妙發現有些變量無法定義,那不是大問題麼?如果非要子類也被限制,除非在子類中也定義 slots ,這樣,子類實例允許定義的屬性就是自身的 slots 加上父類的 slots 。
有些對象你不想外部訪問,即使是通過調用類對象也無法訪問,那就請認真學完本章節。
class obj: def __init__(self,name): self.name=name def pri(self): print self.name __age = 18 a = obj('zhao')a.pri()class obj: def __init__(self,name): self.name=name def prin(self): print self.name __age = 18 @classmethod def pri(cls): print cls.__age a = obj('zhao')a.prin()obj.pri()class obj: def __init__(self,name): self.name=name def prin(self): print self.name __age = 18 @classmethod def pri(cls): print cls.__age @classmethod def set_age(cls,value): cls.__age = value return cls.__age @classmethod def get_age(cls): return cls.__age @classmethod def del_age(cls): del cls.__age print obj.get_age()print obj.set_age(20)obj.del_age()思考: 既然是私有變量,不讓外部訪問,為何有要在後面調用又改變呢?因為可以對私有變量進行額外的檢測,處理,加工等等。比如判斷 value 的值,使用 isinstance 然後做 if-else判斷。
使用私有變量可以對內部變量進行保護,外部無法改變,但是可以對它進行檢測處理。
這裡引申一下私有成員的保護機制,使用 __age 對私有變量其實就是—> obj._obj__age 的樣子進行保護,說白了你直接使用 obj._obj__age 就可以直接調用內部私有變量 age 了。
把類的方法偽裝成屬性調用的方式,就是把類裡面的一個函數,變成一個屬性一樣的東西~
一開始調用類的方法要使用圓括號,現在變成了屬性進行讀取設置存儲。
舉個例子來說明:class obj: def __init__(self,name,age): self.__name=name self.__age=age def get_age(self): return self.__age def set_age(self,value): if isinstance(value,int): self.__age=value else: raise ValueError('非整數類型') def del_age(self): print 'delete over'a = obj('langzi',18)print a.get_age()a.set_age(20)print a.get_age()class obj: def __init__(self,name,age): self.__name=name self.__age=age @property def age(self): return self.__age @age.setter def age(self,value): if isinstance(value,int): self.__age=value else: raise ValueError('非整數類型') @age.deleter def age(self): print 'delete over'a = obj('langzi',18)print a.agea.age=20print a.agedel a.age當然這種調用方法有些麻煩,每次都是一個一個去實例類與對象,有個更加簡單直觀的方法。
更加減半的使用 property() 函數
除了使用裝飾器的方式將一個方法偽裝成屬性外,Python 內置的 builtins 模塊中的 property() 函數,為我們提供了第二種設置類屬性的手段。
class People: def __init__(self, name, age): self.__name = name self.__age = age def get_age(self): return self.__age def set_age(self, age): if isinstance(age, int): self.__age = age else: raise ValueError def del_age(self): print("刪除年齡數據!") age = property(get_age, set_age, del_age, "年齡") obj = People("jack", 18)print(obj.age)obj.age = 19print("obj.age: ", obj.age)del obj.ag通過語句 age = property(get_age, set_age, del_age , 「年齡」)將一個方法偽裝成為屬性。其效果和裝飾器的方法是一樣的。
property() 函數的參數:
第一個參數是方法名,調用 實例.屬性 時自動執行的方法第二個參數是方法名,調用 實例.屬性 = XXX時自動執行的方法第三個參數是方法名,調用 del 實例.屬性 時自動執行的方法第四個參數是字符串,調用 實例.屬性.__doc__時的描述信息。原文連結:
https://mp.weixin.qq.com/s/nI3lcTQ-QIIfql97nTjxmw