史上最全 Python 面向?qū)ο缶幊?/h1>
轉(zhuǎn)自:浪子燕青
http://www.langzi.fun/Python面向?qū)ο缶幊?html
面向?qū)ο缶幊毯秃瘮?shù)式編程(面向過(guò)程編程)都是程序設(shè)計(jì)的方法,不過(guò)稍有區(qū)別。
面向過(guò)程編程:
1. 導(dǎo)入各種外部庫(kù)
2. 設(shè)計(jì)各種全局變量
3. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
4. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
5. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
6. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
7. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
8. ......
9. 寫(xiě)一個(gè)main函數(shù)作為程序入口
在多函數(shù)程序中,許多重要的數(shù)據(jù)被放置在全局?jǐn)?shù)據(jù)區(qū),這樣它們可以被所有的函數(shù)訪問(wèn)。每個(gè)函數(shù)都可以具有它們自己的局部數(shù)據(jù),將某些功能代碼封裝到函數(shù)中,日后便無(wú)需重復(fù)編寫(xiě),僅調(diào)用函數(shù)即可。從代碼的組織形式來(lái)看就是根據(jù)業(yè)務(wù)邏輯從上到下壘代碼 。
面向?qū)ο缶幊蹋?/strong>
1. 導(dǎo)入各種外部庫(kù)
2. 設(shè)計(jì)各種全局變量
3. 決定你要的類(lèi)
4. 給每個(gè)類(lèi)提供完整的一組操作
5. 明確地使用繼承來(lái)表現(xiàn)不同類(lèi)之間的共同點(diǎn)
6. 根據(jù)需要,決定是否寫(xiě)一個(gè)main函數(shù)作為程序入口
面向?qū)ο缶幊讨校瑢⒑瘮?shù)和變量進(jìn)一步封裝成類(lèi),類(lèi)才是程序的基本元素,它將數(shù)據(jù)和操作緊密地連結(jié)在一起,并保護(hù)數(shù)據(jù)不會(huì)被外界的函數(shù)意外地改變。類(lèi)和和類(lèi)的實(shí)例(也稱(chēng)對(duì)象)是面向?qū)ο蟮暮诵母拍睿呛兔嫦蜻^(guò)程編程、函數(shù)式編程的根本區(qū)別。
并不是非要用面向?qū)ο缶幊蹋茨愕某绦蛟趺丛O(shè)計(jì)方便,但是就目前來(lái)說(shuō),基本上都是在使用面向?qū)ο缶幊獭?/p>
類(lèi)的基本用法
面向?qū)ο笫峭ㄟ^(guò)定義class類(lèi)來(lái)定義,這么說(shuō)面向?qū)ο缶幊叹褪侵皇褂胏lass類(lèi),在class類(lèi)中有封裝,繼承的功能,并且還可以構(gòu)造要傳入的參數(shù),方便控制。
案例一
import sys
import time
reload(sys)
sys.setdefaultencoding('utf-8')
class studetn:
# 定義一個(gè)類(lèi)名為studetn
def __init__(self,idx):
# 定義初始化構(gòu)造,這里使用init,還有別的屬性比如reversed,iter之類(lèi)的
self.idx=idx
# 初始化變量,方便繼承
def runx(self):
# 定義運(yùn)行函數(shù),從上面繼承變量
print self.idx
# 打印出idx的值,或者做一些別的處理
time.sleep(1)
a=studetn('a')
a.runx()
# 這是類(lèi)的調(diào)用,一定要記得類(lèi)的使用方法,首先傳入?yún)?shù),類(lèi)賦值給一個(gè)變量a
# 然后調(diào)用這個(gè)類(lèi)下面定義的函數(shù)
一些專(zhuān)業(yè)術(shù)語(yǔ)概念,既然有面向?qū)ο缶幊踢@個(gè)高大上的定義了,自然要搭配一些高大上的概念。
- 類(lèi)(Class): 用來(lái)描述具有相同屬性和方法的對(duì)象的集合。它定義了該集合中每個(gè)對(duì)象所共有的屬性和方法。其中的對(duì)象被稱(chēng)作類(lèi)的實(shí)例。
- 實(shí)例:也稱(chēng)對(duì)象。通過(guò)類(lèi)定義的初始化方法,賦予具體的值,成為一個(gè)”有血有肉的實(shí)體”。
- 實(shí)例化:創(chuàng)建類(lèi)的實(shí)例的過(guò)程或操作。
- 實(shí)例變量:定義在實(shí)例中的變量,只作用于當(dāng)前實(shí)例。
- 類(lèi)變量:類(lèi)變量是所有實(shí)例公有的變量。類(lèi)變量定義在類(lèi)中,但在方法體之外。
- 數(shù)據(jù)成員:類(lèi)變量、實(shí)例變量、方法、類(lèi)方法、靜態(tài)方法和屬性等的統(tǒng)稱(chēng)。
- 方法:類(lèi)中定義的函數(shù)。
- 靜態(tài)方法:不需要實(shí)例化就可以由類(lèi)執(zhí)行的方法
- 類(lèi)方法:類(lèi)方法是將類(lèi)本身作為對(duì)象進(jìn)行操作的方法。
- 方法重寫(xiě):如果從父類(lèi)繼承的方法不能滿足子類(lèi)的需求,可以對(duì)父類(lèi)的方法進(jìn)行改寫(xiě),這個(gè)過(guò)程也稱(chēng)override。
- 封裝:將內(nèi)部實(shí)現(xiàn)包裹起來(lái),對(duì)外透明,提供api接口進(jìn)行調(diào)用的機(jī)制
- 繼承:即一個(gè)派生類(lèi)(derived class)繼承父類(lèi)(base class)的變量和方法。
- 多態(tài):根據(jù)對(duì)象類(lèi)型的不同以不同的方式進(jìn)行處理。
類(lèi)與實(shí)例
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:02
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import time
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class cc:
ccc = 'ccc'
# cc就是類(lèi)名 如果想要繼承別的類(lèi) 就class cc(threading) 意思就是從threading繼承
def __init__(self,a,b,c):
self.a=a
self.b=b
self.c=c
# 定義構(gòu)造的過(guò)程就是實(shí)例化
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').headers
e = cc('AAA','CCC','EEE')
e.runx()
e.runy()
# 這兩個(gè)就是調(diào)用類(lèi)里面的方法
print e.c
#實(shí)例變量指的是實(shí)例本身?yè)碛械淖兞俊C總€(gè)實(shí)例的變量在內(nèi)存中都不一樣。
print e.ccc
#類(lèi)變量,在類(lèi)里面找到定義的變量。
調(diào)用類(lèi)的三種方法
實(shí)例方法
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:16
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import time
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class dd:
def __init__(self,url):
self.url=url
def runx(self):
print requests.get(self.url).status_code
a = dd('http://www.langzi.fun')
a.runx()
# 這種調(diào)用方法就是實(shí)例方法
靜態(tài)方法
靜態(tài)方法由類(lèi)調(diào)用,無(wú)默認(rèn)參數(shù)。將實(shí)例方法參數(shù)中的self去掉,然后在方法定義上方加上@staticmethod,就成為靜態(tài)方法。它屬于類(lèi),和實(shí)例無(wú)關(guān)。建議只使用類(lèi)名.靜態(tài)方法的調(diào)用方式。(雖然也可以使用實(shí)例名.靜態(tài)方法的方式調(diào)用)
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:21
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class ff:
@staticmethod
def runx():
print requests.get('http://www.langzi.fun').status_code
ff.runx()
#這里就直接調(diào)用了類(lèi)的變量,只在類(lèi)中運(yùn)行而不在實(shí)例中運(yùn)行的方法
經(jīng)常有一些跟類(lèi)有關(guān)系的功能但在運(yùn)行時(shí)又不需要實(shí)例和類(lèi)參與的情況下需要用到靜態(tài)方法. 比如更改環(huán)境變量或者修改其他類(lèi)的屬性等能用到靜態(tài)方法. 這種情況可以直接用函數(shù)解決, 但這樣同樣會(huì)擴(kuò)散類(lèi)內(nèi)部的代碼,造成維護(hù)困難。
類(lèi)方法
類(lèi)方法由類(lèi)調(diào)用,采用@classmethod裝飾,至少傳入一個(gè)cls(代指類(lèi)本身,類(lèi)似self)參數(shù)。執(zhí)行類(lèi)方法時(shí),自動(dòng)將調(diào)用該方法的類(lèi)賦值給cls。建議只使用類(lèi)名.類(lèi)方法的調(diào)用方式。(雖然也可以使用實(shí)例名.類(lèi)方法的方式調(diào)用)
實(shí)際案例
如果要構(gòu)造一個(gè)類(lèi),接受一個(gè)網(wǎng)站和這個(gè)網(wǎng)站的狀態(tài)碼,然后打印出來(lái)。就像這樣:
import sys
import requests
reload(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.stat
a = gg('langzi',200)
a.outer()
這樣就是使用實(shí)例方法,雖然可以實(shí)現(xiàn),但是有的時(shí)候傳入的參數(shù)并不是(‘langzi’,200)這樣的格式,而是(‘langzi-200’)這樣的,那該怎么做?首先要把這個(gè)拆分,但是要使用實(shí)例方法實(shí)現(xiàn)起來(lái)很麻煩,這個(gè)時(shí)候就可以使用類(lèi)方法。
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:27
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class gg:
url = 0
stat = 0
# 因?yàn)槭褂胏lassmethod后會(huì)傳入新的變量,所以一開(kāi)始是需要自己先定義類(lèi)變量
def __init__(self,url=0,stat=0):
# 這里按照正常的定義構(gòu)造函數(shù)
self.url=url
self.stat=stat
@classmethod
# 裝飾器,立馬執(zhí)行下面的函數(shù)
def split(cls,info):
# 這個(gè)函數(shù)接受兩個(gè)參數(shù),默認(rèn)的cls就是這個(gè)類(lèi)的init函數(shù),info就是外面?zhèn)魅脒M(jìn)來(lái)的
url,stat=map(str,info.split('-'))
# 這里轉(zhuǎn)換成了格式化的結(jié)構(gòu)
data = cls(url,stat)
# 然后執(zhí)行這個(gè)類(lèi)第一個(gè)方法,這個(gè)類(lèi)構(gòu)造函數(shù)需要傳入兩個(gè)參數(shù),于是就傳入了兩個(gè)參數(shù)
return data
# 這里就直接返回了函數(shù)結(jié)果
def outer(self):
print self.url
print self.stat
r = gg.split(('langzi-200'))
r.outer()
# 這里是調(diào)用類(lèi)方法,與調(diào)用實(shí)例方法一樣
類(lèi)的特性
封裝
封裝是指將數(shù)據(jù)與具體操作的實(shí)現(xiàn)代碼放在某個(gè)對(duì)象內(nèi)部,外部無(wú)法訪問(wèn)。必須要先調(diào)用類(lèi)的方法才能啟動(dòng)。
案例
class cc:
ccc = 'ccc'
# cc就是類(lèi)名 如果想要繼承別的類(lèi) 就class cc(threading) 意思就是從threading繼承
def __init__(self,a,b,c):
self.a=a
self.b=b
self.c=c
print e.ccc
#類(lèi)變量,在類(lèi)里面找到定義的變量。
print ccc
# 這里會(huì)報(bào)錯(cuò),這就是封裝。類(lèi)中的函數(shù)同理。
繼承
當(dāng)我們定義一個(gè)class的時(shí)候,可以從某個(gè)現(xiàn)有的class繼承,新的class稱(chēng)為子類(lèi)(Subclass),而被繼承的class稱(chēng)為基類(lèi)、父類(lèi)或超類(lèi)(Base class、Super class)。
比如,我們已經(jīng)編寫(xiě)了一個(gè)名為Animal的class,有一個(gè)run()方法可以直接打印:
class Animal(object):
def run(self):
print 'Animal is running...'
當(dāng)我們需要編寫(xiě)Dog和Cat類(lèi)時(shí),就可以直接從Animal類(lèi)繼承:
class Dog(Animal):
pass
class Cat(Animal):
pass
繼承有什么好處?最大的好處是子類(lèi)獲得了父類(lèi)的全部功能。由于Animial實(shí)現(xiàn)了run()方法,因此,Dog和Cat作為它的子類(lèi),什么事也沒(méi)干,就自動(dòng)擁有了run()方法:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
當(dāng)子類(lèi)和父類(lèi)都存在相同的run()方法時(shí),我們說(shuō),子類(lèi)的run()覆蓋了父類(lèi)的run(),在代碼運(yùn)行的時(shí)候,總是會(huì)調(diào)用子類(lèi)的run()。這樣,我們就獲得了繼承的另一個(gè)好處:多態(tài)。
多態(tài)
要理解多態(tài)的好處,我們還需要再編寫(xiě)一個(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)Animal類(lèi)型的變量:
def run_twice(animal):
animal.run()
animal.run()
當(dāng)我們傳入Animal的實(shí)例時(shí),run_twice()就打印出:
run_twice(Animal())
運(yùn)行結(jié)果:
Animal is running...
Animal is running...
當(dāng)我們傳入Dog的實(shí)例時(shí),run_twice()就打印出:
run_twice(Dog())
運(yùn)行結(jié)果:
Dog is running...
Dog is running...
當(dāng)我們傳入Cat的實(shí)例時(shí),run_twice()就打印出:
run_twice(Cat())
運(yùn)行結(jié)果:
Cat is running...
Cat is running...
看上去沒(méi)啥意思,但是仔細(xì)想想,現(xiàn)在,如果我們?cè)俣x一個(gè)Tortoise類(lèi)型,也從Animal派生:
class Tortoise(Animal):
def run(self):
print 'Tortoise is running slowly...'
當(dāng)我們調(diào)用run_twice()時(shí),傳入Tortoise的實(shí)例:
run_twice(Tortoise())
運(yùn)行結(jié)果:
Tortoise is running slowly...
Tortoise is running slowly...
你會(huì)發(fā)現(xiàn),新增一個(gè)Animal的子類(lèi),不必對(duì)run_twice()做任何修改,實(shí)際上,任何依賴(lài)Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運(yùn)行,原因就在于多態(tài)。
多態(tài)的好處就是,當(dāng)我們需要傳入Dog、Cat、Tortoise……時(shí),我們只需要接收Animal類(lèi)型就可以了,因?yàn)镈og、Cat、Tortoise……都是Animal類(lèi)型,然后,按照Animal類(lèi)型進(jìn)行操作即可。由于Animal類(lèi)型有run()方法,因此,傳入的任意類(lèi)型,只要是Animal類(lèi)或者子類(lèi),就會(huì)自動(dòng)調(diào)用實(shí)際類(lèi)型的run()方法,這就是多態(tài)的意思:
對(duì)于一個(gè)變量,我們只需要知道它是Animal類(lèi)型,無(wú)需確切地知道它的子類(lèi)型,就可以放心地調(diào)用run()方法,而具體調(diào)用的run()方法是作用在Animal、Dog、Cat還是Tortoise對(duì)象上,由運(yùn)行時(shí)該對(duì)象的確切類(lèi)型決定,這就是多態(tài)真正的威力:調(diào)用方只管調(diào)用,不管細(xì)節(jié),而當(dāng)我們新增一種Animal的子類(lèi)時(shí),只要確保run()方法編寫(xiě)正確,不用管原來(lái)的代碼是如何調(diào)用的。這就是著名的“開(kāi)閉”原則:
對(duì)擴(kuò)展開(kāi)放:允許新增Animal子類(lèi);
對(duì)修改封閉:不需要修改依賴(lài)Animal類(lèi)型的run_twice()等函數(shù)。
總結(jié):繼承可以把父類(lèi)的所有功能都直接拿過(guò)來(lái),這樣就不必重零做起,子類(lèi)只需要新增自己特有的方法,也可以把父類(lèi)不適合的方法覆蓋重寫(xiě);
有了繼承,才能有多態(tài)。在調(diào)用類(lèi)實(shí)例方法的時(shí)候,盡量把變量視作父類(lèi)類(lèi)型,這樣,所有子類(lèi)類(lèi)型都可以正常被接收;
舊的方式定義Python類(lèi)允許不從object類(lèi)繼承,但這種編程方式已經(jīng)嚴(yán)重不推薦使用。任何時(shí)候,如果沒(méi)有合適的類(lèi)可以繼承,就繼承自object類(lèi)。
魔法方法
在上面有提到除了init之外還有iter,reverse的方法,這里就詳細(xì)說(shuō)下除了init初始化還有哪些別的方法。
__init__ : 構(gòu)造函數(shù),在生成對(duì)象時(shí)調(diào)用
__del__ : 析構(gòu)函數(shù),釋放對(duì)象時(shí)使用
__repr__ : 打印,轉(zhuǎn)換
__setitem__ : 按照索引賦值
__getitem__: 按照索引獲取值
__len__: 獲得長(zhǎng)度
__cmp__: 比較運(yùn)算
__call__: 調(diào)用
__add__: 加運(yùn)算
__sub__: 減運(yùn)算
__mul__: 乘運(yùn)算
__div__: 除運(yùn)算
__mod__: 求余運(yùn)算
__pow__: 冪
具體使用
1. doc
說(shuō)明性文檔和信息。Python自建,無(wú)需自定義。
class Foo:
""" 描述類(lèi)信息,可被自動(dòng)收集 """
def func(self):
pass
# 打印類(lèi)的說(shuō)明文檔
print(Foo.__doc__)
2. init()
實(shí)例化方法,通過(guò)類(lèi)創(chuàng)建實(shí)例時(shí),自動(dòng)觸發(fā)執(zhí)行。
class Foo:
def __init__(self, name):
self.name = name
self.age = 18
obj = Foo(jack') # 自動(dòng)執(zhí)行類(lèi)中的 __init__ 方法
3. module__ 和 __class
module 表示當(dāng)前操作的對(duì)象在屬于哪個(gè)模塊。
class 表示當(dāng)前操作的對(duì)象屬于哪個(gè)類(lèi)。
這兩者也是Python內(nèi)建,無(wú)需自定義。
class Foo:
pass
obj = Foo()
print(obj.__module__)
print(obj.__class__)
運(yùn)行結(jié)果:
main
4. del()
析構(gòu)方法,當(dāng)對(duì)象在內(nèi)存中被釋放時(shí),自動(dòng)觸發(fā)此方法。
注:此方法一般無(wú)須自定義,因?yàn)镻ython自帶內(nèi)存分配和釋放機(jī)制,除非你需要在釋放的時(shí)候指定做一些動(dòng)作。析構(gòu)函數(shù)的調(diào)用是由解釋器在進(jìn)行垃圾回收時(shí)自動(dòng)觸發(fā)執(zhí)行的。
class Foo:
def __del__(self):
print("我被回收了!")
obj = Foo()
del obj
5. call()
如果為一個(gè)類(lèi)編寫(xiě)了該方法,那么在該類(lèi)的實(shí)例后面加括號(hào),可會(huì)調(diào)用這個(gè)方法。
注:構(gòu)造方法的執(zhí)行是由類(lèi)加括號(hào)執(zhí)行的,即:對(duì)象 = 類(lèi)名(),而對(duì)于call() 方法,是由對(duì)象后加括號(hào)觸發(fā)的,即:對(duì)象() 或者 類(lèi)()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 執(zhí)行 __init__
obj() # 執(zhí)行 __call__
可以用Python內(nèi)建的callable()函數(shù)進(jìn)行測(cè)試,判斷一個(gè)對(duì)象是否可以被執(zhí)行。
callable(Student())
運(yùn)行結(jié)果:
True
6. dict
列出類(lèi)或?qū)ο笾械乃谐蓡T!非常重要和有用的一個(gè)屬性,Python自建,無(wú)需用戶自己定義。
class Province:
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 獲取類(lèi)的成員
print(Province.__dict__)
# 獲取 對(duì)象obj1 的成員
obj1 = Province('HeBei',10000)
print(obj1.__dict__)
# 獲取 對(duì)象obj2 的成員
obj2 = Province('HeNan', 3888)
print(obj2.__dict__)
7. str()
如果一個(gè)類(lèi)中定義了str()方法,那么在打印對(duì)象時(shí),默認(rèn)輸出該方法的返回值。這也是一個(gè)非常重要的方法,需要用戶自己定義。
下面的類(lèi),沒(méi)有定義str()方法,打印結(jié)果是:
class Foo:
pass
obj = Foo()
print(obj)
定義了__str__()方法后,打印結(jié)果是:'jack'。
class Foo:
def __str__(self):
return 'jack'
obj = Foo()
print(obj)
8、getitem__()、_setitem_()、__delitem()
取值、賦值、刪除這“三劍客”的套路,在Python中,我們已經(jīng)見(jiàn)過(guò)很多次了,比如前面的@property裝飾器。
Python中,標(biāo)識(shí)符后面加圓括號(hào),通常代表執(zhí)行或調(diào)用方法的意思。而在標(biāo)識(shí)符后面加中括號(hào)[],通常代表取值的意思。Python設(shè)計(jì)了getitem()、setitem()、delitem()這三個(gè)特殊成員,用于執(zhí)行與中括號(hào)有關(guān)的動(dòng)作。它們分別表示取值、賦值、刪除數(shù)據(jù)。
也就是如下的操作:
a = 標(biāo)識(shí)符[] : 執(zhí)行__getitem__方法
標(biāo)識(shí)符[] = a : 執(zhí)行__setitem__方法
del 標(biāo)識(shí)符[] : 執(zhí)行__delitem__方法
如果有一個(gè)類(lèi)同時(shí)定義了這三個(gè)魔法方法,那么這個(gè)類(lèi)的實(shí)例的行為看起來(lái)就像一個(gè)字典一樣,如下例所示:
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'] # 自動(dòng)觸發(fā)執(zhí)行 __getitem__
obj['k2'] = 'jack' # 自動(dòng)觸發(fā)執(zhí)行 __setitem__
del obj['k1'] # 自動(dòng)觸發(fā)執(zhí)行 __delitem__
9. iter()
這是迭代器方法!列表、字典、元組之所以可以進(jìn)行for循環(huán),是因?yàn)槠鋬?nèi)部定義了 iter()這個(gè)方法。如果用戶想讓自定義的類(lèi)的對(duì)象可以被迭代,那么就需要在類(lèi)中定義這個(gè)方法,并且讓該方法的返回值是一個(gè)可迭代的對(duì)象。當(dāng)在代碼中利用for循環(huán)遍歷對(duì)象時(shí),就會(huì)調(diào)用類(lèi)的這個(gè)iter()方法。
普通的類(lèi):
class Foo:
pass
obj = Foo()
for i in obj:
print(i)
# 報(bào)錯(cuò):TypeError: 'Foo' object is not iterable<br># 原因是Foo對(duì)象不可迭代
添加一個(gè)__iter__(),但什么都不返回:
class Foo:
def __iter__(self):
pass
obj = Foo()
for i in obj:
print(i)
# 報(bào)錯(cuò):TypeError: iter() returned non-iterator of type 'NoneType'
#原因是 __iter__方法沒(méi)有返回一個(gè)可迭代的對(duì)象
返回一個(gè)個(gè)迭代對(duì)象:
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 3
obj = Foo()
for i in obj:
print(i)
10、len()
在Python中,如果你調(diào)用內(nèi)置的len()函數(shù)試圖獲取一個(gè)對(duì)象的長(zhǎng)度,在后臺(tái),其實(shí)是去調(diào)用該對(duì)象的len()方法,所以,下面的代碼是等價(jià)的:
len('ABC')
3
'ABC'.__len__()
3
Python的list、dict、str等內(nèi)置數(shù)據(jù)類(lèi)型都實(shí)現(xiàn)了該方法,但是你自定義的類(lèi)要實(shí)現(xiàn)len方法需要好好設(shè)計(jì)。
11. repr()
這個(gè)方法的作用和str()很像,兩者的區(qū)別是str()返回用戶看到的字符串,而repr()返回程序開(kāi)發(fā)者看到的字符串,也就是說(shuō),repr()是為調(diào)試服務(wù)的。通常兩者代碼一樣。
class Foo:
def __init__(self, name):
self.name = name
def __str__(self):
return "this is %s" % self.name
__repr__ = __str__
12. add__: 加運(yùn)算 _sub_: 減運(yùn)算 _mul_: 乘運(yùn)算 _div_: 除運(yùn)算 _mod_: 求余運(yùn)算 __pow: 冪運(yùn)算
這些都是算術(shù)運(yùn)算方法,需要你自己為類(lèi)設(shè)計(jì)具體運(yùn)算代碼。有些Python內(nèi)置數(shù)據(jù)類(lèi)型,比如int就帶有這些方法。Python支持運(yùn)算符的重載,也就是重寫(xiě)。
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()
14. slots
Python作為一種動(dòng)態(tài)語(yǔ)言,可以在類(lèi)定義完成和實(shí)例化后,給類(lèi)或者對(duì)象繼續(xù)添加隨意個(gè)數(shù)或者任意類(lèi)型的變量或方法,這是動(dòng)態(tài)語(yǔ)言的特性。例如:
def print_doc(self):
print("haha")
class Foo:
pass
obj1 = Foo()
obj2 = Foo()
# 動(dòng)態(tài)添加實(shí)例變量
obj1.name = "jack"
obj2.age = 18
# 動(dòng)態(tài)的給類(lèi)添加實(shí)例方法
Foo.show = print_doc
obj1.show()
obj2.show()
但是!如果我想限制實(shí)例可以添加的變量怎么辦?可以使slots限制實(shí)例的變量,比如,只允許Foo的實(shí)例添加name和age屬性。
def print_doc(self):
print("haha")
class Foo:
__slots__ = ("name", "age")
pass
obj1 = Foo()
obj2 = Foo()
# 動(dòng)態(tài)添加實(shí)例變量
obj1.name = "jack"
obj2.age = 18
obj1.sex = "male" # 這一句會(huì)彈出錯(cuò)誤
# 但是無(wú)法限制給類(lèi)添加方法
Foo.show = print_doc
obj1.show()
obj2.show()
由于'sex'不在__slots__的列表中,所以不能綁定sex屬性,試圖綁定sex將得到AttributeError的錯(cuò)誤。
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定義的屬性?xún)H對(duì)當(dāng)前類(lèi)的實(shí)例起作用,對(duì)繼承了它的子類(lèi)是不起作用的。想想也是這個(gè)道理,如果你繼承一個(gè)父類(lèi),卻莫名其妙發(fā)現(xiàn)有些變量無(wú)法定義,那不是大問(wèn)題么?如果非要子類(lèi)也被限制,除非在子類(lèi)中也定義slots,這樣,子類(lèi)實(shí)例允許定義的屬性就是自身的slots加上父類(lèi)的slots。
成員保護(hù)與訪問(wèn)機(jī)制
有些對(duì)象你不想外部訪問(wèn),即使是通過(guò)調(diào)用類(lèi)對(duì)象也無(wú)法訪問(wèn),那就請(qǐng)認(rèn)真學(xué)完本章節(jié)。
私有成員
class obj:
def __init__(self,name):
self.name=name
def pri(self):
print self.name
__age = 18
# 加上雙下劃線的就是私有變量,只能在類(lèi)的內(nèi)部訪問(wèn),外部無(wú)法訪問(wèn)
a = obj('zhao')
a.pri()
運(yùn)行結(jié)果:
zhao
如果要在類(lèi)中調(diào)用這個(gè)私有成員,可以這么用
class obj:
def __init__(self,name):
self.name=name
def prin(self):
print self.name
__age = 18
# 加上雙下劃線的就是私有變量,只能在類(lèi)的內(nèi)部訪問(wèn),外部無(wú)法訪問(wèn)
@classmethod
# 如果要在類(lèi)中調(diào)用,首先調(diào)用類(lèi)方法
def pri(cls):
print cls.__age
# 然后在使用
a = obj('zhao')
a.prin()
obj.pri()
# 通過(guò)這樣直接調(diào)用類(lèi)中的私有變量
運(yùn)行結(jié)果:
zhao
18
使用get-set-del方法操作私有成員
class obj:
def __init__(self,name):
self.name=name
def prin(self):
print self.name
__age = 18
# 加上雙下劃線的就是私有變量,只能在類(lèi)的內(nèi)部訪問(wèn),外部無(wú)法訪問(wèn)
@classmethod
# 如果要在類(lèi)中調(diào)用,首先調(diào)用類(lèi)方法
def pri(cls):
print cls.__age
# 然后在使用
@classmethod
def set_age(cls,value):
cls.__age = value
return cls.__age
# 這個(gè)用法就是改變__age的值
@classmethod
def get_age(cls):
return cls.__age
# 這個(gè)用法就是直接返回__age的值
@classmethod
def del_age(cls):
del cls.__age
# 這個(gè)用法就是直接刪除__age的值
print obj.get_age()
# 這里是直接調(diào)用出__age的值 返回值18
print obj.set_age(20)
# 這里是直接改變__age的值 返回值20
obj.del_age()
# 這里是直接刪除__age的值
思考: 既然是私有變量,不讓外部訪問(wèn),為何有要在后面調(diào)用又改變呢?因?yàn)榭梢詫?duì)私有變量進(jìn)行額外的檢測(cè),處理,加工等等。比如判斷value的值,使用isinstance然后做if-else判斷。
使用私有變量可以對(duì)內(nèi)部變量進(jìn)行保護(hù),外部無(wú)法改變,但是可以對(duì)它進(jìn)行檢測(cè)處理。
這里引申一下私有成員的保護(hù)機(jī)制,使用__age對(duì)私有變量其實(shí)就是—>obj._obj__age的樣子進(jìn)行保護(hù),說(shuō)白了你直接使用obj._obj__age就可以直接調(diào)用內(nèi)部私有變量age了。
Propety裝飾器
把類(lèi)的方法偽裝成屬性調(diào)用的方式,就是把類(lèi)里面的一個(gè)函數(shù),變成一個(gè)屬性一樣的東西~
一開(kāi)始調(diào)用類(lèi)的方法要使用圓括號(hào),現(xiàn)在變成了屬性進(jìn)行讀取設(shè)置存儲(chǔ)。
舉個(gè)例子來(lái)說(shuō)明:
常用的調(diào)用方法
class obj:
def __init__(self,name,age):
self.__name=name
self.__age=age
# 講這些設(shè)置成私有變量
def get_age(self):
return self.__age
def set_age(self,value):
if isinstance(value,int):
self.__age=value
else:
raise ValueError('非整數(shù)類(lèi)型')
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
# 把這些設(shè)置成私有變量
@property
def age(self):
return self.__age
@age.setter
def age(self,value):
if isinstance(value,int):
self.__age=value
else:
raise ValueError('非整數(shù)類(lèi)型')
@age.deleter
def age(self):
print 'delete over'
a = obj('langzi',18)
# 使用這些裝飾器,可以使用類(lèi)與對(duì)象的方法直接調(diào)用
print a.age
# 這里就是直接調(diào)用返回age的值
a.age=20
# 這里就是直接使用setter把值轉(zhuǎn)換
print a.age
del a.age
# 刪除age
當(dāng)然這種調(diào)用方法有些麻煩,每次都是一個(gè)一個(gè)去實(shí)例類(lèi)與對(duì)象,有個(gè)更加簡(jiǎn)單直觀的方法。
更加減半的使用property()函數(shù)
除了使用裝飾器的方式將一個(gè)方法偽裝成屬性外,Python內(nèi)置的builtins模塊中的property()函數(shù),為我們提供了第二種設(shè)置類(lèi)屬性的手段。
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("刪除年齡數(shù)據(jù)!")
# 核心在這句
age = property(get_age, set_age, del_age, "年齡")
obj = People("jack", 18)
print(obj.age)
obj.age = 19
print("obj.age: ", obj.age)
del obj.ag
通過(guò)語(yǔ)句age = property(get_age, set_age, del_age, “年齡”)將一個(gè)方法偽裝成為屬性。其效果和裝飾器的方法是一樣的。
property()函數(shù)的參數(shù):
第一個(gè)參數(shù)是方法名,調(diào)用 實(shí)例.屬性 時(shí)自動(dòng)執(zhí)行的方法
第二個(gè)參數(shù)是方法名,調(diào)用 實(shí)例.屬性 = XXX時(shí)自動(dòng)執(zhí)行的方法
第三個(gè)參數(shù)是方法名,調(diào)用 del 實(shí)例.屬性 時(shí)自動(dòng)執(zhí)行的方法
第四個(gè)參數(shù)是字符串,調(diào)用 實(shí)例.屬性.__doc__時(shí)的描述信息。
相關(guān)新聞
轉(zhuǎn)自:浪子燕青
http://www.langzi.fun/Python面向?qū)ο缶幊?html
面向?qū)ο缶幊毯秃瘮?shù)式編程(面向過(guò)程編程)都是程序設(shè)計(jì)的方法,不過(guò)稍有區(qū)別。
面向過(guò)程編程:
1. 導(dǎo)入各種外部庫(kù)
2. 設(shè)計(jì)各種全局變量
3. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
4. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
5. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
6. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
7. 寫(xiě)一個(gè)函數(shù)完成某個(gè)功能
8. ......
9. 寫(xiě)一個(gè)main函數(shù)作為程序入口
在多函數(shù)程序中,許多重要的數(shù)據(jù)被放置在全局?jǐn)?shù)據(jù)區(qū),這樣它們可以被所有的函數(shù)訪問(wèn)。每個(gè)函數(shù)都可以具有它們自己的局部數(shù)據(jù),將某些功能代碼封裝到函數(shù)中,日后便無(wú)需重復(fù)編寫(xiě),僅調(diào)用函數(shù)即可。從代碼的組織形式來(lái)看就是根據(jù)業(yè)務(wù)邏輯從上到下壘代碼 。
面向?qū)ο缶幊蹋?/strong>
1. 導(dǎo)入各種外部庫(kù)
2. 設(shè)計(jì)各種全局變量
3. 決定你要的類(lèi)
4. 給每個(gè)類(lèi)提供完整的一組操作
5. 明確地使用繼承來(lái)表現(xiàn)不同類(lèi)之間的共同點(diǎn)
6. 根據(jù)需要,決定是否寫(xiě)一個(gè)main函數(shù)作為程序入口
面向?qū)ο缶幊讨校瑢⒑瘮?shù)和變量進(jìn)一步封裝成類(lèi),類(lèi)才是程序的基本元素,它將數(shù)據(jù)和操作緊密地連結(jié)在一起,并保護(hù)數(shù)據(jù)不會(huì)被外界的函數(shù)意外地改變。類(lèi)和和類(lèi)的實(shí)例(也稱(chēng)對(duì)象)是面向?qū)ο蟮暮诵母拍睿呛兔嫦蜻^(guò)程編程、函數(shù)式編程的根本區(qū)別。
并不是非要用面向?qū)ο缶幊蹋茨愕某绦蛟趺丛O(shè)計(jì)方便,但是就目前來(lái)說(shuō),基本上都是在使用面向?qū)ο缶幊獭?/p>
類(lèi)的基本用法
面向?qū)ο笫峭ㄟ^(guò)定義class類(lèi)來(lái)定義,這么說(shuō)面向?qū)ο缶幊叹褪侵皇褂胏lass類(lèi),在class類(lèi)中有封裝,繼承的功能,并且還可以構(gòu)造要傳入的參數(shù),方便控制。
案例一
import sys
import time
reload(sys)
sys.setdefaultencoding('utf-8')
class studetn:
# 定義一個(gè)類(lèi)名為studetn
def __init__(self,idx):
# 定義初始化構(gòu)造,這里使用init,還有別的屬性比如reversed,iter之類(lèi)的
self.idx=idx
# 初始化變量,方便繼承
def runx(self):
# 定義運(yùn)行函數(shù),從上面繼承變量
print self.idx
# 打印出idx的值,或者做一些別的處理
time.sleep(1)
a=studetn('a')
a.runx()
# 這是類(lèi)的調(diào)用,一定要記得類(lèi)的使用方法,首先傳入?yún)?shù),類(lèi)賦值給一個(gè)變量a
# 然后調(diào)用這個(gè)類(lèi)下面定義的函數(shù)
一些專(zhuān)業(yè)術(shù)語(yǔ)概念,既然有面向?qū)ο缶幊踢@個(gè)高大上的定義了,自然要搭配一些高大上的概念。
- 類(lèi)(Class): 用來(lái)描述具有相同屬性和方法的對(duì)象的集合。它定義了該集合中每個(gè)對(duì)象所共有的屬性和方法。其中的對(duì)象被稱(chēng)作類(lèi)的實(shí)例。
- 實(shí)例:也稱(chēng)對(duì)象。通過(guò)類(lèi)定義的初始化方法,賦予具體的值,成為一個(gè)”有血有肉的實(shí)體”。
- 實(shí)例化:創(chuàng)建類(lèi)的實(shí)例的過(guò)程或操作。
- 實(shí)例變量:定義在實(shí)例中的變量,只作用于當(dāng)前實(shí)例。
- 類(lèi)變量:類(lèi)變量是所有實(shí)例公有的變量。類(lèi)變量定義在類(lèi)中,但在方法體之外。
- 數(shù)據(jù)成員:類(lèi)變量、實(shí)例變量、方法、類(lèi)方法、靜態(tài)方法和屬性等的統(tǒng)稱(chēng)。
- 方法:類(lèi)中定義的函數(shù)。
- 靜態(tài)方法:不需要實(shí)例化就可以由類(lèi)執(zhí)行的方法
- 類(lèi)方法:類(lèi)方法是將類(lèi)本身作為對(duì)象進(jìn)行操作的方法。
- 方法重寫(xiě):如果從父類(lèi)繼承的方法不能滿足子類(lèi)的需求,可以對(duì)父類(lèi)的方法進(jìn)行改寫(xiě),這個(gè)過(guò)程也稱(chēng)override。
- 封裝:將內(nèi)部實(shí)現(xiàn)包裹起來(lái),對(duì)外透明,提供api接口進(jìn)行調(diào)用的機(jī)制
- 繼承:即一個(gè)派生類(lèi)(derived class)繼承父類(lèi)(base class)的變量和方法。
- 多態(tài):根據(jù)對(duì)象類(lèi)型的不同以不同的方式進(jìn)行處理。
類(lèi)與實(shí)例
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:02
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import time
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class cc:
ccc = 'ccc'
# cc就是類(lèi)名 如果想要繼承別的類(lèi) 就class cc(threading) 意思就是從threading繼承
def __init__(self,a,b,c):
self.a=a
self.b=b
self.c=c
# 定義構(gòu)造的過(guò)程就是實(shí)例化
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').headers
e = cc('AAA','CCC','EEE')
e.runx()
e.runy()
# 這兩個(gè)就是調(diào)用類(lèi)里面的方法
print e.c
#實(shí)例變量指的是實(shí)例本身?yè)碛械淖兞俊C總€(gè)實(shí)例的變量在內(nèi)存中都不一樣。
print e.ccc
#類(lèi)變量,在類(lèi)里面找到定義的變量。
調(diào)用類(lèi)的三種方法
實(shí)例方法
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:16
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import time
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class dd:
def __init__(self,url):
self.url=url
def runx(self):
print requests.get(self.url).status_code
a = dd('http://www.langzi.fun')
a.runx()
# 這種調(diào)用方法就是實(shí)例方法
靜態(tài)方法
靜態(tài)方法由類(lèi)調(diào)用,無(wú)默認(rèn)參數(shù)。將實(shí)例方法參數(shù)中的self去掉,然后在方法定義上方加上@staticmethod,就成為靜態(tài)方法。它屬于類(lèi),和實(shí)例無(wú)關(guān)。建議只使用類(lèi)名.靜態(tài)方法的調(diào)用方式。(雖然也可以使用實(shí)例名.靜態(tài)方法的方式調(diào)用)
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:21
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class ff:
@staticmethod
def runx():
print requests.get('http://www.langzi.fun').status_code
ff.runx()
#這里就直接調(diào)用了類(lèi)的變量,只在類(lèi)中運(yùn)行而不在實(shí)例中運(yùn)行的方法
經(jīng)常有一些跟類(lèi)有關(guān)系的功能但在運(yùn)行時(shí)又不需要實(shí)例和類(lèi)參與的情況下需要用到靜態(tài)方法. 比如更改環(huán)境變量或者修改其他類(lèi)的屬性等能用到靜態(tài)方法. 這種情況可以直接用函數(shù)解決, 但這樣同樣會(huì)擴(kuò)散類(lèi)內(nèi)部的代碼,造成維護(hù)困難。
類(lèi)方法
類(lèi)方法由類(lèi)調(diào)用,采用@classmethod裝飾,至少傳入一個(gè)cls(代指類(lèi)本身,類(lèi)似self)參數(shù)。執(zhí)行類(lèi)方法時(shí),自動(dòng)將調(diào)用該方法的類(lèi)賦值給cls。建議只使用類(lèi)名.類(lèi)方法的調(diào)用方式。(雖然也可以使用實(shí)例名.類(lèi)方法的方式調(diào)用)
實(shí)際案例
如果要構(gòu)造一個(gè)類(lèi),接受一個(gè)網(wǎng)站和這個(gè)網(wǎng)站的狀態(tài)碼,然后打印出來(lái)。就像這樣:
import sys
import requests
reload(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.stat
a = gg('langzi',200)
a.outer()
這樣就是使用實(shí)例方法,雖然可以實(shí)現(xiàn),但是有的時(shí)候傳入的參數(shù)并不是(‘langzi’,200)這樣的格式,而是(‘langzi-200’)這樣的,那該怎么做?首先要把這個(gè)拆分,但是要使用實(shí)例方法實(shí)現(xiàn)起來(lái)很麻煩,這個(gè)時(shí)候就可以使用類(lèi)方法。
# -*- coding: utf-8 -*-
# @Time : 2018/5/3 0003 17:27
# @Author : Langzi
# @Blog : www.langzi.fun
# @File : 面向?qū)ο?.py
# @Software: PyCharm
import sys
import requests
reload(sys)
sys.setdefaultencoding('utf-8')
class gg:
url = 0
stat = 0
# 因?yàn)槭褂胏lassmethod后會(huì)傳入新的變量,所以一開(kāi)始是需要自己先定義類(lèi)變量
def __init__(self,url=0,stat=0):
# 這里按照正常的定義構(gòu)造函數(shù)
self.url=url
self.stat=stat
@classmethod
# 裝飾器,立馬執(zhí)行下面的函數(shù)
def split(cls,info):
# 這個(gè)函數(shù)接受兩個(gè)參數(shù),默認(rèn)的cls就是這個(gè)類(lèi)的init函數(shù),info就是外面?zhèn)魅脒M(jìn)來(lái)的
url,stat=map(str,info.split('-'))
# 這里轉(zhuǎn)換成了格式化的結(jié)構(gòu)
data = cls(url,stat)
# 然后執(zhí)行這個(gè)類(lèi)第一個(gè)方法,這個(gè)類(lèi)構(gòu)造函數(shù)需要傳入兩個(gè)參數(shù),于是就傳入了兩個(gè)參數(shù)
return data
# 這里就直接返回了函數(shù)結(jié)果
def outer(self):
print self.url
print self.stat
r = gg.split(('langzi-200'))
r.outer()
# 這里是調(diào)用類(lèi)方法,與調(diào)用實(shí)例方法一樣
類(lèi)的特性
封裝
封裝是指將數(shù)據(jù)與具體操作的實(shí)現(xiàn)代碼放在某個(gè)對(duì)象內(nèi)部,外部無(wú)法訪問(wèn)。必須要先調(diào)用類(lèi)的方法才能啟動(dòng)。
案例
class cc:
ccc = 'ccc'
# cc就是類(lèi)名 如果想要繼承別的類(lèi) 就class cc(threading) 意思就是從threading繼承
def __init__(self,a,b,c):
self.a=a
self.b=b
self.c=c
print e.ccc
#類(lèi)變量,在類(lèi)里面找到定義的變量。
print ccc
# 這里會(huì)報(bào)錯(cuò),這就是封裝。類(lèi)中的函數(shù)同理。
繼承
當(dāng)我們定義一個(gè)class的時(shí)候,可以從某個(gè)現(xiàn)有的class繼承,新的class稱(chēng)為子類(lèi)(Subclass),而被繼承的class稱(chēng)為基類(lèi)、父類(lèi)或超類(lèi)(Base class、Super class)。
比如,我們已經(jīng)編寫(xiě)了一個(gè)名為Animal的class,有一個(gè)run()方法可以直接打印:
class Animal(object):
def run(self):
print 'Animal is running...'
當(dāng)我們需要編寫(xiě)Dog和Cat類(lèi)時(shí),就可以直接從Animal類(lèi)繼承:
class Dog(Animal):
pass
class Cat(Animal):
pass
繼承有什么好處?最大的好處是子類(lèi)獲得了父類(lèi)的全部功能。由于Animial實(shí)現(xiàn)了run()方法,因此,Dog和Cat作為它的子類(lèi),什么事也沒(méi)干,就自動(dòng)擁有了run()方法:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
當(dāng)子類(lèi)和父類(lèi)都存在相同的run()方法時(shí),我們說(shuō),子類(lèi)的run()覆蓋了父類(lèi)的run(),在代碼運(yùn)行的時(shí)候,總是會(huì)調(diào)用子類(lèi)的run()。這樣,我們就獲得了繼承的另一個(gè)好處:多態(tài)。
多態(tài)
要理解多態(tài)的好處,我們還需要再編寫(xiě)一個(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)Animal類(lèi)型的變量:
def run_twice(animal):
animal.run()
animal.run()
當(dāng)我們傳入Animal的實(shí)例時(shí),run_twice()就打印出:
run_twice(Animal())
運(yùn)行結(jié)果:
Animal is running...
Animal is running...
當(dāng)我們傳入Dog的實(shí)例時(shí),run_twice()就打印出:
run_twice(Dog())
運(yùn)行結(jié)果:
Dog is running...
Dog is running...
當(dāng)我們傳入Cat的實(shí)例時(shí),run_twice()就打印出:
run_twice(Cat())
運(yùn)行結(jié)果:
Cat is running...
Cat is running...
看上去沒(méi)啥意思,但是仔細(xì)想想,現(xiàn)在,如果我們?cè)俣x一個(gè)Tortoise類(lèi)型,也從Animal派生:
class Tortoise(Animal):
def run(self):
print 'Tortoise is running slowly...'
當(dāng)我們調(diào)用run_twice()時(shí),傳入Tortoise的實(shí)例:
run_twice(Tortoise())
運(yùn)行結(jié)果:
Tortoise is running slowly...
Tortoise is running slowly...
你會(huì)發(fā)現(xiàn),新增一個(gè)Animal的子類(lèi),不必對(duì)run_twice()做任何修改,實(shí)際上,任何依賴(lài)Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運(yùn)行,原因就在于多態(tài)。
多態(tài)的好處就是,當(dāng)我們需要傳入Dog、Cat、Tortoise……時(shí),我們只需要接收Animal類(lèi)型就可以了,因?yàn)镈og、Cat、Tortoise……都是Animal類(lèi)型,然后,按照Animal類(lèi)型進(jìn)行操作即可。由于Animal類(lèi)型有run()方法,因此,傳入的任意類(lèi)型,只要是Animal類(lèi)或者子類(lèi),就會(huì)自動(dòng)調(diào)用實(shí)際類(lèi)型的run()方法,這就是多態(tài)的意思:
對(duì)于一個(gè)變量,我們只需要知道它是Animal類(lèi)型,無(wú)需確切地知道它的子類(lèi)型,就可以放心地調(diào)用run()方法,而具體調(diào)用的run()方法是作用在Animal、Dog、Cat還是Tortoise對(duì)象上,由運(yùn)行時(shí)該對(duì)象的確切類(lèi)型決定,這就是多態(tài)真正的威力:調(diào)用方只管調(diào)用,不管細(xì)節(jié),而當(dāng)我們新增一種Animal的子類(lèi)時(shí),只要確保run()方法編寫(xiě)正確,不用管原來(lái)的代碼是如何調(diào)用的。這就是著名的“開(kāi)閉”原則:
對(duì)擴(kuò)展開(kāi)放:允許新增Animal子類(lèi);
對(duì)修改封閉:不需要修改依賴(lài)Animal類(lèi)型的run_twice()等函數(shù)。
總結(jié):繼承可以把父類(lèi)的所有功能都直接拿過(guò)來(lái),這樣就不必重零做起,子類(lèi)只需要新增自己特有的方法,也可以把父類(lèi)不適合的方法覆蓋重寫(xiě);
有了繼承,才能有多態(tài)。在調(diào)用類(lèi)實(shí)例方法的時(shí)候,盡量把變量視作父類(lèi)類(lèi)型,這樣,所有子類(lèi)類(lèi)型都可以正常被接收;
舊的方式定義Python類(lèi)允許不從object類(lèi)繼承,但這種編程方式已經(jīng)嚴(yán)重不推薦使用。任何時(shí)候,如果沒(méi)有合適的類(lèi)可以繼承,就繼承自object類(lèi)。
魔法方法
在上面有提到除了init之外還有iter,reverse的方法,這里就詳細(xì)說(shuō)下除了init初始化還有哪些別的方法。
__init__ : 構(gòu)造函數(shù),在生成對(duì)象時(shí)調(diào)用
__del__ : 析構(gòu)函數(shù),釋放對(duì)象時(shí)使用
__repr__ : 打印,轉(zhuǎn)換
__setitem__ : 按照索引賦值
__getitem__: 按照索引獲取值
__len__: 獲得長(zhǎng)度
__cmp__: 比較運(yùn)算
__call__: 調(diào)用
__add__: 加運(yùn)算
__sub__: 減運(yùn)算
__mul__: 乘運(yùn)算
__div__: 除運(yùn)算
__mod__: 求余運(yùn)算
__pow__: 冪
具體使用
1. doc
說(shuō)明性文檔和信息。Python自建,無(wú)需自定義。
class Foo:
""" 描述類(lèi)信息,可被自動(dòng)收集 """
def func(self):
pass
# 打印類(lèi)的說(shuō)明文檔
print(Foo.__doc__)
2. init()
實(shí)例化方法,通過(guò)類(lèi)創(chuàng)建實(shí)例時(shí),自動(dòng)觸發(fā)執(zhí)行。
class Foo:
def __init__(self, name):
self.name = name
self.age = 18
obj = Foo(jack') # 自動(dòng)執(zhí)行類(lèi)中的 __init__ 方法
3. module__ 和 __class
module 表示當(dāng)前操作的對(duì)象在屬于哪個(gè)模塊。
class 表示當(dāng)前操作的對(duì)象屬于哪個(gè)類(lèi)。
這兩者也是Python內(nèi)建,無(wú)需自定義。
class Foo:
pass
obj = Foo()
print(obj.__module__)
print(obj.__class__)
運(yùn)行結(jié)果:
main
4. del()
析構(gòu)方法,當(dāng)對(duì)象在內(nèi)存中被釋放時(shí),自動(dòng)觸發(fā)此方法。
注:此方法一般無(wú)須自定義,因?yàn)镻ython自帶內(nèi)存分配和釋放機(jī)制,除非你需要在釋放的時(shí)候指定做一些動(dòng)作。析構(gòu)函數(shù)的調(diào)用是由解釋器在進(jìn)行垃圾回收時(shí)自動(dòng)觸發(fā)執(zhí)行的。
class Foo:
def __del__(self):
print("我被回收了!")
obj = Foo()
del obj
5. call()
如果為一個(gè)類(lèi)編寫(xiě)了該方法,那么在該類(lèi)的實(shí)例后面加括號(hào),可會(huì)調(diào)用這個(gè)方法。
注:構(gòu)造方法的執(zhí)行是由類(lèi)加括號(hào)執(zhí)行的,即:對(duì)象 = 類(lèi)名(),而對(duì)于call() 方法,是由對(duì)象后加括號(hào)觸發(fā)的,即:對(duì)象() 或者 類(lèi)()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 執(zhí)行 __init__
obj() # 執(zhí)行 __call__
可以用Python內(nèi)建的callable()函數(shù)進(jìn)行測(cè)試,判斷一個(gè)對(duì)象是否可以被執(zhí)行。
callable(Student())
運(yùn)行結(jié)果:
True
6. dict
列出類(lèi)或?qū)ο笾械乃谐蓡T!非常重要和有用的一個(gè)屬性,Python自建,無(wú)需用戶自己定義。
class Province:
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 獲取類(lèi)的成員
print(Province.__dict__)
# 獲取 對(duì)象obj1 的成員
obj1 = Province('HeBei',10000)
print(obj1.__dict__)
# 獲取 對(duì)象obj2 的成員
obj2 = Province('HeNan', 3888)
print(obj2.__dict__)
7. str()
如果一個(gè)類(lèi)中定義了str()方法,那么在打印對(duì)象時(shí),默認(rèn)輸出該方法的返回值。這也是一個(gè)非常重要的方法,需要用戶自己定義。
下面的類(lèi),沒(méi)有定義str()方法,打印結(jié)果是:
class Foo:
pass
obj = Foo()
print(obj)
定義了__str__()方法后,打印結(jié)果是:'jack'。
class Foo:
def __str__(self):
return 'jack'
obj = Foo()
print(obj)
8、getitem__()、_setitem_()、__delitem()
取值、賦值、刪除這“三劍客”的套路,在Python中,我們已經(jīng)見(jiàn)過(guò)很多次了,比如前面的@property裝飾器。
Python中,標(biāo)識(shí)符后面加圓括號(hào),通常代表執(zhí)行或調(diào)用方法的意思。而在標(biāo)識(shí)符后面加中括號(hào)[],通常代表取值的意思。Python設(shè)計(jì)了getitem()、setitem()、delitem()這三個(gè)特殊成員,用于執(zhí)行與中括號(hào)有關(guān)的動(dòng)作。它們分別表示取值、賦值、刪除數(shù)據(jù)。
也就是如下的操作:
a = 標(biāo)識(shí)符[] : 執(zhí)行__getitem__方法
標(biāo)識(shí)符[] = a : 執(zhí)行__setitem__方法
del 標(biāo)識(shí)符[] : 執(zhí)行__delitem__方法
如果有一個(gè)類(lèi)同時(shí)定義了這三個(gè)魔法方法,那么這個(gè)類(lèi)的實(shí)例的行為看起來(lái)就像一個(gè)字典一樣,如下例所示:
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'] # 自動(dòng)觸發(fā)執(zhí)行 __getitem__
obj['k2'] = 'jack' # 自動(dòng)觸發(fā)執(zhí)行 __setitem__
del obj['k1'] # 自動(dòng)觸發(fā)執(zhí)行 __delitem__
9. iter()
這是迭代器方法!列表、字典、元組之所以可以進(jìn)行for循環(huán),是因?yàn)槠鋬?nèi)部定義了 iter()這個(gè)方法。如果用戶想讓自定義的類(lèi)的對(duì)象可以被迭代,那么就需要在類(lèi)中定義這個(gè)方法,并且讓該方法的返回值是一個(gè)可迭代的對(duì)象。當(dāng)在代碼中利用for循環(huán)遍歷對(duì)象時(shí),就會(huì)調(diào)用類(lèi)的這個(gè)iter()方法。
普通的類(lèi):
class Foo:
pass
obj = Foo()
for i in obj:
print(i)
# 報(bào)錯(cuò):TypeError: 'Foo' object is not iterable<br># 原因是Foo對(duì)象不可迭代
添加一個(gè)__iter__(),但什么都不返回:
class Foo:
def __iter__(self):
pass
obj = Foo()
for i in obj:
print(i)
# 報(bào)錯(cuò):TypeError: iter() returned non-iterator of type 'NoneType'
#原因是 __iter__方法沒(méi)有返回一個(gè)可迭代的對(duì)象
返回一個(gè)個(gè)迭代對(duì)象:
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 3
obj = Foo()
for i in obj:
print(i)
10、len()
在Python中,如果你調(diào)用內(nèi)置的len()函數(shù)試圖獲取一個(gè)對(duì)象的長(zhǎng)度,在后臺(tái),其實(shí)是去調(diào)用該對(duì)象的len()方法,所以,下面的代碼是等價(jià)的:
len('ABC')
3
'ABC'.__len__()
3
Python的list、dict、str等內(nèi)置數(shù)據(jù)類(lèi)型都實(shí)現(xiàn)了該方法,但是你自定義的類(lèi)要實(shí)現(xiàn)len方法需要好好設(shè)計(jì)。
11. repr()
這個(gè)方法的作用和str()很像,兩者的區(qū)別是str()返回用戶看到的字符串,而repr()返回程序開(kāi)發(fā)者看到的字符串,也就是說(shuō),repr()是為調(diào)試服務(wù)的。通常兩者代碼一樣。
class Foo:
def __init__(self, name):
self.name = name
def __str__(self):
return "this is %s" % self.name
__repr__ = __str__
12. add__: 加運(yùn)算 _sub_: 減運(yùn)算 _mul_: 乘運(yùn)算 _div_: 除運(yùn)算 _mod_: 求余運(yùn)算 __pow: 冪運(yùn)算
這些都是算術(shù)運(yùn)算方法,需要你自己為類(lèi)設(shè)計(jì)具體運(yùn)算代碼。有些Python內(nèi)置數(shù)據(jù)類(lèi)型,比如int就帶有這些方法。Python支持運(yùn)算符的重載,也就是重寫(xiě)。
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()
14. slots
Python作為一種動(dòng)態(tài)語(yǔ)言,可以在類(lèi)定義完成和實(shí)例化后,給類(lèi)或者對(duì)象繼續(xù)添加隨意個(gè)數(shù)或者任意類(lèi)型的變量或方法,這是動(dòng)態(tài)語(yǔ)言的特性。例如:
def print_doc(self):
print("haha")
class Foo:
pass
obj1 = Foo()
obj2 = Foo()
# 動(dòng)態(tài)添加實(shí)例變量
obj1.name = "jack"
obj2.age = 18
# 動(dòng)態(tài)的給類(lèi)添加實(shí)例方法
Foo.show = print_doc
obj1.show()
obj2.show()
但是!如果我想限制實(shí)例可以添加的變量怎么辦?可以使slots限制實(shí)例的變量,比如,只允許Foo的實(shí)例添加name和age屬性。
def print_doc(self):
print("haha")
class Foo:
__slots__ = ("name", "age")
pass
obj1 = Foo()
obj2 = Foo()
# 動(dòng)態(tài)添加實(shí)例變量
obj1.name = "jack"
obj2.age = 18
obj1.sex = "male" # 這一句會(huì)彈出錯(cuò)誤
# 但是無(wú)法限制給類(lèi)添加方法
Foo.show = print_doc
obj1.show()
obj2.show()
由于'sex'不在__slots__的列表中,所以不能綁定sex屬性,試圖綁定sex將得到AttributeError的錯(cuò)誤。
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定義的屬性?xún)H對(duì)當(dāng)前類(lèi)的實(shí)例起作用,對(duì)繼承了它的子類(lèi)是不起作用的。想想也是這個(gè)道理,如果你繼承一個(gè)父類(lèi),卻莫名其妙發(fā)現(xiàn)有些變量無(wú)法定義,那不是大問(wèn)題么?如果非要子類(lèi)也被限制,除非在子類(lèi)中也定義slots,這樣,子類(lèi)實(shí)例允許定義的屬性就是自身的slots加上父類(lèi)的slots。
成員保護(hù)與訪問(wèn)機(jī)制
有些對(duì)象你不想外部訪問(wèn),即使是通過(guò)調(diào)用類(lèi)對(duì)象也無(wú)法訪問(wèn),那就請(qǐng)認(rèn)真學(xué)完本章節(jié)。
私有成員
class obj:
def __init__(self,name):
self.name=name
def pri(self):
print self.name
__age = 18
# 加上雙下劃線的就是私有變量,只能在類(lèi)的內(nèi)部訪問(wèn),外部無(wú)法訪問(wèn)
a = obj('zhao')
a.pri()
運(yùn)行結(jié)果:
zhao
如果要在類(lèi)中調(diào)用這個(gè)私有成員,可以這么用
class obj:
def __init__(self,name):
self.name=name
def prin(self):
print self.name
__age = 18
# 加上雙下劃線的就是私有變量,只能在類(lèi)的內(nèi)部訪問(wèn),外部無(wú)法訪問(wèn)
@classmethod
# 如果要在類(lèi)中調(diào)用,首先調(diào)用類(lèi)方法
def pri(cls):
print cls.__age
# 然后在使用
a = obj('zhao')
a.prin()
obj.pri()
# 通過(guò)這樣直接調(diào)用類(lèi)中的私有變量
運(yùn)行結(jié)果:
zhao
18
使用get-set-del方法操作私有成員
class obj:
def __init__(self,name):
self.name=name
def prin(self):
print self.name
__age = 18
# 加上雙下劃線的就是私有變量,只能在類(lèi)的內(nèi)部訪問(wèn),外部無(wú)法訪問(wèn)
@classmethod
# 如果要在類(lèi)中調(diào)用,首先調(diào)用類(lèi)方法
def pri(cls):
print cls.__age
# 然后在使用
@classmethod
def set_age(cls,value):
cls.__age = value
return cls.__age
# 這個(gè)用法就是改變__age的值
@classmethod
def get_age(cls):
return cls.__age
# 這個(gè)用法就是直接返回__age的值
@classmethod
def del_age(cls):
del cls.__age
# 這個(gè)用法就是直接刪除__age的值
print obj.get_age()
# 這里是直接調(diào)用出__age的值 返回值18
print obj.set_age(20)
# 這里是直接改變__age的值 返回值20
obj.del_age()
# 這里是直接刪除__age的值
思考: 既然是私有變量,不讓外部訪問(wèn),為何有要在后面調(diào)用又改變呢?因?yàn)榭梢詫?duì)私有變量進(jìn)行額外的檢測(cè),處理,加工等等。比如判斷value的值,使用isinstance然后做if-else判斷。
使用私有變量可以對(duì)內(nèi)部變量進(jìn)行保護(hù),外部無(wú)法改變,但是可以對(duì)它進(jìn)行檢測(cè)處理。
這里引申一下私有成員的保護(hù)機(jī)制,使用__age對(duì)私有變量其實(shí)就是—>obj._obj__age的樣子進(jìn)行保護(hù),說(shuō)白了你直接使用obj._obj__age就可以直接調(diào)用內(nèi)部私有變量age了。
Propety裝飾器
把類(lèi)的方法偽裝成屬性調(diào)用的方式,就是把類(lèi)里面的一個(gè)函數(shù),變成一個(gè)屬性一樣的東西~
一開(kāi)始調(diào)用類(lèi)的方法要使用圓括號(hào),現(xiàn)在變成了屬性進(jìn)行讀取設(shè)置存儲(chǔ)。
舉個(gè)例子來(lái)說(shuō)明:
常用的調(diào)用方法
class obj:
def __init__(self,name,age):
self.__name=name
self.__age=age
# 講這些設(shè)置成私有變量
def get_age(self):
return self.__age
def set_age(self,value):
if isinstance(value,int):
self.__age=value
else:
raise ValueError('非整數(shù)類(lèi)型')
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
# 把這些設(shè)置成私有變量
@property
def age(self):
return self.__age
@age.setter
def age(self,value):
if isinstance(value,int):
self.__age=value
else:
raise ValueError('非整數(shù)類(lèi)型')
@age.deleter
def age(self):
print 'delete over'
a = obj('langzi',18)
# 使用這些裝飾器,可以使用類(lèi)與對(duì)象的方法直接調(diào)用
print a.age
# 這里就是直接調(diào)用返回age的值
a.age=20
# 這里就是直接使用setter把值轉(zhuǎn)換
print a.age
del a.age
# 刪除age
當(dāng)然這種調(diào)用方法有些麻煩,每次都是一個(gè)一個(gè)去實(shí)例類(lèi)與對(duì)象,有個(gè)更加簡(jiǎn)單直觀的方法。
更加減半的使用property()函數(shù)
除了使用裝飾器的方式將一個(gè)方法偽裝成屬性外,Python內(nèi)置的builtins模塊中的property()函數(shù),為我們提供了第二種設(shè)置類(lèi)屬性的手段。
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("刪除年齡數(shù)據(jù)!")
# 核心在這句
age = property(get_age, set_age, del_age, "年齡")
obj = People("jack", 18)
print(obj.age)
obj.age = 19
print("obj.age: ", obj.age)
del obj.ag
通過(guò)語(yǔ)句age = property(get_age, set_age, del_age, “年齡”)將一個(gè)方法偽裝成為屬性。其效果和裝飾器的方法是一樣的。
property()函數(shù)的參數(shù):