我眼中一個(gè)好的Pythoneer應(yīng)該具備的品質(zhì)(二)

知道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ù)的目的。

知道getattrgetattribute的作用以及其順序關(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ù)方法有


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容