知道python中的幾種字符串拼接方式與效率對(duì)比。
- 使用+拼接
- 使用%拼接
- 使用join
- 使用f''f-string方式
f-string方式出自PEP 498(Literal String Interpolation,字面字符串插值),從Python3.6版本引入。其特點(diǎn)是在字符串前加 f 標(biāo)識(shí),字符串中間則用花括號(hào){}包裹其它字符串變量。 這種方式在可讀性上秒殺format()方式,處理長(zhǎng)字符串的拼接時(shí),速度與join()方法相當(dāng)。
知道鴨子類型(duck typing)的含義與其在python中的表現(xiàn)形式。
如果一個(gè)生物走起來像鴨子,叫起來像鴨子,他就是鴨子。對(duì)于Python編程來說,解釋器不管你這個(gè)對(duì)象是什么類型,只管這個(gè)對(duì)象有沒有對(duì)應(yīng)的方法和屬性。所以你希望他是什么類型,你就調(diào)用什么方法就行了,如果類型錯(cuò)誤解釋器會(huì)告訴你,你所調(diào)用的方法不存在。而可以使用"dir(obj)"這樣的命令查看這個(gè)對(duì)象有什么方法和屬性,當(dāng)然也可以通過"type(obj)"來查看這個(gè)對(duì)象當(dāng)前的類型。
知道函數(shù)和方法的區(qū)別,知道綁定方法(bound-method)與未綁定方法(unbound-method)的關(guān)系。
bound和unbound方法是個(gè)很簡(jiǎn)單的概念。在許多語言當(dāng)中,類似于a.b()這樣的調(diào)用方法是一個(gè)整體,但在Python中,它其實(shí)是兩部分:獲取屬性a.b,調(diào)用()。所以也可以寫成:
c = a.b
c()
跟直接調(diào)用a.b()是等效的。當(dāng)a是某個(gè)類的實(shí)例,b是這個(gè)類的方法的時(shí)候,a.b的返回值就是bound method,也就是綁定方法。它實(shí)際上是個(gè)bound method對(duì)象,這個(gè)對(duì)象提前將self參數(shù)進(jìn)行了綁定。實(shí)際演示一下就很容易懂了:
>>> class A(object):
... def b(self):
... pass
...
>>> a = A()
>>> a.b
<bound method A.b of <__main__.A object at 0x0000000002C1ABA8>>
>>> A.b
<unbound method A.b>
>>>
相應(yīng)的unbound method是沒有綁定self的對(duì)象。在Python 3中,它就是普通的函數(shù),在Python 2中則是unbound method類型,不過區(qū)別不大。
我們知道像A.b這樣的方法實(shí)際上跟一個(gè)普通定義的函數(shù)沒有本質(zhì)區(qū)別,這個(gè)函數(shù)有一個(gè)參數(shù)self,所以實(shí)際上完全可以用A.b(a)的方式來調(diào)用,也就是手工將self參數(shù)指定為a。這也就是unbound method的用法。
而相應(yīng)的,bound method是一個(gè)實(shí)現(xiàn)了call的對(duì)象,它自動(dòng)將調(diào)用這個(gè)對(duì)象的過程重定向到A.b(a)上面,相當(dāng)于通過functools.partial綁定了第一個(gè)參數(shù)的效果,所以叫做bound method。
知道asyncio的使用方式和使用場(chǎng)景。
協(xié)程
知道StringIO和BytesIO的用途。
StringIO 寫
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!
StringIO 讀
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
BytesIO 寫
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
BytesIO 讀
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'
作用在于,提供給某些只支持文件的api,比如gzip
知道以單下劃線開頭、雙下劃線開頭和雙下劃線包圍的變量分別代表著什么含義。
- 在python中單下劃線代表私有,但也僅僅是名義上的私有,只是一種規(guī)范,告訴人們不要在外部使用它。但實(shí)際上python沒有真正意義上的私有,我們一樣可以在外部去調(diào)用私有方法或?qū)傩浴?/li>
- 雙下劃線使用來避免父類方法被子類方法覆蓋的。雙下劃線方法的本質(zhì)是在方法前加了類名,我們可以使用對(duì)象.類名__方法名(),來在外部調(diào)用它。
- 前后雙下滑線方法是python自己定義出來,供自己調(diào)用的。這些方法會(huì)在特定的條件下被觸發(fā)執(zhí)行。比如 __init__
知道__init__和__new__方法在class和type中分別的作用是什么。
__init__ 是初始化實(shí)例的方法,__new__是創(chuàng)建實(shí)例的方法,先執(zhí)行__new__再執(zhí)行__init__ 。
知道類變量和實(shí)例變量的區(qū)別。
- 類變量
類變量指的是在類中,但在各個(gè)類方法外定義的變量。舉個(gè)例子:
class CLanguage :
# 下面定義了2個(gè)類變量
name = "C語言中文網(wǎng)"
add = "http://c.biancheng.net"
# 下面定義了一個(gè)say實(shí)例方法
def say(self, content):
print(content)
- 實(shí)例變量
實(shí)例變量指的是在任意類方法內(nèi)部,以“self.變量名”的方式定義的變量,其特點(diǎn)是只作用于調(diào)用方法的對(duì)象。另外,實(shí)例變量只能通過對(duì)象名訪問,無法通過類名訪問。
class CLanguage :
def __init__(self):
self.name = "C語言中文網(wǎng)"
self.add = "http://c.biancheng.net"
# 下面定義了一個(gè)say實(shí)例方法
def say(self):
self.catalog = 13
知道dict在類中的含義,以及類屬性和方法與dict的關(guān)系。
見__slots__
知道Mixin模式以及在python中的用途。
多繼承
知道python中生成器的實(shí)現(xiàn)以及其使用場(chǎng)景。
首先有兩個(gè)概念,生成器函數(shù)和生成器表達(dá)式
生成器的思想是使用的時(shí)候再實(shí)時(shí)計(jì)算出來,優(yōu)點(diǎn)在于能節(jié)約內(nèi)存開銷。以計(jì)算時(shí)間換內(nèi)存空間
生成器函數(shù)
求N以內(nèi)的偶數(shù)
一般的寫法 先算出100以內(nèi)的偶數(shù),放到nums里,然后遍歷出來
def even_number(N):
nums = []
for i in range(N):
if not (i%2):
nums.append(i)
return nums
nums = even_number(100)
for i in nums:
print(i)
生成器寫法
def even_number(N):
for i in range(N):
if not (i%2):
yield i
nums = even_number(100)
for i in nums:
print(i)
注意觀察even_number沒有return 多了個(gè)yield
yield表示返回一個(gè)值之后 掛起該函數(shù),等待下一次執(zhí)行。
知道python中抽象類的實(shí)現(xiàn)方式,以及其抽象基類模塊,知道如何用python類實(shí)現(xiàn)一個(gè)抽象容器類型。
一、
繼承有兩種用途:
"""
一:繼承基類的方法,并且做出自己的改變或者擴(kuò)展(代碼重用)
二:聲明某個(gè)子類兼容于某基類,定義一個(gè)接口類Interface,接口類中定義了一些接口名(就是函數(shù)名)
且并未實(shí)現(xiàn)接口的功能,子類繼承接口類,并且實(shí)現(xiàn)接口中的功能
三、接口隔離原則:使用多個(gè)專門的接口,而不使用單一的總接口。即客戶端不應(yīng)該依賴那些不需要的接口
"""
"""
接口類:基于同一個(gè)接口實(shí)現(xiàn)的類 剛好滿足接口隔離原則 面向?qū)ο箝_發(fā)的思想 規(guī)范
接口類,python 原生不支持 在python中,并沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應(yīng)該有一些基本的概念
"""
一、接口類單繼承
我們來看一段代碼去了解為什么需要接口類
class Alipay:
def pay(self,money):
print('支付寶支付了')
class Apppay:
def pay(self,money):
print('蘋果支付了')
class Weicht:
def pay(self,money):
print('微信支付了')
def pay(payment,money): # 支付函數(shù),總體負(fù)責(zé)支付,對(duì)應(yīng)支付的對(duì)象和要支付的金額
payment.pay(money)
p=Alipay()
pay(p,200) #支付寶支付了
這段代碼,實(shí)現(xiàn)了一個(gè)有趣的功能,就是通過一個(gè)總體的支付函數(shù),實(shí)現(xiàn)了不同種類的支付方式,不同是支付方式作為對(duì)象,傳入函數(shù)中
但是開發(fā)中容易出現(xiàn)一些問題,那就是類中的函數(shù)名不一致,就會(huì)導(dǎo)致調(diào)用的時(shí)候找不到類中對(duì)應(yīng)方法,例題如下:
class Alipay:
def paying(self,money): #這里類的方法可能由于程序員的疏忽,寫的不是一致的pay,導(dǎo)致后面調(diào)用的時(shí)候找不到pay
print('支付寶支付了')
class Apppay:
def pay(self,money):
print('蘋果支付了')
class Weicht:
def pay(self,money):
print('微信支付了')
def pay(payment,money): # 支付函數(shù),總體負(fù)責(zé)支付,對(duì)應(yīng)支付的對(duì)象和要支付的金額
payment.pay(money)
p=Alipay() #不報(bào)錯(cuò)
pay(p,200) #調(diào)用執(zhí)行就會(huì)報(bào)錯(cuò),'Alipay' object has no attribute 'pay'
這時(shí)候怎么辦呢?可以手動(dòng)拋異常:NotImplementedError來解決開發(fā)中遇到的問題
class payment:
def pay(self):
raise NotImplementedError #手動(dòng)拋異常
class Alipay:
def paying(self, money): # 這里類的方法不是一致的pay,導(dǎo)致后面調(diào)用的時(shí)候找不到pay
print('支付寶支付了')
def pay(payment, money): # 支付函數(shù),總體負(fù)責(zé)支付,對(duì)應(yīng)支付的對(duì)象和要支付的金額
payment.pay(money)
p = Alipay() # 不報(bào)錯(cuò)
pay(p, 200) #調(diào)用的時(shí)候才會(huì)報(bào)錯(cuò) 'Alipay' object has no attribute 'pay'
也可以借用abc模塊來處理這種錯(cuò)誤
from abc import abstractmethod, ABCMeta #接口類中定義了一些接口名:Pay,且并未實(shí)現(xiàn)接口的功能,子類繼承接口類,并且實(shí)現(xiàn)接口中的功能
class Payment(metaclass=ABCMeta): #抽象出的共同功能Pay
@abstractmethod
def pay(self,money):pass #這里面的pay 來源于下面類中的方法pay,意思把這個(gè)方法規(guī)范為統(tǒng)一的標(biāo)準(zhǔn),另外建一個(gè)規(guī)范類Payment
class Alipay(Payment):
def paying(self, money): #這里出現(xiàn)paying和我們規(guī)范的pay不一樣,那么在實(shí)例化 Alipay的時(shí)候就會(huì)報(bào)錯(cuò)
print('支付寶支付了')
class Weicht(Payment):
def pay(self,money):
print('微信支付了')
def pay(pay_obj,money):
pay_obj.pay(money)
p=Alipay() #實(shí)例化的時(shí)候就會(huì)報(bào)錯(cuò) Can't instantiate abstract class Alipay with abstract methods pay 之前兩個(gè)例子都是在執(zhí)行的時(shí)候報(bào)錯(cuò),這里不一樣的是實(shí)例化就會(huì)知道是哪里發(fā)生錯(cuò)誤了
"""
總結(jié):用abc模塊裝飾后,在實(shí)例化的時(shí)候就會(huì)報(bào)錯(cuò),那么當(dāng)我們代碼很長(zhǎng)的時(shí)候,就可以早一點(diǎn)預(yù)知錯(cuò)誤,所以以后在接口類類似問題中用這個(gè)模塊
接口繼承實(shí)質(zhì)上是要求“做出一個(gè)良好的抽象,這個(gè)抽象規(guī)定了一個(gè)兼容接口,使得外部調(diào)用者無需關(guān)心具體細(xì)節(jié),
可一視同仁的處理實(shí)現(xiàn)了特定接口的所有對(duì)象”——這在程序設(shè)計(jì)上,叫做歸一化。
"""
二、接口類多繼承
from abc import abstractmethod,ABCMeta
class Walk_animal(meteaclass=ABCMeta):
@abstractmethod
def walk(self):
print('walk')
class Swim_animal(meteaclass=ABCMeta):
@abstractmethod
def swim(self):pass
class Fly_animal(metaclass=ABCMeta)
@abstractmethod
def fly(self):pass
#如果正常一個(gè)老虎有跑和跑的方法的話,我們會(huì)這么做
class Tiger:
def walk(self):pass
def swim(self):pass
#但是我們使用接口類多繼承的話就簡(jiǎn)單多了,并且規(guī)范了相同功能
class Tiger(Walk_animal,Swim_animal):pass
#如果此時(shí)再有一個(gè)天鵝swan,會(huì)飛,走,游泳 那么我們這么做
class Swan(Walk_animal,Swim_animal, Fly_animal):pass
# 這就是接口多繼承
為什么需要接口類
三、抽象類
#抽象類
# 抽象類的本質(zhì)還是類,
# 指的是一組類的相似性,包括數(shù)據(jù)屬性(如all_type)和函數(shù)屬性(如read、write),而接口只強(qiáng)調(diào)函數(shù)屬性的相似性
"""
1.抽象類是一個(gè)介于類和接口直接的一個(gè)概念,同時(shí)具備類和接口的部分特性,可以用來實(shí)現(xiàn)歸一化設(shè)計(jì)
2.在繼承抽象類的過程中,我們應(yīng)該盡量避免多繼承;
3.而在繼承接口的時(shí)候,我們反而鼓勵(lì)你來多繼承接口
# 一般情況下 單繼承 能實(shí)現(xiàn)的功能都是一樣的,所以在父類中可以有一些簡(jiǎn)單的基礎(chǔ)實(shí)現(xiàn)
# 多繼承的情況 由于功能比較復(fù)雜,所以不容易抽象出相同的功能的具體實(shí)現(xiàn)寫在父類中
"""
為什么要有抽象類
從設(shè)計(jì)角度去看,如果類是從現(xiàn)實(shí)對(duì)象抽象而來的,那么抽象類就是基于類抽象而來的。
從實(shí)現(xiàn)角度來看,抽象類與普通類的不同之處在于:抽象類中有抽象方法,該類不能被實(shí)例化,只能被繼承,且子類必須實(shí)現(xiàn)抽象方法。這一點(diǎn)與接口有點(diǎn)類似,但其實(shí)是不同的
#一切皆文件
import abc #利用abc模塊實(shí)現(xiàn)抽象類
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定義抽象方法,無需實(shí)現(xiàn)功能
def read(self):
'子類必須定義讀功能'
pass
@abc.abstractmethod #定義抽象方法,無需實(shí)現(xiàn)功能
def write(self):
'子類必須定義寫功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #報(bào)錯(cuò),子類沒有定義抽象方法
class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('文本數(shù)據(jù)的讀取方法')
def write(self):
print('文本數(shù)據(jù)的讀取方法')
class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('硬盤數(shù)據(jù)的讀取方法')
def write(self):
print('硬盤數(shù)據(jù)的讀取方法')
class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('進(jìn)程數(shù)據(jù)的讀取方法')
def write(self):
print('進(jìn)程數(shù)據(jù)的讀取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#這樣大家都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
四、擴(kuò)展:
不管是抽象類還是接口類 : 面向?qū)ο蟮拈_發(fā)規(guī)范 所有的接口類和抽象類都不能實(shí)例化
java :
java里的所有類的繼承都是單繼承,所以抽象類完美的解決了單繼承需求中的規(guī)范問題
但對(duì)于多繼承的需求,由于java本身語法的不支持,所以創(chuàng)建了接口Interface這個(gè)概念來解決多繼承的規(guī)范問題
python:
python中沒有接口類 :
python中自帶多繼承 所以我們直接用class來實(shí)現(xiàn)了接口類
python中支持抽象類 : 一般情況下 單繼承 不能實(shí)例化
且可以實(shí)現(xiàn)python代碼
五、注意
"""
1.多繼承問題
在繼承抽象類的過程中,我們應(yīng)該盡量避免多繼承;
而在繼承接口的時(shí)候,我們反而鼓勵(lì)你來多繼承接口
2.方法的實(shí)現(xiàn)
在抽象類中,我們可以對(duì)一些抽象方法做出基礎(chǔ)實(shí)現(xiàn);
而在接口類中,任何方法都只是一種規(guī)范,具體的功能需要子類實(shí)現(xiàn)
"""
知道普通方法,classmethod和staticmethod的區(qū)別。
Python面向?qū)ο缶幊讨?,類中定義的方法可以是 @classmethod 裝飾的類方法,也可以是 @staticmethod 裝飾的靜態(tài)方法,用的最多的還是不帶裝飾器的實(shí)例方法,如果把這幾個(gè)方法放一塊,對(duì)初學(xué)者來說無疑是一頭霧水,那我們?cè)撊绾握_地使用它們呢?
先來看一個(gè)簡(jiǎn)單示例:
class A(object):
def m1(self, n):
print("self:", self)
@classmethod
def m2(cls, n):
print("cls:", cls)
@staticmethod
def m3(n):
pass
a = A()
a.m1(1) # self: <__main__.A object at 0x000001E596E41A90>
A.m2(1) # cls: <class '__main__.A'>
A.m3(1)
我在類中一共定義了3個(gè)方法,m1 是實(shí)例方法,第一個(gè)參數(shù)必須是 self(約定俗成的)。m2 是類方法,第一個(gè)參數(shù)必須是cls(同樣是約定俗成),m3 是靜態(tài)方法,參數(shù)根據(jù)業(yè)務(wù)需求定,可有可無。當(dāng)程序運(yùn)行時(shí),大概發(fā)生了這么幾件事(結(jié)合下面的圖來看)。
- 第一步:代碼從第一行開始執(zhí)行 class 命令,此時(shí)會(huì)創(chuàng)建一個(gè)類 A 對(duì)象(沒錯(cuò),類也是對(duì)象,一切皆對(duì)象嘛)同時(shí)初始化類里面的屬性和方法,記住,此刻實(shí)例對(duì)象還沒創(chuàng)建出來。
- 第二、三步:接著執(zhí)行 a=A(),系統(tǒng)自動(dòng)調(diào)用類的構(gòu)造器,構(gòu)造出實(shí)例對(duì)象 a
- 第四步:接著調(diào)用 a.m1(1) ,m1 是實(shí)例方法,內(nèi)部會(huì)自動(dòng)把實(shí)例對(duì)象傳遞給 self 參數(shù)進(jìn)行綁定,也就是說, self 和 a 指向的都是同一個(gè)實(shí)例對(duì)象。
- 第五步:調(diào)用A.m2(1)時(shí),python內(nèi)部隱式地把類對(duì)象傳遞給 cls 參數(shù),cls 和 A 都指向類對(duì)象。
嚴(yán)格意義上來說,左邊的都是變量名,是對(duì)象的引用,右邊才是真正的對(duì)像,為了描述方便,我直接把 a 稱為對(duì)象,你應(yīng)該明白我說對(duì)象其實(shí)是它所引用右邊的那個(gè)真正的對(duì)象。
再來看看每個(gè)方法各有什么特性
實(shí)例方法
print(A.m1)
# A.m1在py2中顯示為<unbound method A.m1>
<function A.m1 at 0x000002BF7FF9A488>
print(a.m1)
<bound method A.m1 of <__main__.A object at 0x000002BF7FFA2BE0>>
A.m1是一個(gè)還沒有綁定實(shí)例對(duì)象的方法,對(duì)于未綁定方法,調(diào)用 A.m1 時(shí)必須顯示地傳入一個(gè)實(shí)例對(duì)象進(jìn)去,而 a.m1是已經(jīng)綁定了實(shí)例的方法,python隱式地把對(duì)象傳遞給了self參數(shù),所以不再手動(dòng)傳遞參數(shù),這是調(diào)用實(shí)例方法的過程。
A.m1(a, 1)
# 等價(jià)
a.m1(1)
如果未綁定的方法 A.m1 不傳實(shí)例對(duì)象給 self 時(shí),就會(huì)報(bào)參數(shù)缺失錯(cuò)誤,在 py3 與 py2 中,兩者報(bào)的錯(cuò)誤不一致,python2 要求第一個(gè)參數(shù)self是實(shí)例對(duì)象,而python3中可以是任意對(duì)象。
A.m1(1)
TypeError: m1() missing 1 required positional argument: 'n'
類方法
print(A.m2)
<bound method A.m2 of <class '__main__.A'>>
print(a.m2)
<bound method A.m2 of <class '__main__.A'>>
m2是類方法,不管是 A.m2 還是 a.m2,都是已經(jīng)自動(dòng)綁定了類對(duì)象A的方法,對(duì)于后者,因?yàn)閜ython可以通過實(shí)例對(duì)象a找到它所屬的類是A,找到A之后自動(dòng)綁定到 cls。
A.m2(1)
# 等價(jià)
a.m2(1)
這使得我們可以在實(shí)例方法中通過使用 self.m2()這種方式來調(diào)用類方法和靜態(tài)方法。
def m1(self, n):
print("self:", self)
self.m2(n)
靜態(tài)方法
print(A.m3)
<function A.m3 at 0x000002BF7FF9A840>
print(a.m3)
<function A.m3 at 0x000002BF7FF9A840>
m3是類里面的一個(gè)靜態(tài)方法,跟普通函數(shù)沒什么區(qū)別,與類和實(shí)例都沒有所謂的綁定關(guān)系,它只不過是碰巧存在類中的一個(gè)函數(shù)而已。不論是通過類還是實(shí)例都可以引用該方法。
A.m3(1)
# 等價(jià)
a.m3(1)
以上就是幾個(gè)方法的基本介紹。現(xiàn)在把幾個(gè)基本的概念理清楚了,那么現(xiàn)在來說說幾個(gè)方法之間的使用場(chǎng)景以及他們之間的優(yōu)缺點(diǎn)。
應(yīng)用場(chǎng)景
靜態(tài)方法的使用場(chǎng)景:
如果在方法中不需要訪問任何實(shí)例方法和屬性,純粹地通過傳入?yún)?shù)并返回?cái)?shù)據(jù)的功能性方法,那么它就適合用靜態(tài)方法來定義,它節(jié)省了實(shí)例化對(duì)象的開銷成本,往往這種方法放在類外面的模塊層作為一個(gè)函數(shù)存在也是沒問題的,而放在類中,僅為這個(gè)類服務(wù)。例如下面是微信公眾號(hào)開發(fā)中驗(yàn)證微信簽名的一個(gè)例子,它沒有引用任何類或者實(shí)例相關(guān)的屬性和方法。
from hashlib import sha1
import tornado.web
class SignatureHandler(tornado.web.RequestHandler):
def get(self):
"""
根據(jù)簽名判斷請(qǐng)求是否來自微信
"""
signature = self.get_query_argument("signature", None)
echostr = self.get_query_argument("echostr", None)
timestamp = self.get_query_argument("timestamp", None)
nonce = self.get_query_argument("nonce", None)
if self._check_sign(TOKEN, timestamp, nonce, signature):
logger.info("微信簽名校驗(yàn)成功")
self.write(echostr)
else:
self.write("你不是微信發(fā)過來的請(qǐng)求")
@staticmethod
def _check_sign(token, timestamp, nonce, signature):
sign = [token, timestamp, nonce]
sign.sort()
sign = "".join(sign)
sign = sha1(sign).hexdigest()
return sign == signature
類方法的使用場(chǎng)景有:
作為工廠方法創(chuàng)建實(shí)例對(duì)象,例如內(nèi)置模塊 datetime.date 類中就有大量使用類方法作為工廠方法,以此來創(chuàng)建date對(duì)象。
class date:
def __new__(cls, year, month=None, day=None):
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
return self
@classmethod
def fromtimestamp(cls, t):
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
@classmethod
def today(cls):
t = _time.time()
return cls.fromtimestamp(t)
如果希望在方法裡面調(diào)用靜態(tài)類,那么把方法定義成類方法是合適的,因?yàn)橐嵌x成靜態(tài)方法,那么你就要顯示地引用類A,這對(duì)繼承來說可不是一件好事情。
class A:
@staticmethod
def m1()
pass
@staticmethod
def m2():
A.m1() # bad
@classmethod
def m3(cls):
cls.m1() # good
知道python中==與is的區(qū)別。
== 是判斷值是否相等 is 是判斷指針是否是同一個(gè)變量
知道裝飾器中添加functools.wraps的含義與作用。
functools.wraps 旨在消除裝飾器對(duì)原函數(shù)造成的影響,即對(duì)原函數(shù)的相關(guān)屬性進(jìn)行拷貝,已達(dá)到裝飾器不修改原函數(shù)的目的。
知道getattr和getattribute的作用以及其順序關(guān)系。
在閱讀很多優(yōu)秀的python框架代碼時(shí),getattr(), __getattr__(), __getattribute__()和__get__()這幾個(gè)方法都是很常見的,它們都是在什么時(shí)候 被調(diào)用呢,用處又是什么,然后它們之前有哪些關(guān)聯(lián)呢。下面來通過例子分析一下。getattr()和另外三個(gè)方法都是魔法函數(shù)不同的是,getattr()是python內(nèi)置的一個(gè)函數(shù),它可以用來獲取對(duì)象的屬性和方法。例子如下:
class A():
a = 5
def __init__(self, x):
self.x = x
def hello(self):
return 'hello func'
a = A(10)
print(getattr(a, 'x')) #相當(dāng)于a.x
print(getattr(a, 'y', 20)) #相當(dāng)于a.y,因?yàn)閍.y并不存在,所以返回第三個(gè)參數(shù)作為默認(rèn)值
print(getattr(a, 'hello')()) # 相當(dāng)于a.hello()
print(getattr(A, 'a')) # 相當(dāng)于A.a這段代碼的輸出結(jié)果是:10
20
hello func
5
可以看出,getattr()可以用來獲取對(duì)像的屬性和方法,需要注意的是,如果通過getattr()來嘗試獲取對(duì)象里并不存在的屬性時(shí)沒有添加第三個(gè)默認(rèn)值,代碼會(huì) 報(bào)錯(cuò),如下所示:
print(getattr(a, 'y'))
運(yùn)行會(huì)報(bào)異常提示找不到屬于y:
Traceback (most recent call last):
File "app/test.py", line 32, in <module>
print(getattr(a, 'y'))
AttributeError: 'A' object has no attribute 'y'
__getattr()與__getattribute()這兩個(gè)是類對(duì)象的魔法函數(shù),在訪問對(duì)象屬性的時(shí)候會(huì)被調(diào)用,但是兩者之間也有一點(diǎn)區(qū)別, 我們通過代碼來看一下:
class A(object):
def __init__(self, x):
self.x = x
def hello(self):
return 'hello func'
def __getattr__(self, item):
print('in __getattr__')
return 100
def __getattribute__(self, item):
print('in __getattribute__')
return super(A, self).__getattribute__(item)
a = A(10)
print(a.x)
print(a.y)運(yùn)行代碼,得到下面輸出:in __getattribute__
10
in __getattribute__
in __getattr__
100
可以看出,在獲到對(duì)象屬性時(shí),__getattribute__()是一定會(huì)被調(diào)用的,無論屬性存不存在,首先都會(huì)調(diào)用這個(gè)魔法方法。 如果調(diào)用像a.y這種不存在的對(duì)象時(shí),調(diào)用__getattribute__()找不到y(tǒng)這個(gè)屬性,就會(huì)再調(diào)用__getattr__()這個(gè)魔法方法,
可以通過在這個(gè)方法里實(shí) 來設(shè)置屬性不存在時(shí)的默認(rèn)值。使用上面的getattr()方法獲取屬性時(shí),也是同樣的調(diào)用關(guān)系,只不過只有在getattr()帶第三個(gè)參數(shù)作為默認(rèn)值時(shí),才會(huì)調(diào)用 __getattr__()方法。
__get__()
__get__()方法是描述符方法之一,和他經(jīng)常配套使用的是__set__()方法,通過描述符,可以將訪問對(duì)象屬性轉(zhuǎn)變?yōu)檎{(diào)用描述符方法。這在ORM中被經(jīng)常使用, 可以通過描述符方法進(jìn)行參數(shù)格式驗(yàn)證。
import random
class Die(object):
def __init__(self, sides=6):
self.sides = sides
def __get__(self, instance, owner):
print('Die __get__()')
return int(random.random() * self.sides) + 1
def __set__(self, instance, value):
print('Die __set__()')
class Game(object):
d6 = Die()
d10 = Die(sides=10)
d20 = Die(sides=20)
game = Game()
print(game.d6)
game.d6 = 10
這段代碼的輸出結(jié)果是:
Die __get__()
5
Die __set__()
這就是描述符的作用, 使用描述符可以讓我們?cè)讷@取或者給對(duì)象賦值時(shí)對(duì)數(shù)據(jù)值進(jìn)行一些特殊的加工和處理。
python里經(jīng)常使用的@property裝飾器其實(shí)就是 通過描述符的方式實(shí)現(xiàn)的。 當(dāng)然關(guān)于描述符,我們還需要知道,
如果一個(gè)類僅僅實(shí)現(xiàn)了__get__()方法,那么這個(gè)類被稱為非數(shù)據(jù)描述符;如果一個(gè)類實(shí)現(xiàn)在__get__()并且還實(shí)現(xiàn)在 __set__()和__del__()中的一個(gè),這個(gè)類就被稱為數(shù)據(jù)描述符。
知道python中自省的使用方式,知道inspect庫(kù)的常見用法。
我們用自省最重要的幾個(gè)目的就是,讓python回答我們:對(duì)象名稱是什么?對(duì)象能做什么?對(duì)象是什么類型?對(duì)象的一些基礎(chǔ)信息是什么?好了,廢話不多說,看代碼就知道我指的函數(shù)是什么函數(shù)。代碼:
s_obj = "hello world"
# 檢查對(duì)象類型
print(type(s_obj))
# 檢查對(duì)象ID
print(id(s_obj))
# 檢查對(duì)象是否包含某個(gè)屬性
print(hasattr(s_obj, '__doc__'))
print(hasattr(dir, '__doc__'))
# 獲取某個(gè)對(duì)象的屬性值
print(getattr(s_obj, '__doc__'))
type和id函數(shù)之前都有用過就不講了,hasattr是用來檢測(cè)對(duì)象是否包含某個(gè)屬性的,如果是就返回True否則返回False。getattr是用來獲取屬性值的。python的自省遠(yuǎn)遠(yuǎn)不止這些,比如前面章節(jié)講到的成員運(yùn)算符isinstance()也是一個(gè)python自省的函數(shù)。不僅如此,還可以檢測(cè)某個(gè)對(duì)象是否屬于某個(gè)類、或者某個(gè)類是否是別的類的字類等等。這些會(huì)在我后面講述自定義類的時(shí)候講到。
inspect:
1.對(duì)類,模塊的操作,成員,類,模塊類型的判斷
2.獲取源碼
3.獲取類或函數(shù)的參數(shù)信息
4.解析堆棧
知道python中弱引用的使用方式,知道python中g(shù)c的回收算法方式以及回收規(guī)則。
和許多其它的高級(jí)語言一樣,Python使用了垃圾回收器來自動(dòng)銷毀那些不再使用的對(duì)象。每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù),當(dāng)這個(gè)引用計(jì)數(shù)為0時(shí)Python能夠安全地銷毀這個(gè)對(duì)象。
引用計(jì)數(shù)會(huì)記錄給定對(duì)象的引用個(gè)數(shù),并在引用個(gè)數(shù)為零時(shí)收集該對(duì)象。由于一次僅能有一個(gè)對(duì)象被回收,引用計(jì)數(shù)無法回收循環(huán)引用的對(duì)象。
一組相互引用的對(duì)象若沒有被其它對(duì)象直接引用,并且不可訪問,則會(huì)永久存活下來。一個(gè)應(yīng)用程序如果持續(xù)地產(chǎn)生這種不可訪問的對(duì)象群組,就會(huì)發(fā)生內(nèi)存泄漏。
在對(duì)象群組內(nèi)部使用弱引用(即不會(huì)在引用計(jì)數(shù)中被計(jì)數(shù)的引用)有時(shí)能避免出現(xiàn)引用環(huán),因此弱引用可用于解決循環(huán)引用的問題。
在計(jì)算機(jī)程序設(shè)計(jì)中,弱引用,與強(qiáng)引用相對(duì),是指不能確保其引用的對(duì)象不會(huì)被垃圾回收器回收的引用。一個(gè)對(duì)象若只被弱引用所引用,則可能在任何時(shí)刻被回收。弱引用的主要作用就是減少循環(huán)引用,減少內(nèi)存中不必要的對(duì)象存在的數(shù)量。
使用weakref模塊,你可以創(chuàng)建到對(duì)象的弱引用,Python在對(duì)象的引用計(jì)數(shù)為0或只存在對(duì)象的弱引用時(shí)將回收這個(gè)對(duì)象。
創(chuàng)建弱引用
你可以通過調(diào)用weakref模塊的ref(obj[,callback])來創(chuàng)建一個(gè)弱引用,obj是你想弱引用的對(duì)象,callback是一個(gè)可選的函數(shù),當(dāng)因沒有引用導(dǎo)致Python要銷毀這個(gè)對(duì)象時(shí)調(diào)用?;卣{(diào)函數(shù)callback要求單個(gè)參數(shù)(弱引用的對(duì)象)。
一旦你有了一個(gè)對(duì)象的弱引用,你就能通過調(diào)用弱引用來獲取被弱引用的對(duì)象。
>>>> import sys
>>> import weakref
>>> class Man:
def __init__(self,name):
print self.name = name
>>> o = Man('Jim')
>>> sys.getrefcount(o)
2
>>> r = weakref.ref(o) # 創(chuàng)建一個(gè)弱引用
>>> sys.getrefcount(o) # 引用計(jì)數(shù)并沒有改變
2
>>> r
<weakref at 00D3B3F0; to 'instance' at 00D37A30> # 弱引用所指向的對(duì)象信息
>>> o2 = r() # 獲取弱引用所指向的對(duì)象
>>> o is o2
True
>>> sys.getrefcount(o)
3
>>> o = None
>>> o2 = None
>>> r # 當(dāng)對(duì)象引用計(jì)數(shù)為零時(shí),弱引用失效。
<weakref at 00D3B3F0; dead>de>
上面的代碼中,我們使用sys包中的getrefcount()來查看某個(gè)對(duì)象的引用計(jì)數(shù)。需要注意的是,當(dāng)使用某個(gè)引用作為參數(shù),傳遞給getrefcount()時(shí),參數(shù)實(shí)際上創(chuàng)建了一個(gè)臨時(shí)的引用。因此,getrefcount()所得到的結(jié)果,會(huì)比期望的多1。
一旦沒有了對(duì)這個(gè)對(duì)象的其它的引用,調(diào)用弱引用將返回None,因?yàn)镻ython已經(jīng)銷毀了這個(gè)對(duì)象。 注意:大部分的對(duì)象不能通過弱引用來訪問。
weakref模塊中的getweakrefcount(obj)和getweakrefs(obj)分別返回弱引用數(shù)和關(guān)于所給對(duì)象的引用列表。
弱引用對(duì)于創(chuàng)建對(duì)象(這些對(duì)象很費(fèi)資源)的緩存是有用的。
知道global,local和nonlocal關(guān)鍵字在python中的含義和其使用場(chǎng)景。
默認(rèn)變量都是local
global
global 把局部變量設(shè)置為全局變量
name = '張三'
def fun():
global name
name = '李四'
def fun2():
print(name)
>>> fun2()
張三
>>> fun()
>>> fun2()
李四
>>>
nonlocal
nonlocal 把局部變量,“下放”到上層空間用
name = '張二'
def fun():
name = '張三'
def fun2():
def fun3():
nonlocal name
name = '李四'
print(name)
fun3()
print(name)
print(name)
fun2()
print(name)
fun()
print(name)
>>> fun()
張三
張三
李四
李四
>>> name
'張二'
知道for-else,try-else的含義和用途。
- for-else
for 循環(huán)里沒有遇到break,自然循環(huán)結(jié)束后執(zhí)行else里的語句 - try-else
try 里的語句正常執(zhí)行,沒有遇到except,正常執(zhí)行完成以后執(zhí)行else
知道.pyc文件的含義,清楚python代碼大概的執(zhí)行過程。
將.py形式的程序編譯成中間式文件(byte-compiled)的.pyc文件,這么做的目的就是為了加快下次執(zhí)行文件的速度。
運(yùn)行python文件的時(shí)候,就會(huì)自動(dòng)首先查看是否具有.pyc文件,如果有的話,而且.py文件的修改時(shí)間和.pyc的修改時(shí)間一樣,就會(huì)讀取.pyc文件,否則,Python就會(huì)讀原來的.py文件。
執(zhí)行過程
- 完成模塊的加載和鏈接;
- 將源代碼翻譯為PyCodeObject對(duì)象(這貨就是字節(jié)碼),并將其寫入內(nèi)存當(dāng)中(方便CPU讀取,起到加速程序運(yùn)行的作用);
- 從上述內(nèi)存空間中讀取指令并執(zhí)行;
- 程序結(jié)束后,根據(jù)命令行調(diào)用情況(即運(yùn)行程序的方式)決定是否將PyCodeObject寫回硬盤當(dāng)中(也就是直接復(fù)制到.pyc或.pyo文件中);
- 之后若再次執(zhí)行該腳本,則先檢查本地是否有上述字節(jié)碼文件。有則執(zhí)行,否則重復(fù)上述步驟。
知道python中常見的魔術(shù)方法和其使用方式。
魔法方法就是可以給你的類增加魔力的特殊方法,如果你的對(duì)象實(shí)現(xiàn)了這些方法中的某一個(gè),那么這個(gè)方法就會(huì)在特殊的情況下被 Python 所調(diào)用,你可以定義自己想要的行為,而這一切都是自動(dòng)發(fā)生的。
它們經(jīng)常是兩個(gè)下劃線包圍來命名的(比如 init/new等等),Python的魔法方法是非常強(qiáng)大的。如果你學(xué)習(xí)過Java,那你會(huì)發(fā)現(xiàn)Python中的魔法方法像是Java中的重載,Python中的魔法方法可以理解為:對(duì)類中的內(nèi)置方法的重載,注意這里不是重寫。
舉個(gè)例子,Python中有個(gè)比較操作符==用來比較兩個(gè)變量的大小,而這個(gè)操作符是通過內(nèi)置函數(shù)eq來實(shí)現(xiàn)的,所以我們只需要通過改變這個(gè)內(nèi)置函數(shù)代碼,就可以改變重新定義這個(gè)操作符的行為。
我們定義一個(gè)類Word,繼承自str類,現(xiàn)需要重新定義該類的操作符==,使這個(gè)操作符用來判斷兩個(gè)字符串長(zhǎng)度是否相等,而不是通過字母順序判斷兩個(gè)字符串是否相等。注意該變化只適用于Word類,而不適用于其它類。
class Word(str):
def __eq__(self, other):
return len(self) == len(other)
a = Word('asd')
b = Word('ips')
print(a == b)
python中常用魔術(shù)方法有
