python重要知識點總結一

內容包含:

  • 元類
  • python 對象和類的綁定以及類方法,靜態方法
  • python 子類調用父類方法總結
  • python 方法解析順序MRQ
  • python定制類和魔法方法
  • 關于用法__slots__
  • @property使用
  • 修飾器
  • 閉包

0、元類

元類就是類的類,所體現的終極思想就是一切皆對象。

image.png

關于深層次,待使用到在總結。

1、python 對象和類的綁定以及類方法,靜態方法

通常我們要使用一個類中的方法時,都需要實例化該類,再進行調用,那類中 self 和 cls 有什么含義,能不能不初始化一個實例而直接調用類方法,對象方法和類方法,靜態方法又有什么關系。是本篇文章思考的問題。

類的調用有以下兩種方法:

>>>class Test:
...    def func(self, message):
...        print message
...
>>>object1=Test()
>>>x=object1.func
>>>x('abc')
abc
>>>t=Test.func
>>>t(object1,'abc')
abc

但是對于 t=Test.func 來說,變量名 t 是關聯到了類 Test 的func 方法的地址上,t是非綁定的,所以在調用t(object1, ‘abc’) 時,必須顯式的將實例名與 self 關聯,否則將會報出”TypeError: unbound method func() must be called with Test instance as first argument (got str instance instead)” 的錯誤。

參考學習

明白以下幾點:
1、類默認的方法都是綁定對象的,而self參數也是指向該對象,沒有實例化對象時,類中方法調用會出錯,也涉及到python自動傳遞self參數。
2、若想不實例化而直接通過 類名.方法 來調用,需要指定該方法綁定到類,如下,一要使用@classmethod 裝飾器,二方法中第一個參數為cls,而不是self。

>>> class Foo(object):          
...     @classmethod                #定義類方法要點1
...     def foo(cls):               #定義類方法要點2
...             print 'call foo'
... 
>>> Foo.foo()
call foo
>>> Foo().foo()
call foo

類也是對象,因此和下面的靜態方法還是有不一樣。類中直接定義的屬性如下,在類方法中也是可以直接使用的。

class pizza(object):
    radius = 42
    @classmethod
    def get_radius(cls):
        return cls.radius
print pizza.get_radius()

類方法對于創建工廠函數最有用,如下

class pizza(object):
    def __init__(self,ingre):
        self.ingre = ingre

    @classmethod
    def from_other(cls,fridge):
        return cls(fridge.juice()+fridge.cheese())  
    def get_ingre(self):
        return self.ingre

cls代表該類,cls()也是用來創建對象,和pizza(fridge.juice()+fridge.cheese())效果一樣。待理解,工廠方法是什么?
3、若只想當成一個普通函數,定義不包含self和cls,則可以使用靜態方法,如下:

>>> class Foo(object):
...     @staticmethod
...     def foo():
...             print 'call foo'
... 
>>> Foo.foo()
call foo
>>> Foo().foo()
call foo

作者:_Zhao_
鏈接:http://www.lxweimin.com/p/4b871019ef96
來源:簡書

2、python 子類調用父類方法總結

參考來源

talk is weak,從程序開始:

class Person(object):
    def __init__(self):
        self.name = "Tom"
    def getName(self):
        return self.name

class Student(Person):
    def __init__(self):
        self.age = 12
    def getAge(self):
        return self.age

if __name__ == "__main__":
    stu = Student()
    print stu.getName()

作者:nummy
鏈接:http://www.lxweimin.com/p/dfa189850651
image.png

上面只是說明一個常用的子類調用父類場景,即調用父類的初始化函數。
直接運行以上代碼會出錯,因為盡管Student類繼承了Person類,但是并沒有調用父類的init()方法,因為子類中對init函數進行了重寫,若沒有重寫會直接繼承父類的init函數自動運行。有以下兩種方法:

參考
1、super方法

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        # super().__init__()
        super(A,self).__init__()
        print('A.__init__')

class B(Base):
    def __init__(self):
        # super().__init__()
        super(B,self).__init__()
        print('B.__init__')

class C(A,B):
    def __init__(self):
        # super().__init__()  # Only one call to super() here  python3
        super(C,self).__init__()
        print('C.__init__')

運行結果

>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>>

2、調用未綁定的父類構造方法

class Person(object):
    def __init__(self):
        self.name = "Tom"
    def getName(self):
        return self.name

class Student(Person):
    def __init__(self):
        Person.__init__(self)
        self.age = 12
    def getAge(self):
        return self.age

if __name__ == "__main__":
    stu = Student()
    print stu.getName()

作者:nummy
鏈接:http://www.lxweimin.com/p/dfa189850651

非綁定方法不經常用到,上述場景卻使用的比較多(即子類覆蓋父類的方法)。運行時沒有父類person的實例,需要顯示地進行傳遞,但有Student的實例,可以用來進行代替。
這種方法叫做調用父類的未綁定的構造方法。在調用一個實例的方法時,該方法的self參數會被自動綁定到實例上(稱為綁定方法)。但如果直接調用類的方法(比如Person.__init),那么就沒有實例會被綁定。這樣就可以自由的提供需要的self參數,這種方法稱為未綁定unbound方法。
通過將當前的實例作為self參數提供給未綁定方法,Student類就能使用其父類構造方法的所有實現,從而name變量被設置。

3、python 方法解析順序

參考

上述博文具有很強的參考意義,轉述如下:
在類的多繼承中,方法解析順序MRQ具有很重要的意義,比如對以下菱形繼承,D的實例調用show方法,是調用A的還是C的show。

image.png

python解析順序的規范化也是一個不斷發展的過程,主要有以下兩個階段:

  • 2.2之前的經典類。經典類中多繼承方法解析采用深度優先從左到右搜索,即D-B-A-C-A,也就是說經典類中只采用A的show方法。
  • 經典類對單層繼承沒有什么問題,但是對上述來說,我們顯然更愿意使用C的show方法,因為他是對A的具體化,但是經典類比并不能實現,于是在2.2中引入新式類(繼承自object),它仍然采用從左至右的深度優先遍歷,但是如果遍歷中出現重復的類,只保留最后一個。并且在定義類時就計算出該類的 MRO 并將其作為類的屬性。因此新式類可以直接通過 mro 屬性獲取類的 MRO。
    舉個例子:
image.png

按照深度遍歷,其順序為 [D, B, A, object, C, A, object],重復類只保留最后一個,因此變為 [D, B, C, A, object]

這樣看起來好像么有問題,但是會有潛在的問題,比如破壞了單調性原則,因此在2.3中引入了 __ C3 算法__。

C3 MRQ

我們把類 C 的線性化(MRO)記為 L[C] = [C1, C2,…,CN]。其中 C1 稱為 L[C] 的頭,其余元素 [C2,…,CN] 稱為尾。如果一個類 C 繼承自基類 B1、B2、……、BN,那么我們可以根據以下兩步計算出 L[C]:
1、L[object] = [object]
2、L[C(B1…BN)] = [C] + merge(L[B1]…L[BN], [B1]…[BN])
這里的關鍵在于 merge,其輸入是一組列表,按照如下方式輸出一個列表:
檢查第一個列表的頭元素(如 L[B1] 的頭),記作 H。
若 H 未出現在其它列表的尾部,則將其輸出,并將其從所有列表中刪除,然后回到步驟1;否則,取出下一個列表的頭部記作 H,繼續該步驟。
重復上述步驟,直至列表為空或者不能再找出可以輸出的元素。如果是前一種情況,則算法結束;如果是后一種情況,說明無法構建繼承關系,Python 會拋出異常。

舉例:


image.png

根據C3,計算過程為:

image.png

4、python定制類和魔法方法

參考學習

形如__xxx__的變量或者函數名要注意,這些在Python中是有特殊用途。常見的就是__inint__()函數了,在對象創建后用來初始化,類似的還有__new()__ 和__del__函數。用的不是很多,不做細節深入。

__str__ 和 __rerp__
class yuan(object):
    def __init__(self):
        self.name = 'yuanqijie'
        self.age = 22
        self.ambition = 'yes'
    def __str__(self):
        return 'object name: %s'  % self.name

qijie = yuan()
print qijie

輸出為:
object name: yuanqijie

若沒有重寫 __str__ 則輸出為 <main.Student object at 0x109afb310>
但注意到,若直接輸出變量而不是用print在提示符下還是上述信息,因為直接顯示變量調用的不是str(),而是repr(),兩者的區別是str()返回用戶看到的字符串,而repr()返回程序開發者看到的字符串,也就是說,repr()是為調試服務的??梢灶愃粕鲜龇椒ㄟM行重寫,作為了解即可。

5、__slots__

當定義一個類時,可以動態的給該類綁定一個屬性和方法,比如:

>>> class Student(object):
...     pass

>>> s = Student()
>>> s.name = 'Michael' # 動態給實例綁定一個屬性
>>> print s.name
Michael

>>> def set_age(self, age): # 定義一個函數作為實例方法
...     self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s, Student) # 給實例綁定一個方法
>>> s.set_age(25) # 調用實例方法
>>> s.age # 測試結果
25

注意的是,上面是給一個實例綁定的相應的方法,也就是說當在生成一個實例時,上述增加的屬性和方法就不起作用了??梢越oclass綁定方法:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = MethodType(set_score, None, Student)

只需將MethodType第二個參數改為None就行。

__slots__ 用來限制屬性
>>> class people(object):
...     __slots__ = ('age','name') # 用tuple定義允許綁定的屬性名稱
... 
>>> p = people()
>>> p.age = 20
>>> p.na = yuan
>>> p.na = 'yuan'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'people' object has no attribute 'na'
>>> p.name = 'yuan'
>>> 

6、@property使用

參考
http://python.jobbole.com/80955/
由上面一節可以知道,綁定屬性時,可以隨意修改屬性值,比如

s = Student()
s.score = 9999

很多時候都需要對屬性值進行判斷,比如正負,大小范圍等,一般來說就需要寫一個函數進行邏輯檢查,比如:

class Student(object):

    def get_score(self):
        return self._score

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

這樣就能保證能對傳入的值進行邏輯約束,但是每次設置需要調用相應函數,比如s.set_score( 99 ),又顯得不是很簡潔,能不能像 s.score = 99一樣簡潔又能進行邏輯檢查呢。就是@property。
@property裝飾器可以將一個method變為屬性,可以像屬性一樣簡單調用,如student.get_score ,若沒有裝飾器,則返回的是函數地址。關于setter用法見下。

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

@score.setter裝飾器表示可以對該屬性賦值,若沒有則是一個只讀的屬性。

7、修飾器

參考學習

示例1:
class myDecorator(object):
    def __init__(self, fn):
        print "inside myDecorator.__init__()"
        self.fn = fn

    def __call__(self):
        self.fn()
        print "inside myDecorator.__call__()"


@myDecorator
def aFunction():
    print "inside aFunction()"

print "Finished decorating aFunction()"
aFunction()

運行上述輸出為:

inside myDecorator.__init__()
Finished decorating aFunction()
inside aFunction()
inside myDecorator.__call__()
示例2:
def check_is_admin(f):
    def wrapper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception("error occur")
        return f(*args, **kwargs)
    return wrapper

class store(object):
    @check_is_admin
    def get_food(self,username,food):
        print food

s = store()
s.get_food(username='admin',food='noodles')
print s.get_food.__name__

上述程序定義了check_is_admin的裝飾器,裝飾器的主要作用是調用某個函數之前執行一類通用的操作,比如日志任務,上述是執行了權限檢查。
函數被裝飾器修飾時,本質上函數變為
get_food = check_is_admin(get_food(self,username,food))
check_is_admin直接返回 wrapper函數地址,因此get_food也是指向wrapper函數,故print s.get_food.__name__結果是 wrapper.
因此調用s.get_food(username='admin',food='noodles')也就是
wrapper(username='admin',food='noodles')。該函數最后一定要有return f(*args, **kwargs) ,這確保原來函數被執行并返回結果。
因為裝飾器使原函數指向了另一個函數(如上面的wrapper),而原函數只是該函數的一部分,該方式確實對原函數進行了擴展。但同時引入了另外的問題,原函數的屬性和名字沒有了,如上面s.get_food.__name__并不是get_food。functools提供了名為wraps的裝飾器,會復制這些屬性給裝飾器函數,用法如下:

import functools
def check_is_admin(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception("error occur")
        #return f(*args, **kwargs)
    return wrapper

只需額外添加兩行代碼。
值得一提的是,**kwargs指定了字典方式傳入數據,因此只支持s.get_food(username='admin',food='noodles')而不支持s.get_food('admin','noodles')。為了代碼的通用性,考慮對其進行完善,使用inspect模塊,最終為:

import functools
import inspect
def check_is_admin(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        func_args = inspect.getcallargs(f,*args,**kwargs)
        if func_args.get('username') != 'admin':
            raise Exception("error occur")
        print 'test'
        return f(*args, **kwargs)
    return wrapper

func_args會以字典形式記錄對應的key和value。意味著裝飾器不用檢查參數是否是基于位置的參數還是關鍵字參數,最終以相同的格式保存在返回字典中。

補充
funclist = []
def decorate(func):
    funclist.append(func)
    return func

@decorate
def func1(num):
    return num + 1

@decorate
def func2(num):
    return num * num

@decorate
def func3(num):
    return num * 2

if __name__ == '__main__':
    print max(func(3) for func in funclist)

上面想要說明的是裝飾器函數是會執行的,func1 = decorate(func1),這是一條語句,是會執行的,因此,funclist中已經進行了記錄。上面的裝飾器是最簡單的裝飾器。

閉包

def decorate(func):
    print 'hello'
    msg = 'nice day'
    def wrapper(*args, **kwargs):
        print msg
        if kwargs.get('username') == 'yuan':
            return func(*args, **kwargs)
        else:
            return
    return wrapper

@decorate
def login(username):
    print username

if __name__ == '__main__':
    login(username='yuan')
    print login.__name__
    print login.__closure__

輸出:

hello
nice day
yuan
wrapper
(<cell at 0x7f60023c64b0: function object at 0x7f60023c4ed8>, <cell at 0x7f60023c6558: str object at 0x7f60023c72d0>)

對于函數login來說,執行login = decorate(login),返回一個wrapper函數,因此__name__打印出wrapper,但有一個問題,因為wrapper是一個函數,返回之后比較有趣的是 func參數和msg參數仍能使用。一般來說,函數的局部變量只有在執行期間有效,返回之后就會失效。就是說上面的函數通過閉包使脫離了函數本身的作用范圍,但仍能訪問到局部變量。
如何實現的呢?
函數都有一個__closure__屬性,如果這個函數是一個閉包的話,那么它返回的是一個由 cell 對象組成的元組對象。cell 對象的cell_contents 屬性就是閉包中的自由變量。

值得注意的是:
def decorate():
    total = 0
    def wrapper(value)
        total += value
    return wrapper

上面函數會報錯,錯誤原因是total 變量沒有定義,其實,對數字或者任何不可變類型,在嵌套函數中重新賦值都會發生錯誤,因為會創建局部變量,從而不會保存在閉包中。
要解決這個問題,可以使用 nonlocal 關鍵字,在python3中有效。

總結

嵌套函數可以訪問外層函數的局部變量,閉包則可以使函數脫離了函數本身的作用范圍,依然能夠訪問局部變量。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 要點: 函數式編程:注意不是“函數編程”,多了一個“式” 模塊:如何使用模塊 面向對象編程:面向對象的概念、屬性、...
    victorsungo閱讀 1,563評論 0 6
  • Python進階框架 希望大家喜歡,點贊哦首先感謝廖雪峰老師對于該課程的講解 一、函數式編程 1.1 函數式編程簡...
    Gaolex閱讀 5,524評論 6 53
  • 定義類并創建實例 在Python中,類通過 class 關鍵字定義。以 Person 為例,定義一個Person類...
    績重KF閱讀 3,971評論 0 13
  • 1.堅持背誦英語短文 2.跑步吧 3.紋個眉
    簡Jane1閱讀 228評論 0 0
  • 有時候你抱怨也會害了自己
    劉賢武閱讀 401評論 0 0