裝飾器是一個用來裝飾其餘函數的工具,即爲其餘函數添加附加功能,其本質就是函數。html
裝飾器須要遵循的如下兩個原則:python
一、若要新增一個功能,就不能再修改源代碼,即不能再修改被裝飾函數的源代碼。算法
二、不能修改被裝飾函數的調用方式。數組
實現裝飾器的知識儲備:app
一、函數即變量。ssh
二、高階函數。函數
三、函數嵌套。工具
即要想實現裝飾器,首先應對以上三條知識有必定了解。單元測試
首先咱們來簡單介紹一下函數即變量這個概念。咱們舉一個簡單的例子來講明:學習
def bar(): print('in the bar') def foo(): print('in the foo') bar() foo()
代碼運行結果以下:
如今你們想象一下,若是把函數bar和函數foo的位置調換一下,還能運行嗎?或者說運行結果仍是同樣嗎?
咱們試驗一下:
def foo(): print('in the foo') bar() def bar(): print('in the bar') foo()
運行結果仍是同樣。
這說明在python中函數可看作是一個變量,定義一個函數與定義一個變量x並沒有二致,因此上述代碼調換位置以後仍是能夠運行的。
在寫一個裝飾器函數時,咱們也會用到高階函數這個概念,它的主要思想就是把一個函數名當作實參傳給另外一個函數,以實如今不修改要裝飾程序的源代碼的狀況下爲其添加新功能的做用。下面咱們舉一個簡單的例子進行說明。
def bar():
print('in the bar')
def test1(func):
print(func) #打印函數func的內存地址
func() #調用函數func
return (func) #返回函數func的內存地址
print(test1(bar)) #將函數bar做爲實參傳給func
上例經過函數test1來調用函數bar,咱們能夠看到代碼運行結果以下:
上面是一個簡單的高階函數的例子,你們能夠簡單看一下結構,下面咱們介紹一個具有簡單功能的高階函數的代碼,以下:
import time def bar(): time.sleep(3) #bar函數實現延遲三秒輸出的功能 print('in the bar') def test(func): start_time=time.time() func() end_time=time.time() print('the func run time is %s'%(end_time-start_time)) #爲func函數添加一個計算函數執行時間的功能 return func #返回函數func的內存地址 bar=test(bar) bar()
代碼執行結果以下:
咱們能夠看到經過高階函數test,咱們實現了爲bar函數添加計算函數執行時間的功能,而且,經過把函數賦值給變量bar,咱們實現了不改變原函數調用方式的前提下增長新功能的做用。
這既是裝飾器的核心所在。
在這一節咱們介紹嵌套函數,函數的嵌套在裝飾器中也發揮着很是重要的做用,示例以下:
def foo(): print('in the foo') def bar(): print('in the bar') bar() foo()
代碼執行結果以下:
經過上述例子咱們能夠直觀的看到局部做用域和全局做用域的訪問順序:先外后里。
經過以前的熱身,如今咱們本身動手來寫一個裝飾器。
假設剛開始咱們有兩個函數test1和test2,咱們想經過裝飾器來添加一個計算函數運行時間的新功能,代碼以下:
import time def timmer(func): def deco(*args,**kwargs): #這裏用到參數組*args和**kwargs是針對可能出現func函數的形參個數不固定的狀況而設定的 start_time=time.time() func(*args,**kwargs) end_time=time.time() print('the func run time is %s'%(end_time-start_time)) #計算函數func實際運行時間 return deco @timmer #test1=timmer(test1) def test1(): time.sleep(3) print('in the test1') @timmer #test2=timmer(test2) def test2(name): print('test2:',name) test1() #因以前@timmer操做,這裏實際調用的是deco test2('abcd') #同理,這裏調用的也是deco
這裏,你們須要格外注意的是爲了簡潔,python中能夠用@timmer來代替test1=timmer(test1),也就是上面高階函數講到的不改變函數調用方式。
代碼運行結果以下:
咱們能夠看到裝飾器圓滿完成了「裝飾」的功能。
學習完上述簡單的裝飾器程序,咱們再挑戰一下高難度的——咱們用裝飾器來寫一個爲某些網站設置登錄界面的代碼,具體以下:
user,passwd='kobe','0824' #初始化用戶名和密碼 def auth(func): def wrapper(*args,**kwargs): username=input('username:').strip() password=input('password:').strip() if user==username and passwd==password: #驗證用戶名和密碼是否正確 print('\033[32;1muser has passed authentication\033[0m') res=func(*args,**kwargs) print('--after authentication') return res else: exit('\033[31;1minvalid username or password\033[0m') #錯誤提示 return wrapper def index(): print('welcome to index page') @auth def home(): print('welcome to home page') return 'from home' @auth def bbs(): print('welcome to bbs page') index() print(home()) bbs()
代碼運行結果以下:
首先進入一個home用戶登陸界面,用於輸入用戶名和密碼:
若輸入錯誤的用戶名和密碼,則有:
若輸入正確的用戶名和密碼,則會顯示歡迎登陸home界面,並進入登陸下一個bbs界面時所需輸入用戶名和密碼的界面,以下:
咱們再輸入以前的正確的用戶名和密碼,則會進入歡迎登陸bbs界面的相關信息,以下:
以上咱們就用裝飾器實現了爲某些網站設置登錄界面的功能。
在學習迭代器前我先向你們介紹一下列表生成式。
假如咱們想要輸出0到20間全部的偶數,咱們能夠用列表生成式以下:
>>>a=[i*2 for i in range(11)]
>>>a
[0,2,4,8,10,12,14,16,18,20]
咱們能夠看到,經過列表生成式咱們能夠直接建立一個列表。可是,受到內存限制,列表的容量確定是有限的。並且建立一個包含100個元素的列表不只佔用很大的存儲空間,並且若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間就白白浪費啦。因此,若是列表元素能夠按照算法推算出來,name咱們是否能夠在循環過程當中不斷推算出後續的元素呢?這樣的話就不用建立一個完整的list,從而節省大量的空間。在python中,這種一邊循環一邊計算的機制稱爲生成器(generator)。
若咱們想用生成器來輸出0到20間全部的偶數,咱們將上述列表生成式的中括號 [ ] 換成小括號 ( ) 便可,代碼以下:
>>>a=(i*2 for i in range(11)) >>>a
<generator object <genexpr> at 0x0000027EAA3FE410>
咱們能夠看到,當咱們按照以前列表生成式的方式來輸出元素時,卻提示建立了一個生成器generator——<generator object <genexpr> at 0x0000027EAA3FE410>。
那麼,若咱們想要輸出生成器中的元素時應該怎麼辦呢?這裏就要用到next ( ) 方法,以下:
>>>a=(i*2 for i in range(11)) >>>a <generator object <genexpr> at 0x0000027EAA3FE410> >>>a.__next__() 0 >>>a.__next__() 2 >>>a.__next__() 4
從上述的調用過程咱們能夠看到,只有在調用時纔會生成相應的數據,,這樣也就能節省大量的空間。並且注意的是,咱們在調用生成器中的元素用到next()方法時,只能不斷地向下一級生成數據而不能返回上一級。
以上咱們只是舉了一個簡單的生成偶數的例子,若要推算的算法比較複雜,還能夠用到函數。好比咱們想要生成一個Fibonacci數列,
注:Fibonacci數列的定義可參見:https://baike.baidu.com/item/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97
咱們就能夠用函數來完成,代碼以下:
def fib(max): n,a,b=0,0,1 while n<max: print(b) a,b=b,a+b n=n+1 return 'done' fib(10)
上述代碼能夠輸出前十個fibonacci數列的元素,以下:
那若是咱們想以生成器的形式來輸出前十個fibonacci數列的元素應該怎麼作呢?
其實很簡單,就是把上述代碼函數中的 print(b)換成 yield b,並用next()方法來輸出元素便可,代碼以下:
def fib(max): n,a,b=0,0,1 while n<max: yield b a,b=b,a+b n=n+1 return 'done' f=fib(10) print(f.__next__()) print(f.__next__()) print('-----------') for i in f: print(i)
代碼運行結果以下:
可直接做用於for循環的有如下幾種:
一是集合數據類型,如列表list,元組tuple,字典dict,集合set,字符串str等。
二是generator,包括生成器和帶yield的generator function。
這些能夠直接做用於for循環的對象統稱爲可迭代對象:Iterrable。
咱們可使用isinstance()判斷一個對象是否爲Iterable對象,例如:
>>>from collections import Iterable >>>isinstance('abc',Iterable) #判斷字符串'abc'是否爲Iterable對象 True >>>isinstance({},Iterable) #判斷{}是否爲Iterable對象 True
而生成器不但能夠做用於for循環,還能夠被next函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤,即表示沒法返回下一個值了。
咱們稱可被next()方法調用並不斷返回下一個值的對象統稱爲迭代器:Iterator。
以列表爲例,咱們能夠用dir()方法來查看某對象可以使用的方法:
>>> a=[1,2,3] >>> dir(a) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__',
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
咱們能夠看到列表a不能使用next()方法,故不是迭代器。
咱們也能夠用剛纔的isinstance方法來驗證:
>>> a=[1,2,3] >>> dir(a) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] >>> from collections import Iterator >>> isinstance(a,Iterator) False
咱們能夠看到列表並非一個迭代器。
經過上節的學習,生成器可使用next()方法來不斷調用下一個元素,那生成器應該就是一個迭代器,驗證以下:
>>> from collections import Iterator >>> isinstance((x for x in range(5)),Iterator) True
那麼,咱們一樣也可使用其餘方法來使非迭代器對象轉化爲迭代器對象,這裏就須要用到iter函數,示例以下:
>>> a=[1,2,3] >>> b=iter(a) >>> dir(b) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__',
'__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__'] >>> isinstance(b,Iterator) True
迭代器的好處是它能夠表示一個無限大的數據流,如全體天然數,而list是不可能存儲全體天然數的。
關於生成器和迭代器總結以下:
一、凡是能夠做用於for循環的對象都是Iterable類型。
二、凡是能夠做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列。
三、集合數據類型如list,dict,str,等都是Iterable類型但不是Iterator類型,不過能夠經過iter函數得到一個Iterator類型對象。
python解釋器有許多可用的內置函數和類型,它們按字母順序排列以下:
它們的用法可參見https://docs.python.org/3/library/functions.html?highlight=built#ascii,在這裏咱們就很少作介紹了。
完成一個項目時,設計好軟件目錄結構規範是很是重要的。目錄結構規範化能夠更好地控制程序結構,讓程序具備更高的可讀性,而且可維護性更高。
下面介紹一下個人老師教給個人一種目錄組織方式,以下:
假設咱們要完成的項目名爲foo,最方便快捷的目錄結構能夠寫爲:
Foo/ |-- bin/ | |-- foo | |-- foo/ | |-- tests/ | | |-- __init__.py | | |-- test_main.py | | | |-- __init__.py | |-- main.py | |-- docs/ | |-- conf.py | |-- abc.rst | |-- setup.py |-- requirements.txt |-- README
下面簡單解釋一下:
一、bin/
: 存放項目的一些可執行文件,固然你能夠起名script/
之類的也行。
二、foo/
: 存放項目的全部源代碼。(1) 源代碼中的全部模塊、包都應該放在此目錄。不要置於頂層目錄。(2) 其子目錄tests/
存放單元測試代碼; (3) 程序的入口最好命名爲main.py
。
三、docs/
: 存放一些文檔。
四、setup.py
: 安裝、部署、打包的腳本。
五、requirements.txt
: 存放軟件依賴的外部Python包列表。
六、README
: 項目說明文件。
關於readme內容,咱們須要注意的是,它應該包含如下五方面內容:
一、軟件定位,軟件的基本功能。
二、運行代碼的方法,安裝環境,啓動命令等。
三、簡要的使用說明。
四、代碼目錄結構說明,軟件的基本原理。
五、常見問題說明。