內容包含:
- 元類
- python 對象和類的綁定以及類方法,靜態方法
- python 子類調用父類方法總結
- python 方法解析順序MRQ
- python定制類和魔法方法
- 關于用法__slots__
- @property使用
- 修飾器
- 閉包
0、元類
元類就是類的類,所體現的終極思想就是一切皆對象。
關于深層次,待使用到在總結。
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
上面只是說明一個常用的子類調用父類場景,即調用父類的初始化函數。
直接運行以上代碼會出錯,因為盡管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。
python解析順序的規范化也是一個不斷發展的過程,主要有以下兩個階段:
- 2.2之前的經典類。經典類中多繼承方法解析采用深度優先從左到右搜索,即D-B-A-C-A,也就是說經典類中只采用A的show方法。
- 經典類對單層繼承沒有什么問題,但是對上述來說,我們顯然更愿意使用C的show方法,因為他是對A的具體化,但是經典類比并不能實現,于是在2.2中引入新式類(繼承自object),它仍然采用從左至右的深度優先遍歷,但是如果遍歷中出現重復的類,只保留最后一個。并且在定義類時就計算出該類的 MRO 并將其作為類的屬性。因此新式類可以直接通過 mro 屬性獲取類的 MRO。
舉個例子:
按照深度遍歷,其順序為 [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 會拋出異常。
舉例:
根據C3,計算過程為:
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中有效。
總結
嵌套函數可以訪問外層函數的局部變量,閉包則可以使函數脫離了函數本身的作用范圍,依然能夠訪問局部變量。