前言:
繼續前進
基礎回顧:
1、集合
集合有2個重要作用:關系測試(並集,差集,交集)和去重。
2、文件編碼
2.7上默認文件編碼是ASCII碼,因為不支持中文,就出了GB2312,在2.7上要支持中文就必須申明文件編碼以UTF-8的格式,UTF-8與GB2312的關系?
UTF-8是Unicode的擴展集合,Unicode包括全國地區的編碼,中國很多開始程序還是以GBK的格式,GBK向下兼容GB2312,Windows默認編碼是GBK。
Unicode為何要做出來?為了節省空間,存英文中文都是2個字節,本來我用ASCII碼存英文只用1個字節,但是現在用你2個,所以出了UTF-8 ,存英文是1個字節,中文統一3個字節。
假如1個文件是GBK編碼的,另外一個是UTF-8,如果它要讀這個文件,就要進行一個轉換,但是他們之間不能直接轉換,這個時候就涉及到了轉碼的問題。所以GBK轉換成UTF-8,語法是先decode 成Unicode,然后在encode成utf-8,見下圖:
在3.0中,默認編碼是Unicode,在2.7中要打印中文就得申明字符編碼 # -*- coding:utf-8 -*-
在3.0可以不寫,默認文件編碼就是Unicode,那么現在文件編碼就是Unicode,因為Unicode本來也支持中文,按2個字節存放,不需要轉換成utf-8,要想變成utf-8也得encode一下,如下所示:
a= '我是'.encode("utf-8")
。當然也可以申明字符編碼 # -*- coding:utf-8 -*- ,那么現在的文件編碼就是utf-8了。
3、函數
格式如下:
def func_name():
pass
位置參數,比如 arg1 和 arg2
def func_name(arg1,arg2): pass func_name(5,3)
5對應的是arg1 3對應的是arg2
關鍵參數,可以指定參數名,比如:
def func_name(arg1,arg2,arg3): pass func_name(1,2,arg3=5)
注意,關鍵參數不能寫在位置參數前面。
多個參數,就用到了*args,比如:
def func_name(arg1,arg2,*args): pass func_name(4,5,6,7,8) 那么打印出來效果 4,5,(6,7,8)
把后面非固定參數寫成了元祖
**kwargs ,打印出來是一個字典,例如
def func_name(arg1,arg2,arg3,*args,**kwargs): pass func_name(3,4,55,666,77,name=xiedi) 打印出來的結果 3,4,55,(666,77),{'name':'xiedi'}
4、局部變量和全局變量
總的來說,局部變量只對函數內生效,對函數外不起作用。
它涉及到一個作用域的問題,只是在函數里生效的,函數執行完畢,變量就沒了,作用域只允許在函數里改東西。
找變量的順序,先從內到外找變量。
如果非得改變它的作用域,就加一個global,但是不建議這么做,例如
age = 22 def change_age(): global age age = 24
5、返回值
返回值是因為我想得到函數的執行結果,它還代表着程序的結束
6、遞歸
遞歸相當於自己調自己,有幾個條件:
1、要有一個明確的結束條件。
因為遞歸相當於一層進入一層。
2、問題規模每遞歸一次都應該比上一次的問題規模有所減少。
3、效率低
7、高階函數
把一個函數當做另一個函數的參數傳進去,返回的時候要用到這個函數。
函數式編程是不需要變量的,純粹是一個映射關系,函數式編程是沒有副作用的,就是傳進去的數據是確定的,得出來的結果也是固定的。
8、文件操作
打開模式:
f = open
r,w,a
r是讀,w是寫,它會覆蓋,a是追加,r+是讀寫模式,寫到后面,追加的模式。
w+ 是寫讀,以寫的模式打開文件,如果文件存在,直接覆蓋。
a+追加寫讀
rb二進制模式打開,全部是字節格式
獲得文件句柄
操作:
f.
關閉:
f.close
接下來就是重點了,先來個裝飾器。顧名思義,裝飾一下。
一、裝飾器
從字面意思來看,器代表函數的意思,可以說,裝飾器本身就是函數,都是用def語法來定義的。
裝飾器:
定義:本質是函數,(裝飾其他函數)
為其他函數添加附加功能。
①先來看個簡單的,在沒學函數之前,我想給定義的函數打個日志,寫法如下:
def test1(): pass print('logging') def test2(): pass print('logging') #調用 test1() test2()
②接下來學了函數,我就把打日志定義成一個函數
# -*- coding: utf-8 -*- #Author: Leon xie def logger(): print('logging') def test1(): pass logger() def test2(): pass logger() #調用 test1() test2()
假設我寫的函數已經上線運行了,某一天,我有個需求,在這個里面新增一個功能,那怎么去做這個事?
最簡單的就是:挨個找到100個函數,加上去。但是問題是程序已經運行了,我剛才操作是修改我程序的源代碼,會有風險發生。
所以說,我要新增一個功能,不能夠修改函數的源代碼,函數一旦寫好了,原則上不能動源代碼了。
所以就有了下面的原則:
原則:
1、不能修改被裝飾函數的源代碼。
2、不能修改被裝飾的函數的調用方式。
裝飾器對於被裝飾函數是完全透明的。他沒有動我的源代碼,我該怎么調用運行就怎么運行。
舉例子:
定義1個函數
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie import time def test1(): time.sleep(3) print('in the test1') test1()
這個函數實現的就是 睡3秒然后打印
接下來寫個裝飾器:
用的時候只要在函數前面加一個“@函數名”, 即可
先睡3s然后打印,隨后統計了一個test1函數的運行時間。
第一:裝飾器本質就是一個函數
第二:裝飾器不修改被裝飾函數的源代碼和調用方式
第三:對於函數 test1來說,裝飾器完全不存在。
實現這個裝飾器的功能需要哪些知識呢?
1、函數即變量
2、高階函數
3、嵌套函數
最終:
高階函數+嵌套函數===>裝飾器
我們來復習一下變量:
變量是存在內存當中,比如我x=1,那么它是如何存在變量中呢?如下圖:
其實我要說的就是函數即變量。
變量調用加上變量名直接調用。
函數調用呢就是函數加個小括號。 test()
python解釋器中有一個概念叫做引用計數。
比如x=1 ,y=x,那么就是2次計數。
x和y相當於房間的門牌號,如果沒有門牌號了,那么內存里的1就會被清空。
匿名函數:
有的函數是不定義名字的。
例如:
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie #為了后面調用,我起了一個變量名,這個函數沒有名字 calc = lambda x:x*3 print(calc(3))
輸出結果
9
匿名函數沒有def起函數名。
小結:
函數就是一個變量,定義一個函數,就是把函數體付給了這個函數名。
變量特性是:內存回收。
既然說函數即變量那么下面這個函數如何存放呢?
def foo():
print('in the foo')
bar()
foo()
這個函數就回報錯,如下圖所示:
變量是先定義,后引用,函數也是一樣。
看下面這個例子:可以正常調用,只要在調用之前存在就可以調用
def foo():
print('in the foo')
bar()
def bar():
print('in the bar')
foo()
高階函數:(滿足下面2個條件)
a:把一個函數名當做實參傳給另外一個函數(在不修改被裝飾函數源代碼的情況下為其添加功能)
b:返回值中包含函數名
按照第一條原則寫一個
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie def bar(): print('in the bar') def test1(func): print(func) test1(bar)
輸出結果
<function bar at 0x0000000000A69268>
一段內存地址
上面相當於
func= bar 是一個門牌地址
func()是可以運行的,所以可以寫成這樣 類似於x=1 y=x
那么就有了下面的函數,附加一個計數的功能。
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie import time def bar(): time.sleep(3) print('in the bar') def test1(func): start_time =time.time() #運行一下func func() stop_time =time.time() #傳進來的運行時間不是test1 print("the func run time is %s" %(stop_time-start_time)) test1(bar)
輸出結果
in the bar
the func run time is 3.0002999305725098
這里在沒有修改源代碼的基礎上新增了一個計數的功能。不過我們知道裝飾器還有一個條件就是不改變調用方式。所以我們接着往下看
嵌套函數舉例:
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie def foo(): print('in the foo') def bar(): print('in the bar') bar() foo()
輸出結果
in the foo
in the bar
最后裝飾器效果:
#寫個裝飾器統計運行的時間 import time def timer(func): #timer(test1) test1 的內存地址給了func def deco(*args,**kwargs): start_time=time.time() func(*args,**kwargs) stop_time= time.time() print('the func run time is %s' %(stop_time-start_time)) return deco #返回了deco的內存地址 #嵌套函數寫成下面的形式 #def timer(): # def deco(): # pass @timer #test1= timer(test1) def test1(): time.sleep(1) print('in the test1') @timer #test2= timer(test2) def test2(name,age): time.sleep(1) print("test2:",name,age) test1() test2("xiedi",22)
輸出結果
in the test1
the func run time is 1.0
test2: xiedi 22
the func run time is 1.0
升級

#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie #需求:公司有網站,有很多頁面,模擬1個頁面1個函數,在之前情況誰都可以登錄沒有任何驗證 #100個頁面有20個登錄以后才能看到,就說給20個加入驗證功能。 import time user,passwd = 'xiedi','123' 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("--afterauthenticaion") 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 hoem page") return "from home" @auth def bbs(): print("welcome to bbs page") index() print(home()) bbs()
輸出結果
welcome to index page Username:xiedi Password:123 User has passed authentication welcome to hoem page --afterauthenticaion from home Username:
升級,加入新的判斷,登錄判斷
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie #需求:公司有網站,有很多頁面,模擬1個頁面1個函數,在之前情況誰都可以登錄沒有任何驗證 #100個頁面有20個登錄以后才能看到,就說給20個加入驗證功能。 #可不可以讓home認證的時候使用本地認證,bbs用遠程認證 import time user,passwd = 'xiedi','123' def auth(auth_type): print("auth func:",auth_type) def outer_wrapper(func): def wrapper(*args,**kwargs): print("wrapper func args:",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("--afterauthenticaion") return res else: exit("\033[31;1mInvalid username or password\033[0m") return wrapper return outer_wrapper def index(): print("welcome to index page") @auth(auth_type = "local") def home(): print("welcome to hoem page") return "from home" @auth(auth_type = "ldap") def bbs(): print("welcome to bbs page") index() home() bbs()
輸出結果
auth func: local auth func: ldap welcome to index page wrapper func args: () Username:xiedi Password:123 User has passed authentication welcome to hoem page --afterauthenticaion wrapper func args: () Username:xiedi Password:123 User has passed authentication welcome to bbs page --afterauthenticaion Process finished with exit code 0
二、迭代器和生成器
列表生成式:
我們到列表的定義,比如a=[1,2,3],我們還可以這么寫[i*2 for i in range(10)]
就是i在range(10)做一個for循環,然后乘以2得到一個列表。這個就叫做列表生成式。主要作用是使代碼更簡潔。
還可以在前面執行一個函數,如下圖:
生成器:
通過列表生成式,我們可以直接創建一個列表,但是,收到內存限制,列表容量肯定是有限的。
比如我創建100W元素的列表,我只用前面幾個,后面都不用,是不是浪費?
所以,如果列表元素可以按照某種算法推算出來,那我們就不必創建完整的list,從而節省大量的空間,在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
怎么去節省內存呢?循環列表是1個1個循環,列表從頭循環到尾的時候,我循環10次,循環到第5次的時候,后面的5個數據是已經准備好的。剩下的就很占用空間,那么我能不能搞個機制出來,我循環到第4次的時候,第4次的數據才剛生成。剩下的我不調用就沒有
這樣我就不需要提前把數據准備好了,省了空間了。
那么數據是怎么生成呢?有規律的做法
這樣就是沒循環一次乘以2了。你訪問它,它才會生成。
生成器,只有在調用時才會生成相應的數據。
生成器只記住當前這個位置,它也不知道前面,也不知道后面,前面用完了對它來講沒了,它只保存一個值。
1、只記錄當前位置
2、只有一個_next_()方法。
(i*i for i in range(10))這個語句高了一個生成器。
如果后面生成數據沒有規律那怎么辦?
再次,創建一個生成器:
用函數來做一個生成器。
斐波拉契數列,除第一個和第二個數外,任意一個數都可以由前面2個數相加得到
1,1,2,3,5,8,13,21,34。。。。
規則就是如此。
他是有一定規律就可以推導出來。
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie 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)
結果
1
1
2
3
5
8
13
21
34
55
分析:
a,b=1,2
a=1
b=2
t=(b,a+b)
所以這個時候
a=2 b=3了
把上面函數改成生成器,1步即可
變成了一個生成器。
這樣做的好處在哪呢?
之前,我們調用函數,如果函數在執行時候需要花費10分鍾,那么我接下來的操作就要在10分鍾后進行。程序就卡在這了
現在這個呢?現在函數變成生成器之后,我直接調用一下next,它就在里面循環一次,停在這了,程序就跑到外面了,我可以干點別的事在回去。例如:
這樣就把函數做成了一個生成器。
接下來有個問題,就是如果我取得數大於10,用next 方法取不到就會報一個異常。如何解決呢?
就是要抓住這個異常: try一下
g = fib(6) while True: try: x = next(g) print('g:',x) except StopIteration as e: print('Generator return value:',e.value) break
yield是保存了函數的中斷狀態,返回當前狀態的值,函數停在這了,一會還可以回來。
工作中如何使用呢?
我們可以通過yield來實現單線程的情況下實現並發運算的效果
#!/usr/bin/env python # -*- coding: utf-8 -*- #Author: Leon xie import time #典型的生產者消費者模型 def consumer(name): print("%s 准備吃包子啦!!" %name) while True: baozi = yield print("包子[%s]來了,被[%s]吃了" %(baozi,name)) c = consumer("xiedi") c.__next__() b1 = "韭菜餡" c.send(b1) #c.__next__() def producer(name): c = consumer('A') c2 = consumer('B') c.__next__() c2.__next__() print("老子開始准備做包子了!") for i in range(10): time.sleep(1) print("做了2個包子") c.send(i)
producer("dd")
輸出結果
xiedi 准備吃包子啦!!
包子[韭菜餡]來了,被[xiedi]吃了
A 准備吃包子啦!!
B 准備吃包子啦!!
老子開始准備做包子了!
做了2個包子
包子[0]來了,被[A]吃了
做了2個包子
包子[1]來了,被[A]吃了
做了2個包子
包子[2]來了,被[A]吃了
做了2個包子
包子[3]來了,被[A]吃了
做了2個包子
包子[4]來了,被[A]吃了
做了2個包子
包子[5]來了,被[A]吃了
做了2個包子
包子[6]來了,被[A]吃了
做了2個包子
包子[7]來了,被[A]吃了
做了2個包子
包子[8]來了,被[A]吃了
做了2個包子
包子[9]來了,被[A]吃了
迭代器:
可直接作用於for循環的數據類型有以下幾種:
一類是集合數據類型,如list,tuple ,dict ,set ,str等。
一類是generator,包括生成器和帶yield的 generator function。
可以使用isinstance()判斷一個對象是否是Iterable對象。
可以被next()函數調用並不斷返回下一個值得對象統稱為迭代器。
可以直接作用於for循環的對象統稱為可迭代對象:Iterable 。
三、軟件目錄結構規范
目錄結構目的
- 可讀性高: 不熟悉這個項目的代碼的人,一眼就能看懂目錄結構,知道程序啟動腳本是哪個,測試目錄在哪兒,配置文件在哪兒等等。從而非常快速的了解這個項目。
- 可維護性高: 定義好組織規則后,維護者就能很明確地知道,新增的哪個文件和代碼應該放在什么目錄之下。這個好處是,隨着時間的推移,代碼/配置的規模增加,項目結構不會混亂,仍然能夠組織良好。
假設你的項目名為foo, 我比較建議的最方便快捷目錄結構這樣就足夠了:
Foo/ 項目名
|-- bin/ 可執行放的目錄
| |-- foo 啟動foo調用main
|
|-- foo/ 主程序目錄
| |-- tests/ 測試的,程序的主邏輯,測試代碼
| | |-- __init__.py
| | |-- test_main.py
| |
| |-- __init__.py 必須有,這是一個空文件
| |-- main.py 程序主入口,啟動foo去調用main
|
|-- docs/ 文檔
| |-- conf.py
| |-- abc.rst
|
|-- setup.py 安裝部署的腳步
|-- requirements.txt 依賴關系,比如依賴安裝mysql
|-- README
---conf 配置文件目錄
簡要解釋一下:
bin/
: 存放項目的一些可執行文件,當然你可以起名script/
之類的也行。foo/
: 存放項目的所有源代碼。(1) 源代碼中的所有模塊、包都應該放在此目錄。不要置於頂層目錄。(2) 其子目錄tests/
存放單元測試代碼; (3) 程序的入口最好命名為main.py
。docs/
: 存放一些文檔。setup.py
: 安裝、部署、打包的腳本。requirements.txt
: 存放軟件依賴的外部Python包列表。README
: 項目說明文件。- conf:配置文件目錄
關於README的內容
這個我覺得是每個項目都應該有的一個文件,目的是能簡要描述該項目的信息,讓讀者快速了解這個項目。
它需要說明以下幾個事項:
- 軟件定位,軟件的基本功能。
- 運行代碼的方法: 安裝環境、啟動命令等。
- 簡要的使用說明。
- 代碼目錄結構說明,更詳細點可以說明軟件的基本原理。
- 常見問題說明。
我覺得有以上幾點是比較好的一個README
。在軟件開發初期,由於開發過程中以上內容可能不明確或者發生變化,並不是一定要在一開始就將所有信息都補全。但是在項目完結的時候,是需要撰寫這樣的一個文檔的。
可參考:https://github.com/antirez/redis#what-is-redi