Python入門學習D3-函式、全域性與區域性變數、高階函式、
本節內容
1. 函式基本語法及特性
2. 引數與區域性變數
3. 返回值
巢狀函式
4.遞迴
5.匿名函式
6.函數語言程式設計介紹
7.高階函式
8.內建函式
溫故知新
1. 集合
主要作用:
- 去重
- 關係測試, 交集\差集\並集\反向(對稱)差集
1234567891011121314151617181920 | >>> a = { 1 , 2 , 3 , 4 } >>> b = { 3 , 4 , 5 , 6 } >>> a { 1 , 2 , 3 , 4 } >>> type (a) < class 'set' > >>> a.symmetric_difference(b) { 1 , 2 , 5 , 6 } >>> b.symmetric_difference(a) { 1 , 2 , 5 , 6 } >>> >>> >>> a.difference(b) { 1 , 2 } >>> a.union(b) { 1 , 2 , 3 , 4 , 5 , 6 } >>> a.issu a.issubset( a.issuperset( >>> a.issubset(b) False |
2. 元組
只讀列表,只有count, index 2 個方法
作用:如果一些資料不想被人修改, 可以存成元組,比如身份證列表
3. 字典
key-value對
- 特性:
- 無順序
- 去重
- 查詢速度快,比列表快多了
- 比list佔用記憶體多
為什麼會查詢速度會快呢?因為他是hash型別的,那什麼是hash呢?
雜湊演算法將任意長度的二進位制值對映為較短的固定長度的二進位制值,這個小的二進位制值稱為雜湊值。雜湊值是一段資料唯一且極其緊湊的數值表示形式。如果雜湊一段明文而且哪怕只更改該段落的一個字母,隨後的雜湊都將產生不同的值。要找到雜湊為同一個值的兩個不同的輸入,在計算上是不可能的,所以資料的雜湊值可以檢驗資料的完整性。一般用於快速查詢和加密演算法
dict會把所有的key變成hash 表,然後將這個表進行排序,這樣,你通過data[key]去查data字典中一個key的時候,python會先把這個key hash成一個數字,然後拿這個數字到hash表中看沒有這個數字, 如果有,拿到這個key在hash表中的索引,拿到這個索引去與此key對應的value的記憶體地址那取值就可以了。
上面依然沒回答這樣做查詢一個數據為什麼會比列表快,對不對? 呵呵,等我課上揭曉。
4. 字元編碼
先說python2
- py2裡預設編碼是ascii
- 檔案開頭那個編碼宣告是告訴解釋這個程式碼的程式 以什麼編碼格式 把這段程式碼讀入到記憶體,因為到了記憶體裡,這段程式碼其實是以bytes二進位制格式存的,不過即使是2進位制流,也可以按不同的編碼格式轉成2進位制流,你懂麼?
- 如果在檔案頭聲明瞭#_*_coding:utf-8*_,就可以寫中文了, 不宣告的話,python在處理這段程式碼時按ascii,顯然會出錯, 加了這個聲明後,裡面的程式碼就全是utf-8格式了
- 在有#_*_coding:utf-8*_的情況下,你在宣告變數如果寫成name=u"大保健",那這個字元就是unicode格式,不加這個u,那你宣告的字串就是utf-8格式
- utf-8 to gbk怎麼轉,utf8先decode成unicode,再encode成gbk
再說python3
- py3裡預設檔案編碼就是utf-8,所以可以直接寫中文,也不需要檔案頭宣告編碼了,乾的漂亮
- 你宣告的變數預設是unicode編碼,不是utf-8, 因為預設即是unicode了(不像在py2裡,你想直接宣告成unicode還得在變數前加個u), 此時你想轉成gbk的話,直接your_str.encode("gbk")即可以
- 但py3裡,你在your_str.encode("gbk")時,感覺好像還加了一個動作,就是就是encode的資料變成了bytes裡,我擦,這是怎麼個情況,因為在py3裡,str and bytes做了明確的區分,你可以理解為bytes就是2進位制流,你會說,我看到的不是010101這樣的2進位制呀, 那是因為python為了讓你能對資料進行操作而在記憶體級別又幫你做了一層封裝,否則讓你直接看到一堆2進位制,你能看出哪個字元對應哪段2進位制麼?什麼?自己換算,得了吧,你連超過2位數的數字加減運算都費勁,還還是省省心吧。
- 那你說,在py2裡好像也有bytes呀,是的,不過py2裡的bytes只是對str做了個別名(python2裡的str就是bytes, py3裡的str是unicode),沒有像py3一樣給你顯示的多出來一層封裝,但其實其內部還是封裝了的。 這麼講吧, 無論是2還是三, 從硬碟到記憶體,資料格式都是 010101二進位制到-->b'\xe4\xbd\xa0\xe5\xa5\xbd' bytes型別-->按照指定編碼轉成你能看懂的文字
編碼應用比較多的場景應該是爬蟲了,網際網路上很多網站用的編碼格式很雜,雖然整體趨向都變成utf-8,但現在還是很雜,所以爬網頁時就需要你進行各種編碼的轉換,不過生活正在變美好,期待一個不需要轉碼的世界。
最後,編碼is a piece of fucking shit, noboby likes it.
1.函式基本語法及特性
背景提要
現在老闆讓你寫一個監控程式,監控伺服器的系統狀況,當cpu\memory\disk等指標的使用量超過閥值時即發郵件報警,你掏空了所有的知識量,寫出了以下程式碼
123456789101112131415161718 | while True : if cpu利用率 > 90 % : #傳送郵件提醒 連線郵箱伺服器 傳送郵件 關閉連線 if 硬碟使用空間 > 90 % : #傳送郵件提醒 連線郵箱伺服器 傳送郵件 關閉連線 if 記憶體佔用 > 80 % : #傳送郵件提醒 連線郵箱伺服器 傳送郵件 關閉連線 |
上面的程式碼實現了功能,但即使是鄰居老王也看出了端倪,老王親切的摸了下你家兒子的臉蛋,說,你這個重複程式碼太多了,每次報警都要重寫一段發郵件的程式碼,太low了,這樣幹存在2個問題:
- 程式碼重複過多,一個勁的copy and paste不符合高階程式設計師的氣質
- 如果日後需要修改發郵件的這段程式碼,比如加入群發功能,那你就需要在所有用到這段程式碼的地方都修改一遍
你覺得老王說的對,你也不想寫重複程式碼,但又不知道怎麼搞,老王好像看出了你的心思,此時他抱起你兒子,笑著說,其實很簡單,只需要把重複的程式碼提取出來,放在一個公共的地方,起個名字,以後誰想用這段程式碼,就通過這個名字呼叫就行了,如下
12345678910111213141516 | def 傳送郵件(內容) #傳送郵件提醒 連線郵箱伺服器 傳送郵件 關閉連線 while True : if cpu利用率 > 90 % : 傳送郵件( 'CPU報警' ) if 硬碟使用空間 > 90 % : 傳送郵件( '硬碟報警' ) if 記憶體佔用 > 80 % : 傳送郵件( '記憶體報警' ) |
你看著老王寫的程式碼,氣勢恢巨集、磅礴大氣,程式碼裡透露著一股內斂的傲氣,心想,老王這個人真是不一般,突然對他的背景更感興趣了,問老王,這些花式玩法你都是怎麼知道的? 老王親了一口你兒子,捋了捋不存在的鬍子,淡淡的講,“老夫,年少時,師從京西沙河淫魔銀角大王 ”, 你一聽“銀角大王”這幾個字,不由的嬌軀一震,心想,真nb,怪不得程式碼寫的這麼6, 這“銀角大王”當年在江湖上可是數得著的響噹噹的名字,只可惜後期縱慾過度,卒於公元2016年, 真是可惜了,只留下其哥哥孤守當年兄弟倆一起打下來的江山。 此時你看著的老王離開的身影,感覺你兒子跟他越來越像了。。。
函式是什麼?
函式一詞來源於數學,但程式設計中的「函式」概念,與數學中的函式是有很大不同的,具體區別,我們後面會講,程式設計中的函式在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子過程或子程式),在Pascal中叫做procedure(過程)和function,在C中只有function,在Java裡面叫做method。
定義: 函式是指將一組語句的集合通過一個名字(函式名)封裝起來,要想執行這個函式,只需呼叫其函式名即可
特性:
- 減少重複程式碼
- 使程式變的可擴充套件
- 使程式變得易維護
語法定義
1234 | def sayhi(): #函式名 print ( "Hello, I'm nobody!" ) sayhi() #呼叫函式 |
可以帶引數
12345678910111213 | #下面這段程式碼 a,b = 5 , 8 c = a * * b print (c) #改成用函式寫 def calc(x,y): res = x * * y return res #返回函式執行結果 c = calc(a,b) #結果賦值給c變數 print (c) |
2.函式引數與區域性變數
形參變數只有在被呼叫時才分配記憶體單元,在呼叫結束時,即刻釋放所分配的記憶體單元。因此,形參只在函式內部有效。函式呼叫結束返回主呼叫函式後則不能再使用該形參變數
實參可以是常量、變數、表示式、函式等,無論實參是何種型別的量,在進行函式呼叫時,它們都必須有確定的值,以便把這些值傳送給形參。因此應預先用賦值,輸入等辦法使引數獲得確定值
預設引數
看下面程式碼
12345678910 | def stu_register(name,age,country,course): print ( "----註冊學生資訊------" ) print ( "姓名:" ,name) print ( "age:" ,age) print ( "國籍:" ,country) print ( "課程:" ,course) stu_register( "王山炮" , 22 , "CN" , "python_devops" ) stu_register( "張叫春" , 21 , "CN" , "linux" ) stu_register( "劉老根" , 25 , "CN" , "linux" ) |
發現 country 這個引數 基本都 是"CN", 就像我們在網站上註冊使用者,像國籍這種資訊,你不填寫,預設就會是 中國, 這就是通過預設引數實現的,把country變成預設引數非常簡單
1 | def stu_register(name,age,course,country = "CN" ): |
這樣,這個引數在呼叫時不指定,那預設就是CN,指定了的話,就用你指定的值。
另外,你可能注意到了,在把country變成預設引數後,我同時把它的位置移到了最後面,為什麼呢?
關鍵引數
正常情況下,給函式傳引數要按順序,不想按順序就可以用關鍵引數,只需指定引數名即可,但記住一個要求就是,關鍵引數必須放在位置引數之後。
1 | stu_register(age = 22 ,name = 'alex' ,course = "python" ,) |
非固定引數
若你的函式在定義時不確定使用者想傳入多少個引數,就可以使用非固定引數
12345678910 | def stu_register(name,age, * args): # *args 會把多傳入的引數變成一個元組形式 print (name,age,args) stu_register( "Alex" , 22 ) #輸出 #Alex 22 () #後面這個()就是args,只是因為沒傳值,所以為空 stu_register( "Jack" , 32 , "CN" , "Python" ) #輸出 # Jack 32 ('CN', 'Python') |
還可以有一個**kwargs
12345678910 | def stu_register(name,age, * args, * * kwargs): # *kwargs 會把多傳入的引數變成一個dict形式 print (name,age,args,kwargs) stu_register( "Alex" , 22 ) #輸出 #Alex 22 () {}#後面這個{}就是kwargs,只是因為沒傳值,所以為空 stu_register( "Jack" , 32 , "CN" , "Python" ,sex = "Male" ,province = "ShanDong" ) #輸出 # Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'} |
區域性變數
1234567891011 | name = "Alex Li" def change_name(name): print ( "before change:" ,name) name = "金角大王,一個有Tesla的男人" print ( "after change" , name) change_name(name) print ( "在外面看看name改了麼?" ,name) |
輸出
123 | before change: Alex Li after change 金角大王,一個有Tesla的男人 在外面看看name改了麼? Alex Li |
全域性與區域性變數
在子程式中定義的變數稱為區域性變數,在程式的一開始定義的變數稱為全域性變數。全域性變數作用域是整個程式,區域性變數作用域是定義該變數的子程式。當全域性變數與區域性變數同名時:在定義區域性變數的子程式內,區域性變數起作用;在其它地方全域性變數起作用。3.返回值
要想獲取函式的執行結果,就可以用return語句把結果返回
注意:
- 函式在執行過程中只要遇到return語句,就會停止執行並返回結果,so 也可以理解為 return 語句代表著函式的結束
- 如果未在函式中指定return,那這個函式的返回值為None
強行插入知識點: 巢狀函式
看上面的標題的意思是,函式還能套函式?of course
123456789101112131415 | name = "Alex" def change_name(): name = "Alex2" def change_name2(): name = "Alex3" print ( "第3層列印" ,name) change_name2() #呼叫內層函式 print ( "第2層列印" ,name) change_name() print ( "最外層列印" ,name) |
此時,在最外層呼叫change_name2()會出現什麼效果?
沒錯, 出錯了, 為什麼呢?
巢狀函式的用法會了,但它有什麼用呢?下節課揭曉。。。
4. 遞迴
在函式內部,可以呼叫其他函式。如果一個函式在內部呼叫自身本身,這個函式就是遞迴函式。
12345678910111213 | def calc(n): print (n) if int (n / 2 ) = = 0 : return n return calc( int (n / 2 )) calc( 10 ) 輸出: 10 5 2 1 |
遞迴特性:
1. 必須有一個明確的結束條件
2. 每次進入更深一層遞迴時,問題規模相比上次遞迴都應有所減少
3. 遞迴效率不高,遞迴層次過多會導致棧溢位(在計算機中,函式呼叫是通過棧(stack)這種資料結構實現的,每當進入一個函式呼叫,棧就會加一層棧幀,每當函式返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞迴呼叫的次數過多,會導致棧溢位)
堆疊掃盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
遞迴函式實際應用案例,二分查詢
123456789101112131415161718192021222324 | data = [ 1 , 3 , 6 , 7 , 9 , 12 , 14 , 16 , 17 , 18 , 20 , 21 , 22 , 23 , 30 , 32 , 33 , 35 ] def binary_search(dataset,find_num): print (dataset) if len (dataset) > 1 : mid = int ( len (dataset) / 2 ) if dataset[mid] = = find_num: #find it print ( "找到數字" ,dataset[mid]) elif dataset[mid] > find_num : # 找的數在mid左面 print ( "\033[31;1m找的數在mid[%s]左面\033[0m" % dataset[mid]) return binary_search(dataset[ 0 :mid], find_num) else : # 找的數在mid右面 print ( "\033[32;1m找的數在mid[%s]右面\033[0m" % dataset[mid]) return binary_search(dataset[mid + 1 :],find_num) else : if dataset[ 0 ] = = find_num: #find it print ( "找到數字啦" ,dataset[ 0 ]) else : print ( "沒的分了,要找的數字[%s]不在列表裡" % find_num) binary_search(data, 66 ) |
5. 匿名函式
匿名函式就是不需要顯式的指定函式
12345678 | #這段程式碼 def calc(n): return n * * n print (calc( 10 )) #換成匿名函式 calc = lambda n:n * * n print (calc( 10 )) |
你也許會說,用上這個東西沒感覺有毛方便呀, 。。。。呵呵,如果是這麼用,確實沒毛線改進,不過匿名函式主要是和其它函式搭配使用的呢,如下
123 | res = map ( lambda x:x * * 2 ,[ 1 , 5 , 7 , 4 , 8 ]) for i in res: print (i) |
輸出
1
25
49
16
64
6.函數語言程式設計介紹
函式是Python內建支援的一種封裝,我們通過把大段程式碼拆成函式,通過一層一層的函式呼叫,就可以把複雜任務分解成簡單的任務,這種分解可以稱之為面向過程的程式設計。函式就是面向過程的程式設計的基本單元。
函數語言程式設計中的函式這個術語不是指計算機中的函式(實際上是Subroutine),而是指數學中的函式,即自變數的對映。也就是說一個函式的值僅決定於函式引數的值,不依賴其他狀態。比如sqrt(x)函式計算x的平方根,只要x不變,不論什麼時候呼叫,呼叫幾次,值都是不變的。
Python對函數語言程式設計提供部分支援。由於Python允許使用變數,因此,Python不是純函數語言程式設計語言。
一、定義
簡單說,"函數語言程式設計"是一種"程式設計正規化"(programming paradigm),也就是如何編寫程式的方法論。
主要思想是把運算過程儘量寫成一系列巢狀的函式呼叫。舉例來說,現在有這樣一個數學表示式:
(1 + 2) * 3 - 4
傳統的程序式程式設計,可能這樣寫:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函數語言程式設計要求使用函式,我們可以把運算過程定義為不同的函式,然後寫成下面這樣:
var result = subtract(multiply(add(1,2), 3), 4);
這段程式碼再演進以下,可以變成這樣
add(1,2).multiply(3).subtract(4)
這基本就是自然語言的表達了。再看下面的程式碼,大家應該一眼就能明白它的意思吧:
merge([1,2],[3,4]).sort().search("2")
因此,函數語言程式設計的程式碼更容易理解。
要想學好函數語言程式設計,不要玩py,玩Erlang,Haskell, 好了,我只會這麼多了。。。
7.高階函式
變數可以指向函式,函式的引數能接收變數,那麼一個函式就可以接收另一個函式作為引數,這種函式就稱之為高階函式。
123456 | def add(x,y,f): return f(x) + f(y) res = add( 3 , - 6 , abs ) print (res) |
8. 內建引數
內建引數詳解 https://docs.python.org/3/library/functions.html?highlight=built#ascii
#compile f = open("函式遞迴.py") data =compile(f.read(),'','exec') exec(data) #print msg = "又回到最初的起點" f = open("tofile","w") print(msg,"記憶中你青澀的臉",sep="|",end="",file=f) # #slice # a = range(20) # pattern = slice(3,8,2) # for i in a[pattern]: #等於a[3:8:2] # print(i) # # #memoryview #usage: #>>> memoryview(b'abcd') #<memory at 0x104069648> #在進行切片並賦值資料時,不需要重新copy原列表資料,可以直接對映原資料記憶體, import time for n in (100000, 200000, 300000, 400000): data = b'x'*n start = time.time() b = data while b: b = b[1:] print('bytes', n, time.time()-start) for n in (100000, 200000, 300000, 400000): data = b'x'*n start = time.time() b = memoryview(data) while b: b = b[1:] print('memoryview', n, time.time()-start)
本節作業
有以下員工資訊表
當然此表你在檔案儲存時可以這樣表示
1 | 1 ,Alex Li, 22 , 13651054608 ,IT, 2013 - 04 - 01 |
現需要對這個員工資訊檔案,實現增刪改查操作
- 可進行模糊查詢,語法至少支援下面3種:
- select name,age from staff_table where age > 22
- select * from staff_table where dept = "IT"
- select * from staff_table where enroll_date like "2013"
- 查到的資訊,列印後,最後面還要顯示查到的條數
- 可建立新員工紀錄,以phone做唯一鍵,staff_id需自增
- 可刪除指定員工資訊紀錄,輸入員工id,即可刪除
- 可修改員工資訊,語法如下:
- UPDATE staff_table SET dept="Market" WHERE where dept = "IT"
注意:以上需求,要充分使用函式,請盡你的最大限度來減少重複程式碼!