Python入門筆記(程式碼中成長)

南沐ヾ發表於2020-10-21

第一章 基礎部分

1、輸入輸出

程式碼:

name = input('你叫什麼名字:')
print ('我將稱呼你:'+name)

2、除法運算

執行一下程式。 讓我們來看一下輸出的結果,並做一下對應的分析:
當我們使用 / 進行除法時,無論參與除法的值是小數還是整數, 運算的結果都是精確的可以帶有小數部分的數。
當我們使用 // 進行除法時,如果參與運算的值都是整數,則除法為取整除,結果不會含有小數部分。
程式碼:

print (3/2)
print (3//2)
print (3.//2.)

結果:

3、不只是數學運算

就如數學運算類似,乘法運算子是對加法運算相同數的連加的簡化,這裡的a * 3的意思與a + a + a是一致的。
這時候乘法運算子也是同樣被對於字串型別變數進行了過載(對於字串使用乘法運算子我們有時候也稱之為重複運算子)。
輸出的結果則將是用一個字串 a 連線在另一個字串 a 的後面,之後再連線一個字串 a ,再連線一個字串 b 的結果。
現在執行一下這個程式,看看結果是不是和你預期的一致呢?
程式碼:

a = '我愛'
b = '中國'
print(a+b)
print(a*3+b)

結果:
在這裡插入圖片描述

4、字串長度

你可能注意到了,這裡 b 是一個字串,而 a_len 是一個數字。雖然 Python 中變數、引數、函式(方法)在宣告時都是無需說明型別,但在 Python 中,儲存著整數和字串的兩個不同型別的變數間是沒有已經被定義的運算的。
因此我們在這通過str函式,將 a_len 中儲存的數字變成了一個字串(如果原本 a_len 是 2 ,那麼 str(a_len) 則是 ‘2’)。並使之與 b 進行連線後輸出。
執行一下程式,看看結果吧!
程式碼:

a = '中國'
a_len=len(a)
b='a的長度是'
print(b+str(a_len))

結果:
在這裡插入圖片描述

5、運算分析

在前一節課程中,我們對多檔案、多模組的程式設計進行對於下面這個 Python 程式,請選出 a, b, c 三個變數的值(請關注型別)完全正確的選項。
程式碼:

a = '10'
b = 10
c = str(len(a) + b)
a = len(a * b)
b = a + b

選擇:

(1) 20, 30, '12'

(2) '20', 30, '12'

(3) 100, 30, '4'

(4) '100', 20, '12'

(5) '20', '30', 12

(6) 20, '30', '12'

(7) 20, 20, '4'

答案:

(1) 20, 30, '12'

a 最終被賦予了最初 a 這個字串重複 b 次(10次)後的字串長度,也就是 20。b 是重新取值後的 a(也就是 20)加上原本的 b(也就是 10)之後的結果 30。c 是最初的 a 的長度(也就是 2)加上原本的 b(也就是 10),得到 12。

6、函式

程式碼:

def main():
   print('世界,你好') 

if __name__ == '__main__':
    main()
    

程式碼:

def max_pow(a,b):
    if a>b:
        pow_ab=a**b
        print(pow_ab)
        return pow_ab
    pow_ba=b**a
    print(pow_ba)
    return pow_ba

值得一提的是,在上面的例子中我們在函式定義部分所用到的變數a、b、pow_ab、pow_ba都只在函式的定義部分有效。如果我們其他地方使用這些變數,他們都將是沒有定義或不同於函式定義中的值的。

7、拯救牛郎織女

太棒了,你似乎成功的在王母娘娘定義的銀河上架起了一座現代化橋樑,牛郎織女就要成功的團聚了。雖然我們批判王母娘娘不支援自有婚姻的老舊思想,但是我們也要像她學習,她很清楚,函式需要在被使用前被定義。
也就是說,在yinhe函式的定義需要被先寫在被呼叫前,在這點上,王母娘娘還是做的很不錯的。
程式碼:

def yinhe(a):
    print('='+a+'=')
def main():
    print('牛郎')
    yinhe('||')
    print('織女')
     
if __name__ == '__main__':
    main()

結果:
在這裡插入圖片描述

8、縮排的使用

你或許注意到了這課的第一段我提到了 起始縮排 的概念,對於每一個起到組織語句作用的語句,比如上面的程式的def和if,他們都會有自己的起始縮排。對於上面這段程式碼,函式下轄的語句共有一個自己的起始縮排,我們可以看見這裡是 4 個空格。
而在if起始的這行後,有兩行則又因為他們被組織在if這條語句下,而擁有相同的 8 個空格的 實際縮排,其中 4 個是因為他們被組織在這個函式下,由函式貢獻的 起始縮排,而另外 4 個是因為他們被組織在這個函式下的if語句中而進一步累加的由if語句貢獻的 起始縮排。
程式碼:

def max_pow(a,b):
    if a>b:
        pow_ab=a**b
        print(pow_ab)
        return pow_ab
    pow_ba=b**a
    print(pow_ba)
    return pow_ba

9、無處不在的幫助

你做的很棒,這三行程式碼讓我們可以去看了在sys下被定義了的exit函式的形式和作用說明、一個字串( str )下被定義了的split函式的形式和作用說明、以及list下所有的被定義的函式方法的列表。
相信你有了 help 和 dir 這兩個神器,你一定能比王母娘娘學 Python 學的更好!
執行一下,讓我們來看看結果,瞭解下上面這些方法到底是幹什麼的吧!
程式碼:

import sys

help(len)
print(dir(sys))
help(sys.exit)
help('中國'.split)
print(dir(list))

結果:

10.Python 的註釋

同時,Python 提供了另外一種讓區域性程式不被執行的方式,你可以在你希望不被執行的程式碼塊前後兩行分別加上 ’ ’ ’ 這樣的三個單引號。有的地方會稱這種方式為多行註釋,但是我們並不建議用於新增註釋內容時使用,而建議你僅在需要讓部分程式碼暫時不被執行時臨時使用這個方式讓程式避開這部分的執行。

11、程式知多少(題目)

這一部分我們學了一些 Python 的基本知識,讓我們來測驗下你對 Python 程式及相關介紹到的知識瞭解有多少了呢。 請從給出的選項中選出 2 個 錯誤 的描述。
題目:

(1)Python 函式的引數可以是其他函式
(2)a, b, i, t 這樣的單字母的命名不會影響程式的可讀性
(3)函式的引數形式需要可以簡單代表“函式可接受引數,及其在函式具體定義中的意義”
(4)Python程式中不按照規範,統一使用 3 個空格進行縮排是不會出現錯誤的
(5)getMoney 所示的是一個駝峰方式的命名,是 Python 中不推薦的
(6)Python 程式中使用 Tab 進行縮排和使用 4 個空格進行縮排混在一起使用是不會造成錯誤的好習慣
(7)通過使用 help 和 dir 可以快速瞭解大多數 Python 模組和方法的資訊
(8)Python 的函式是可以沒有 return 語句的
(9)Python 的函式是可以包含有多個 return 語句的
(10)Python 程式幾乎所有的錯誤都是在程式執行到含有錯誤的那行的時候被報出

答案:

(2)a, b, i, t 這樣的單字母的命名不會影響程式的可讀性

我們很難知道單個字母的變數或者函式到底是什麼意義。因此,我們要儘可能避免大量使用單字母命名的習慣

(6)Python 程式中使用 Tab 進行縮排和使用 4 個空格進行縮排混在一起使用是不會造成錯誤的好習慣

縮排一定要統一,Python 是縮排敏感的。不統一的縮排不但在開發時會帶來不好的視覺體驗,在 Python 中更是會導致程式報錯

12、A+B+C問題

這是一個非常簡單的題目,意在考察你程式設計的基礎能力。千萬別想難了哦。輸入為一行,包括用空格分隔的三個整數,分別為 A、B、C(資料範圍均在 -40 ~ 40 之間)。輸出為一行,為“A+B+C”的計算結果。
樣例輸入:22 1 3
樣例輸出:26
程式碼:

a, b, c=(int(x) for x in input().split(' '))
print(a+b+c)
for … in…用法:

說明:也是迴圈結構的一種,經常用於遍歷字串、列表,元組,字典等
格式:

for x in y:
    迴圈體

執行流程:x依次表示y中的一個元素,遍歷完所有元素迴圈結束

enumerate() 函式:

用於將一個可遍歷的資料物件(如列表、元組或字串)組合為一個索引序列,同時列出資料和資料下標,一般用在 for 迴圈當中。
語法:

enumerate(sequence, [start=0])

引數
sequence – 一個序列、迭代器或其他支援迭代物件。(下標)
start – 下標起始位置。(元素)

第二章 字串使用

1、Python 的字串

我們可以通過方括號來訪問字串中的每一個字元(長度為 1 的在某一個位置的子字串),在這點上, Python 的設計與 Java, C++ 等語言一致,他們的開始索引位置都是 0。對於一個指向字串的變數 str = ‘你好啊’,如果我們訪問 str[1]我們將得到的是字元 好,而不是 你。如果我們試圖訪問的索引位置不存在, Python 將會給我們一個“超出合法範圍”的錯誤。類似的訪問方式對於列表等其他 Python 的型別也是存在的,你可以之後進行進一步的瞭解。

2、字串位置

你做的很棒,現在執行程式,看看輸出是不是和你的預期一致呢?

a = '你好啊'
b=a[0]+'i'
print(b)
print(len(b))

3、大寫與小寫

你或許發現了,不加 r 的字串中\n會使得字串換行(我們稱之為換行的轉義字元)。加上 r 以後的字串中任何字串中的轉義字元都不會被轉義(這裡的 r 是英語單詞 raw 的簡寫),因此我們看見,在最後的將字串 b 轉為大寫字母輸出來的時候\n變為了\N

a = '我是\n tom'
b = r'我是\nTom'
print (a)
print (b)
print (a.lower())
print (b.upper())
lower()函式:

將字串中的所有大寫字母轉換為小寫字母

upper( )函式:

將字串小寫轉大寫

4、字串測試

非常棒,讓我們執行一下,看看王母娘娘想得到的測試結果是什麼呢!是不是發現王母娘娘把“World”這個單詞拼寫錯誤了呢,你真是棒棒的!

s = 'HelloabcdWord'

print(s.isalpha())
print(s.isdigit())

print(s.startswith('Hello'))
print(s.endswith('World'))
isdigit()函式:

S.isdigit()返回的是布林值:True False
S中至少有一個字元且如果S中的所有字元都是數字,那麼返回結果就是True;否則,就返回False

isalpha()函式:

S.isalpha()返回的是布林值:True False
S中至少有一個字元且如果S中的所有字元都是字母,那麼返回結果就是True;否則,就返回False

startswith()函式:

作用:判斷字串是否以指定字元或子字串開頭

endswith()函式:

作用:判斷字串是否以指定字元或子字串結尾,常用於判斷檔案型別

5、字串的索引

在這裡插入圖片描述

如上圖所示,如果我們希望從通過a = 'Hello'被賦值的字串變數 a 中列印出字母 e。 我們既可以寫print(a[1]),也可以寫print(a[-4])。他們訪問並輸出的都將是字串 Hello 中字元 e 所在的那個“格子”。

6、 切取字串

太棒了,讓我們執行看看結果。

從結果中,我們可以看到,tower[1:4] 切取了“寶塔”第 2 個字元到第 4 個字元,你也可以想象成真實世界的寶塔上,我們爬了一層樓到了 2 層然後一直爬到 4 層,所以我們寫 [1:4] 被切取的是第 2 個字元到第 4 個字元(1 層屋頂到 4 層地屋頂間)組成的新字串 木水火

類似的,tower[3:] 列印出了第 4 個字元及之後字元組成的字串(你可以想象成爬了 3 層塔以後以上的部分)火土風雨tower[:-2] 則列印出的是從字串去掉最後兩個字元(你可以想象塔被砍掉了最上面兩層)組成的字串 金木水火土

tower = '金木水火土風雨'
print (tower[1:4])
print(tower[3:])
print (tower[:-2])

7、if 語句

有的時候,我們可能會用到在中文意義上的“與”、“或”、“不是”的邏輯,比方說,我們想判斷一個變數 a 是不是在比 5 小或比 13 大的範圍,並執行相關語句,我們可以利用寫if a < 5 or a > 13:——這裡我們引入了表示“或”的 or 關鍵字。類似的,對於“與” Python 提供了 and 關鍵字,對於“不是” Python 則提供了 not 關鍵字。你將在之後的實踐中逐步的更好的體會和使用。

8、查詢與替換

太棒了,讓我們執行一下程式,是不是下雨天程式就幫多聞天王把傘放在包裡了呢? 當然,你也可以把 weather 變數的值改為’大晴天’再看看 if 語句是不是按照你的預期工作了呢?

weather = '下雨天'
bag = '包裡空空的'
if weather.find('雨') != -1:
    bag = bag.replace ('空空的','有')    
print(bag)
find()函式:

檢測字串中是否包含子字串 str ,如果指定 beg(開始) 和 end(結束) 範圍,則檢查是否包含在指定範圍內,如果指定範圍內如果包含指定索引值,返回的是索引值在字串中的起始位置。如果不包含索引值,返回-1。

replace()函式:

把字串中的 old(舊字串) 替換成 new(新字串),如果指定第三個引數max,則替換不超過 max 次。

9、字串格式化

很好,執行一下程式,你會發現,這樣的寫法得到的輸出結果和之前的寫法是一致的。字串中的 {} 表示字串中可以嵌入值的佔位。而 .format 則用於列出要嵌入到輸出中的所有變數或數值,具體變數或數值在其後的圓括號內由逗號分隔列出。在字串中的大括號內的數字則與嵌入變數或數值的位置一一對應。

例如 {0} 對應了 name{1} 對應了 age{2} 對應了 height(如果大括號內不寫數字,則預設按出現在字串中的位置與 format 列出的變數或數值一一對應)。

類似的還有一種字串格式化的方式,你可以講最後一行替代為:

print('%s是一位%d歲的老奶奶,她身高%g米' % (name, age, height))

在這裡,單引號外的 % 前定義了輸出字串的格式,其中的 %s 表示字串變數嵌入佔位,%d 表示整數變數嵌入佔位、%g 表示浮點數變數(高精度小數)嵌入佔位。相應的,在 % 後的括號內,我們按照前面的格式中的佔位順序,依次列出希望將值進行嵌入的變數(在之後我們會知道,% 後的東西是一個“元組”)。

這兩種 字串格式化 的方式非常實用和簡潔,不易出錯,是我們大多數時候會去鼓勵使用的。你可以對大括號內的數字、變數的值進行一些修改,看看執行結果是不是也相應的做出了改變呢?

name = '王母娘娘'
age = 9000
height = 1.73
print(name + '是一位' + str(age) + '歲的老奶奶,她身高' + str(height) + '米')
print('{0}是一位{1}歲的老奶奶,她身高{2}米'.format(name,age,height))
print('%s是一位%d歲的老奶奶,她身高%g米' %(name,age,height))

10、豐富的字串(題目)

你已經對 Python 字串有了不少的瞭解,文曲星決定考考你,看看你是不是都理解了。請選出 2 個關於 Python 字串的 錯誤 描述。

(1)字串的 `replace` 函式會替換所有符合第一個引數定義的子字串而不是隻替換第一個

(2)`print('Hello'.lower() == 'hello')` 輸出的結果為 True

(3)Python 中對於 2 < a < 13 的判斷不能寫為 if 2 < a < 13: 而必須寫成 if 2 < a and a < 13:

(4)print('Hello Jisuanke'.find('ello')) 輸出的結果為 2,因為我們找到的子字串的第一個字元在原字串的索引位置是 2。

(5)print('{0}是一位{1}歲的老奶奶,她身高{2}米'.format(name, age, height)) 和 print('{}是一位{}歲的老奶奶,她身高{}米'.format(name, age, height)) 的輸出結果是一樣的(假設 name, age, height 已經定義了)

(6)print('Hello Jisuanke'.isalpha()) 輸出的結果為 False

(7)對於一個字串 s 和一個整數 n(可能為負),s[:n] + s[n:] 與 s[:] 和 s 都一致

答案:

(3)Python 中對於 2 < a < 13 的判斷不能寫為 if 2 < a < 13: 而必須寫成 if 2 < a and a < 13:

Python 中可以直接寫 if 2 < a < 13:

(4)print('Hello Jisuanke'.find('ello')) 輸出的結果為 2,因為我們找到的子字串的第一個字元在原字串的索引位置是 2。

輸出的結果為 1,因為第二個字元的索引是 1。

11、批量替換字串

在網路程式設計中,如果 URL 中含有特殊字元,如空格、“#”等,伺服器將無法識別導致無法獲得正確的引數值,我們需要將這些特殊字元轉換成伺服器可以識別的字元,例如將空格轉換成“%20”。給定一個字串,將其中的空格轉換成“%20”。
輸入一個原始字串,例如 hello world。
輸出轉換後的字串,例如 hello%20world。

樣例輸入:we are happy
樣例輸出:we%20are%20happy

程式碼:

s = input()
print(s.replace(' ','%20'))

第三章 簡單結構

1、使用列表

對於列表來說,len 函式給出了列表的元素數量(列表長度)。我們剛剛提到了,字串是特殊列表,對於字串 len 函式給出了字串的長度的原因,其實就是因為它給出了字串這種特殊列表所包含的長度為 1 的子字串元素的長度。讓我們執行一下程式,看看輸出出來的值是不是都是正確的呢?

list = [100,23,45]
print (list[0])
print (list[1])
print (list[2])
print(len(list))

2、列表尾部的新增

很好,讓我們執行一下,看看 append 和 extend 被執行後的列表是否實現了王母娘娘預期的效果呢?

hello = ['hi', 'hello']
world = ['earth', 'field', 'universe']
hello.append('你好')
print (hello)
hello.extend(world)
print(hello)

append()函式:

用於在列表末尾新增新的物件。

extend()函式:

用於在列表末尾一次性追加另一個序列中的多個值(用新列表擴充套件原來的列表)。

3、插入資料與元素定位

執行一下你的程式,看看結果與你預期是不是一致的呢? 字串 hi 的索引位置是不是改變了呢?

hello = ['hi', 'hello']
hello.insert(0,'你好')
print (hello)
print(hello.index('hi'))

insert()函式:

用於將指定物件插入列表的指定位置。

index()函式:

檢測字串中是否包含子字串 str ,如果指定 beg(開始) 和 end(結束) 範圍,則檢查是否包含在指定範圍內,該方法與 python find()方法一樣,只不過如果str不在 string中會報一個異常。

4、列表彈出與刪除

執行一下你的程式,看看結果與你預期是不是一致的呢? 仔細思考一下,直接用方括號訪問索引位置的元素和用 pop 函式的區別;再想想 remove 和 pop 的相似之處。

hello = ['你好', 'hi', 'hello']
hello.remove('你好')
print (hello)
hello.pop(0)
print(hello)
remove()函式:

用於移除列表中某個值的第一個匹配項。

pop()函式:

用於移除列表中的一個元素(預設最後一個元素),並且返回該元素的值。

5、字串的切割與列表合成

很棒,讓我們執行一下程式,看看玉皇大帝會不會對你的工作滿意呢?

備註:split 函式很多時候會被和 input 函式放在一起使用,用於處理一些簡單的輸入,之前你可能已經用到過了,可以回顧一下。

manager = '托塔天王,太白金星,捲簾大將'
manager_list = manager.split(',')
print(manager_list)
new_manager = ' '.join(manager_list)
print(new_manager)

split()函式:

通過指定分隔符對字串進行切片,如果引數 num 有指定值,則分隔 num+1 個子字串

join()函式:

方法用於將序列中的元素以指定的字元連線生成一個新的字串。

6、列表的高效使用

已知列表的切取和字串類似,請判斷一下,給出的選項中,哪 3 個說法是 錯誤 的。

(1)對於 list = ['a', 'b', 'c', 'd'] 來說,list[-2:] 和 list[2:] 的結果值應該相同

(2)對於 list = ['a', 'b', 'c', 'd'] 來說,print(list.pop(3)) 將得到輸出結果 d

(3)將字串'a b c'按空格進行切割後並輸出結果可以寫成 print('a b c'.split())

(4)對於 list = ['a', 'b', 'c', 'd'] 來說,print(list[2:]) 將得到輸出結果 ['b', 'c', 'd']

(5)將 list = ['a', 'b', 'c'] 合成成字串 'a|b|c'並輸出 可以寫成 print(list.join('|'))

(6)對於 list = ['a', 'b', 'c', 'd'] 來說,print(list.pop(3)) 將得到輸出結果 ['a', 'b', 'c']

(7)對於 list = ['a', 'b', 'c', 'd'] 來說,list.insert(3, 'x') 後,list 的值為 ['a', 'b', 'c', 'x', 'd']

答案:

(4)對於 list = ['a', 'b', 'c', 'd'] 來說,print(list[2:]) 將得到輸出結果 ['b', 'c', 'd']

(5)將 list = ['a', 'b', 'c'] 合成成字串 'a|b|c'並輸出 可以寫成 print(list.join('|'))

(6)對於 list = ['a', 'b', 'c', 'd'] 來說,print(list.pop(3)) 將得到輸出結果 ['a', 'b', 'c']

7、列表求和

真棒,執行一下程式,看看到底王母娘娘的蟠桃園今年結了多少的蟠桃吧。

gardens = [7204, 3640, 1200, 1240, 71800, 3200, 604]
total = 0
for num in gardens:
    total+=num
    
print (total)
print(sum(gardens))

8、range 的使用

有的時候,我們可能會希望取得一個遞減取值的列表,那麼我們可以將開始數字被設定的大於結束數字,而每次取值的間隔為一個負值。例如,我們呼叫range(10, 1, -2)將得到一個從 10 開始,每兩個數取一次元素,並確保每個元素都大於 1 的列表 [10, 8, 6, 4, 2]

9、while 迴圈

很棒!你應該看見了,我們將一系列的語句組織在了 while 下的語句塊裡,這些語句會一直被迴圈執行,直到 first 的數值不再小於 100,則開始執行這個語句塊後的其他程式。

我們在一個時間會關注兩個數值,每次我們會輸出第一個數,並將兩個數求和後給到大的那個數,再把原來的大的那個數變成當前兩個數中小的那一個。執行一下程式,看看哪些數是斐波那契數吧!

first = 0
second = 1
# 請在下一行寫程式碼
while first < 100:
    print(first)
    first, second = second, first + second
print('一切都完成啦')

10、簡單斐波那契(題目)

斐波那契數列是一種非常有意思的數列,由 0 和 1 開始,之後的斐波那契係數就由之前的兩數相加。用數學公式定義斐波那契數列則可以看成如下形式:

F0=0
F1=
F_n=F_{n-1}+F_{n-2}
我們約定 F_n表示斐波那契數列的第 n 項,你能知道斐波那契數列中的任何一項嗎?
輸入包括一行,包括一個數字 n(0≤n≤50)。
輸出包括一行,包括一個數字,為斐波那契數列的第n項的值。

樣例輸入:7
樣例輸出:13

n = int(input())

first = 0
second = 1
# 請在下一行寫程式碼
while first < n:
    first, second = second, first + second

print (second)

第四章 排序與元組

1、簡單的排序

Python 沒有內建對陣列的支援,但可以使用 Python 列表代替。

很好,但是你需要注意,呼叫 sort 函式並不會帶有返回值。也就是說,如果我們寫

x=numbers.sort()

的話 x 將得到的值是 None,而不是一個排序後的列表。現在執行一下程式,看看結果是不是符合你的預期吧。
程式碼:

numbers = [1, 4, 2, 3, 8, 3, 0]

numbers.sort()

print(numbers)

x=numbers.sort()

print(x)

結果:

sort()函式:

用於對原列表進行排序,如果指定引數,則使用比較函式指定的比較函式。

2、基本的排序

在這裡,我們使用了 sorted 函式的 reverse 引數,這個引數用於標記排序的結果的順序性,將這個引數設定為 True 會將排序的結果順序設定為逆序。執行一下,看看結果是不是和你預期的一致呢?
程式碼:

numbers = [1, 4, 2, 3, 8, 3, 0]

print (sorted(numbers))

print (sorted(numbers,reverse = True))

結果:
在這裡插入圖片描述

sorted() 函式:

對所有可迭代的物件進行排序操作。
在這裡插入圖片描述

3、字典序

根據我們上面說的,對於一些字串,你將較為輕鬆的得出一個這樣的結論——boat < boot < cap < card < cat < to < too < two < up 。很多市面上的字典採用了字典序的方式對單詞進行排列(假設都是小寫字母),你如果有機會也可以翻一翻字典看一看。

4、個性化排序

很好!我們可以看到,這樣我們就可以根據自定義的 china_first 函式的返回值作為中間值對原來的列表輸入進行排序了。執行一下程式,看看和你預期的是否一致?分析下為什麼會得到這樣的結果。
程式碼:

def china_first(item):
    if item == 'China':
        return 0
    else:
        return len(item)
country = ['jp', 'China', 'USA', 'Thai']

print (sorted(country,key=len))

print(sorted(country,key = china_first))

結果:
在這裡插入圖片描述

5、排序題目

請從關於排序的陳述中選出 正確 的兩個

(1)list.sorted() 可以讓 list 列表內元素升序排序

sorted 並不能作為 list 的一個方法被呼叫,應該將 list 放入 sorted 後的括號內

(2)sorted([1, -4, 3, -10, -2, 6], key = abs, reverse = True) 的結果是 [-10, -4, -2, 1, 3, 6]

正確的結果是 [-10, 6, -4, 3, -2, 1]

(3)sort(list) 可以返回 list 列表升序排序後的結果

sort 並不能這麼使用,sorted 才可以喔。

(4)sorted(list, reverse=False) 與 sorted(list) 的結果是不一致的

應該是一致的,reverse 預設值為 False

(5)sorted([1, -4, 3, -10, -2, 6], key = abs, reverse = True) 的結果是 [1, -2, 3, -4, 6, -10]

正確的結果是 [-10, 6, -4, 3, -2, 1]

(6)sorted([1, -4, 3, -10, -2, 6], key = abs, reverse = True) 的結果是 [-10, 6, -4, 3, -2, 1]

正確

(7)直接列印 sorted(list) 可以獲得正確的排序後結果

正確

6、元組是什麼

Python的元組與列表類似,不同之處在於元組的元素不能修改。
元組使用小括號,列表使用方括號。
元組建立很簡單,只需要在括號中新增元素,並使用逗號隔開即可。

如果建立一個只有 1 個元素的元組,則第一個元素的後面需要加一個逗號tuple = (‘hi’,)。這是一個很有意思的設計,並且這裡的逗號是不能省略的,這個設計是為了區分一個正常的括號內放一個元素的這種情況,如果不加這個逗號,有的時候 Python 會直接將它等價成一個沒有括號的值。

在接收元組的值的時候,我們也可以通過元組型的變數進行接收。接收後我們可以通過其中的單一變數對返回的多個值中的某一個進行訪問。

7、元組的使用

很棒,讓我們輸出一下現在的 tuple 值。類似列印列表的做法,請在下一行寫下

print (tuple)

程式碼:

tuple = (1, 2, 'hi')

print (tuple)
print (len(tuple))
print (tuple[2])

tuple = (1,2,'bye')

結果:

8、元組做返回值

你可能注意到了,其實我們在輸出時,也可以依次輸出多個變數,就像print(y, z)這樣的形式。執行一下你的程式,讓我們來看看你程式的結果是什麼樣的。你理解了用元組來做函式返回值的好處了嗎?
程式碼:

def plus_one(tuple):
    return tuple[0] + 1, tuple[1] + 1, tuple[2] + 1

t = (1, 4, -1)

(x,y,z) = plus_one(t)

print(x)
print(y,z)

結果:

9、交叉排序

輸入一行 k 個用空格分隔開的整數,依次為 n_1, n_2 … n_k。請將所有下標不能被 3但可以被 2 整除的數在這些數字原有的位置上進行升序排列,此外,將餘下下標能被 3 整除的數在這些數字原有的位置上進行降序排列。
輸出包括一行,與輸入相對應的若干個整數,為排序後的結果,整數之間用空格分隔。
樣例輸入:1 5 4 3 10 7 19
樣例輸出:1 3 7 5 10 4 19
提示:
請注意,題面中的下標是從 1 開始的哦!
另外,對於讀入的數 x 建議通過int(x)轉成整數以後,這道題就不難做啦。
程式碼:

1

結果:

第五章 字典與檔案

1、什麼是字典


可以作為鍵名的常見資料型別包括了字串、數值和元組,而鍵值則可以是任何型別的數值。如果使用了錯誤的鍵名,將無法取出正確的鍵值。

看看以下程式會輸出什麼吧!
程式碼:

bat = {} 
bat['b'] = '百度' 
bat['a'] = '阿里巴巴' 
bat['t'] = '騰訊'

print (bat)
print(bat['a'])

bat['a'] = '亞馬遜'
print(bat['a'])

print('b' in bat)
print('x' in bat)

結果:
在這裡插入圖片描述

2、檢視字典元素

這裡的 items 函式會取到每一個鍵名和對應鍵值,並將他們組成一系列的元組放到一個列表裡。現在執行一下,看看結果是不是和你預期的一樣呢?

如果你希望輸出不包括 dict_keys,dict_values,dict_items 這樣的資訊。你可以額外寫一個 list:

print(list(bat.keys()))
print(list(bat.values()))
print(list(bat.items()))

程式碼:

bat = {'a': '阿里巴巴', 'b': '百度', 't': '騰訊'}

print (bat.keys())
print (bat.values())
print (bat.items())

print(list(bat.keys()))
print(list(bat.values()))
print(list(bat.items()))

結果:

keys()函式:

返回一個可迭代物件,可以使用 list() 來轉換為列表(鍵名)

values() 函式:

以列表返回字典中的所有值(鍵值)

items()函式:

以列表返回可遍歷的(鍵, 值) 元組陣列。

3、for迴圈列印字典

在這裡,我們實際上訪問了 bat.items() 這個列表中的每一個元組元素,並且讓他們在迴圈中被賦給臨時的 k 和 v 變數,並按照結果輸出出來了。

程式碼:

bat = {'a': '阿里巴巴', 'b': '百度', 't': '騰訊'}

for value in bat.values():
    print(value)
    
for key in bat:
    print(key)
    
for k,v in bat.items():
    print(k,'>',v)

結果:

4、字典資料格式

在這裡,與之前的字串格式化相同, %s 表示字串變數嵌入佔位,%d 表示整數變數嵌入佔位、%g 表示浮點數變數(高精度小數)嵌入佔位,而每一個佔位字元的 % 與型別標識字母前都有一個圓括號,裡面填寫了鍵名。

這樣,在字串格式的百分號後,我們只需要將字典的變數名寫上,就可以將它的每一個鍵值都對應正確嵌入了。執行一下程式,看看這個某廠老闆的是怎麼樣的吧。

程式碼:

boss = {} 
boss['name'] = 'robin' 
boss['age'] = 45
boss['height'] = 1.78

print ('The boss named %(name)s is %(age)d-year-old and %(height)g tall.' %boss)

結果:
在這裡插入圖片描述

5、刪除表示式

刪除表示式 意見反饋
本節內容計 5 分,完成後得滿分
在 Python 中可以對元素使用 del 進行刪除,我們可以刪除變數、列表中元素或者字典中元素。讓我們先對列表 list 做一些操作,刪除 list 的開頭的元素和最後兩個元素,然後輸出一下被操作後的列表 list,請在接下來寫下

del list[0]
del list[-2:]
print(list)

類似的,我們也可以刪除一下字典中的鍵名為 b 的元素,並輸出一下被操作後的字典。請在下兩行寫下

del dict['b']
print(dict)

讓我們再來刪除變數 num 然後試著輸出一下變數 num。請在下兩行寫下

del num
print(num)

程式碼:

num = 6
list = ['a', 'b', 'c', 'd']
dict = {'a': 1, 'b': 2, 'c': 3}
del list[0]
del list[-2:]
print(list)
del dict['b']
print(dict)
del num
print(num)

結果:
在這裡插入圖片描述

6、字典操作

對於一個字典

dict = {'lily': 100, 'bob': 88, 'alice': 59}

請從下面的字典操作的說法中選出兩個正確的選項:

(1)print(dict['bob'] + 1) 得到的輸出是 89
(2)接著寫下 dict['leijun'] = 99 並 del dict['alice'] 後,輸出 print(list(dict.items())) 得到的輸出是是字典所有的鍵名組成的一個列表
(3)接著寫下 dict['leijun'] = 99 並 del dict['alice'] 後,輸出 print(list(dict.keys())) 得到的輸出是字典所有的鍵名組成的一個列表
(4)print('The grades are lily: %('lily')d, bob: %('bob')d, alice: %('alice')d' % dict) 得到的結果是 The grades are lily: 100, bob: 88, alice: 59
(5)print(dict['bob'] + '1') 得到的輸出是 '881'

答案:

7、檔案的使用

open 函式:
close() 函式:

在 Python 有一個 open 函式,這個函式會返回一個可以用於讀出和寫入檔案的 檔案操作符(file descriptor)。我們可以通過

fd = open('filename', 'r')

的方式,開啟一個檔名叫做 filename 的檔案獲取它的檔案操作符,並讓變數 fd 指向這個操作符(這裡的 r 標記了檔案被開啟用於讀取)。當我們用完這個檔案不再繼續使用時,我們可以用 fd.close() 結束對這個檔案的使用。

還可以用 w 來表示寫入(或者用 a 表示向後繼續新增),這些標記可以被放在一起使用,比如我們如果寫

fd = open('filename', 'rw')

則表示對這個檔案會有讀操作,也會有寫操作。

我們可以用標準的 for 迴圈來讀取一個檔案的每一行(僅對檔案讀取有效,對二進位制讀取並不能用)。

# 按行輸出整個檔案
f = open('filename', 'rU')
for line in f:   # 訪問檔案每一行
    print(line, end = ' ')    # 列印每一行,end = ' '可以確保不新增換行

每次讀取一行的好處在於我們不會受到記憶體的限制,也就是說,我們可以每次只把檔案的一部分放到記憶體進行處理,而不會需要一次性完整載入大塊頭的檔案到記憶體中。這樣,對於一個 1T 的檔案,很難找到一個 1T 記憶體的你也可以對它進行優雅的處理。

readline()函式:
read ()函式:

我們也可以通過呼叫檔案操作符的函式 readline(寫成 fd.readline()),它會一次性載入完整的檔案到記憶體,讓檔案的每一行作為它這個列表結構的每一個字串元素。類似的,檔案操作符的函式 read (寫成 fd.read())則會一次性將完整檔案作為一個字串讀入到記憶體內。但是這兩種方式往往都在處理大檔案的時候會遇到記憶體無法完整存下內容的問題。

write()函式:

對於向檔案中的寫入,我們可以用 write 函式(寫成 fd.write(字串))將指定的字串寫入到開啟的檔案中。

8、檔案編碼

在 Python 中有一些已經寫好的功能性的“包”稱為 模組(module)。其中有一個名叫 codecs 的模組,它提供了讀取一個非英文的檔案所需要的 unicode 讀取支援。

讓我們使用它時,我們需要先通過 import 將它引入,之後在開啟檔案時,標記明確所需要使用的編碼字符集(在這裡我們使用 utf-8)。

import codecs 
fd = codecs.open('foo.txt', 'rU', 'utf-8')

當讀取完,並且成功完成相關的處理後,我們需要注意。我們只可以使用 fd.write() 的形式來進行寫出。

9、題目

題1、兩數之和:
兩個數,使得他們的和為一個給定的數值target。
函式twoSum 返回兩個數字 index1 , index2
其中:number[index1] + number[index2]=target;
注意:
index1必須小於 index2
且不能為 0 假設每一組輸入只有唯一的一組解。
格式:第一行輸入一個數 n,接下來的兩行分別輸入陣列 number[n] 和 target,返回index1和index2

樣例輸入:
3
5 75 25
100
樣例輸出:
2 3

​程式碼:

import ast

n = int(input())
number =  ast.literal_eval(input()) 
target = int(input())

for index1, number1 in enumerate(number):
    for index2, number2 in enumerate(number):
        if(index2>index1 and number1+number2==target):
            print(index1,index2)

結果:
在這裡插入圖片描述

enumerate()函式:

用於將一個可遍歷的資料物件(如列表、元組或字串)組合為一個索引序列,同時列出資料和資料下標,一般用在 for 迴圈當中

題2、統計字元個數:
輸入一行字元,分別統計出其中英文字母,數字,空格,其它字元的個數。
輸入格式:

輸入一行字元

輸出格式:

輸出為一行,分別輸出英文字母,數字,空格,其它字元的個數,用空格分隔

樣例輸入:

aklsjflj123 sadf918u324 asdf91u32oasdf/.';123

樣例輸出:

23 16 2 4

答案參考:
https://www.cnblogs.com/python-xkj/p/9225985.html

程式碼:

tmpStr = input('請輸入字串:')
alphaNum=0
numbers=0
spaceNum=0
otherNum=0
for i in tmpStr:
    if i.isalpha():
        alphaNum +=1
    elif i.isnumeric():
        numbers +=1
    elif i.isspace():
        spaceNum +=1
    else:
        otherNum +=1
print('字母=%d'%alphaNum)
print('數字=%d'%numbers)
print('空格=%d'%spaceNum)
print('其他=%d'%otherNum)

結果:
在這裡插入圖片描述

第六章 正規表示式

1、什麼是正規表示式

正規表示式是一個特殊的字元序列,它能幫助你方便的檢查一個字串是否與某種模式匹配。
Python 自1.5版本起增加了re 模組,它提供 Perl 風格的正規表示式模式。
re 模組使 Python 語言擁有全部的正規表示式功能。
compile 函式根據一個模式字串和可選的標誌引數生成一個正規表示式物件。該物件擁有一系列方法用於正規表示式匹配和替換。
re 模組也提供了與這些方法功能完全一致的函式,這些函式使用一個模式字串做為它們的第一個引數。

^ 作為開始標記,$ 作為結束標記,分別用於標記一個字串的開始和結束的位置。 \ 用於一些字元的轉義.
比如 \ . 表示對於一個真實點字元的匹配,\ \ 表示對於一個真實反斜槓字元的匹配等。如果你對不是很確定一些字元是否需要進行轉義才能匹配,你大可都加上斜槓,比如對於@你寫成@是一定沒有問題的。

2、正規表示式查詢

在 Python 中使用模組 re 提供的 search 函式,我們可以用正規表示式在一個字串中進行匹配查詢。請在下一行寫下

match = re.search(r'word:\w\w\w', str)

這裡表示我們將用word:\w\w\w這個正則從字串str進行匹配查詢,正規表示式前的 r 標記了這個表示式不需要被轉義處理,也就是說\n這樣的東西在寫了 r 標記的情況下,不會被理解成換行。match 變數將指向匹配查詢的結果。請在下一行寫下

if match:

這樣,我們就可以判斷是否有合法的匹配,如果存在,if 條件下的語句塊就會被執行。請在下一行 if 語句塊內(請注意,這裡需要縮排)寫下:

	print('found', match.group())

很棒!執行一下,看看結果怎麼樣呢?是不是成功的把符合要求的 cat 找了出來呢?

程式碼:

import re
str = 'A cute word:cat!!'
match = re.search(r'word:\w\w\w', str)
if match:
    print('found', match.group())

結果:
在這裡插入圖片描述

re.match函式:

嘗試從字串的起始位置匹配一個模式,如果不是起始位置匹配成功的話,match()就返回none

re.search函式:

掃描整個字串並返回第一個成功的匹配。

group()函式:

在正規表示式中用於獲取分段截獲的字串

3、基礎正則使用

讓我們來嘗試使用一下正規表示式中的 . 的使用,請在下一行寫下

print(re.search(r'..g', 'piiig').group())

接下來,我們試一試 \d 的使用,請在下一行寫下

print(re.search(r'\d\d\d', 'p123g').group())

不錯,我們還可以再看看 \w 的使用,請在下一行寫下

print(re.search(r'\w\w\w', '@@abcd!!').group())

執行並檢視結果:

程式碼:

import re
print(re.search(r'..g', 'piiig').group())
print(re.search(r'\d\d\d', 'p123g').group())
print(re.search(r'\w\w\w', '@@abcd!!').group())

結果:
在這裡插入圖片描述

4、正則表達重複

每次都要寫好多遍一樣的形式,在出現重複的形式的時候似乎會很讓人糾結。在正規表示式中,我們有 * 和 + 來實現重複多次的形式的表達, * 表示對它之前一個字元有 0 或更多次的重複,+ 表示對它之前一個字元有 1 或更多次的重複。請在下一行寫下:

print(re.search(r'pi+', 'piiig').group())

很好,我們在這裡其實去尋找的形式是 p 後跟了至少一個 i 的連續個 i 的形式。接下來,讓我們在下面兩行試著用一下 *,請寫下:

print(re.search(r'pi*', 'pg').group())

這樣,我們就可以在執行時比較一下 + 和 * 的區別了。
執行一下程式,看看結果和你的預期是否一致吧!

程式碼:

import re
print(re.search(r'pi+', 'piiig').group())
print(re.search(r'pi*', 'pg').group())

結果:
在這裡插入圖片描述

5、題目

題1、正確的正規表示式:
選出說法正確的那兩個:

(1)re.search(r'\w\w\w', '@@abcd!!').group() 的值是 abc

(2)re.search(r'\d\s*\d\s+\d', 'xx1 2 3xx') 的值是空

(3)驗證 yodada-b@jisuanke.com 是否符合郵箱形式是可以用\w+@\w+匹配的並不可以,至少 @ 後面要能匹配出一個點。

(4)驗證 yodada-b@jisuanke.com 是否符合郵箱形式是不可以用\w@\w匹配的

(5)re.search(r'\d\s*\d\s*\d', 'xx12 3xx') 的值是空

(6)re.search(r'\d\s+\d\s+\d', 'xx1 2 3xx') 的值是空

6、正則裡的方括號

在之前的閱讀裡面,我們也提到過一些正則字元等價於一些方括號包裹的正則字元,那麼方括號的作用是什麼呢?其實方括號將一系列的正則字元以“或”關係組織在一起。請在下一行寫下

print(re.search(r'[abc]+', 'xxxacbbcbbadddedede').group())

很好,除了在方括號內一一列舉的方式,我們還可以採用橫線來標識一個範圍,請在下一行寫下:

print(re.search(r'[a-d]+', 'xxxacbbcbbadddedede').group())

太棒了!執行一下,看看結果與我們預期的結果是不是一致呢?是不是前者匹配了連續的由 a, b, c 組成的字串;而後者匹配了從 a 到 d 的所有字母組成的連續字串呢?

程式碼:

import re
print(re.search(r'[abc]+', 'xxxacbbcbbadddedede').group())
print(re.search(r'[a-d]+', 'xxxacbbcbbadddedede').group())

結果:

7、正則提取

對於一個正規表示式的匹配過程,我們可以在其中新增幾對圓括號,用於指定幾個具體的匹配的部分。請在下一行寫下:

match = re.search('([\w.-]+)@([\w.-]+)', str)

如果不看圓括號,這裡我們實際上去對郵件地址進行了匹配,而圓括號匹配了@前的部分和之後的部分。請在接下來寫下:

if match:
    print(match.group())
    print(match.group(1))
    print(match.group(2))

執行一下程式,看看 group 方法在加了引數後輸出的匹配結果是什麼樣呢?對比一下不加引數時匹配出完全正則的結果。

程式碼:

import re
str = 'purple alice-b@jisuanke.com monkey dishwasher'
match = re.search('([\w.-]+)@([\w.-]+)', str)
if match:
    print(match.group())
    print(match.group(1))
    print(match.group(2))

結果:
在這裡插入圖片描述

8、正規表示式的除錯

正規表示式用簡單的一些字元的組合包含了太豐富的語義,但是它們實在太過密集了,為了把你的正規表示式寫對,你可能要花上太多太多的時間。在這裡,我們提供一些簡單的建議幫助你更高效的對正規表示式進行除錯。

你可以設計一系列放在列表裡的字串用於除錯,其中一部分是可以產生符合正規表示式的結果的,另一部分是產生不符合正規表示式的結果的。請注意,在設計這些字串時,儘可能讓他們的特徵表現的更為不同一些,便於覆蓋到我們可能出現的各種正規表示式沒有寫對的錯誤。

例如,對於一個存在+的正規表示式,我們可以考慮選用一個符合*但是不符合+的字串。

然後你可以寫一個迴圈,依次驗證每個列表內的字串是否符合指定的某個正規表示式並且和你設定的存在另一個列表內的預期結果進行比對,如果出現了不一致的情況,則你應該考慮看看你的正規表示式是不是還需要修改,如果結果基本一致,那麼我們可以考慮進一步修改我們用於除錯的字串或新增新的字串。

9、查詢所有方法

findall函式:

除了我們之前用到的 search 方法,在結合圓括號後,我們還可以使用另一個名為 findall 的方法來匹配查詢出所有的被查詢字串符合正則的結果,並得到一個結果元組為元素的列表。請在下一行寫下:

tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)

這樣,我們就將所有 str 中符合正則的結果以元組列表的形式賦給了 tuples 變數。接下來,讓我們輸出一下 tuples,請在下一行寫下:

print(tuples)

很好,執行一下程式,你是不是掌握了 findall() 了呢?

程式碼:

import re
str = 'purple alice@jisuanke.com, blah monkey bob@abc.com blah dishwasher'
tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
print(tuples)

結果:

10、在檔案中查詢

結合檔案操作和 findall 的使用選出下面這段程式碼正確的輸出結果

f = open('test.txt', 'r')
users = re.findall(r'<li><span class="name">([\w]+)</span>([\w]+@[\w\.-]+)</li>', f.read())
for user in users:
    print(user, end = ' ')

test.txt 的文字內容如下:

<ul><li><span class="name">Bob</span>bob@jisuanke.com</li><li><span class="name">ALice</span>alice@gmail.com</li><li><span class="name">Cane</span>cane.tec@bob.com</li></ul>

選擇:

(1)('Bob', 'bob@jisuanke.com') ('ALice', 'alice@gmail.com')

(2)('Bob', 'bob@jisuanke.com')

(3)('Bob', 'bob@jisuanke.com') ('ALice', 'alice@gmail.com') ('Cane', 'cane.tec@bob.com')

(4)Bobbob@jisuanke.com ALicealice@gmail.com

(5)Bobbob@jisuanke.com ALicealice@gmail.com Canecanetec@gmail.com

(6)Bobbob@jisuanke.com ALicealice@gmail.com Canecane.tec@gmail.com

(7)('Bob', 'bob@jisuanke.com') ('ALice', 'alice@gmail.com') ('Cane', 'canetec@bob.com')

11、選項與貪心匹配

在用於正規表示式的 re 模組中的函式有一些可選引數,我們可以對 search() 函式或者 findall() 函式傳入額外的引數來進行使用,如 re.search(pat, str, re.IGNORECASE)中的 re.IGNORECASE就是使用了 re 中的一個標記作為額外的引數。

在 re 模組中,提供了很多不同的可選引數,其中上面提到的IGNORECASE表示了讓匹配時忽略大小寫的區別;而另外一個可選引數DOTALL如果被新增,則會允許正則中的.去跨行匹配,加了這個引數以後.*這樣的匹配方式,將可以跨行進行匹配,而不只是在行內進行。另外,還有一個可選引數是MULTILINE,使用它以後,對於一個多行文字組成的字串,^$將可以用於匹配每一行的開始和結束,而如果沒有用它時^$只會匹配整個字串的開始和結束。

除了可選引數之外,我們還需要理解一下正則匹配的“貪心情況”。假設我們有一段文字<b>foo</b> and <i>so on</i>而你希望匹配(<.*>)提取的所有 HTML 標籤,你覺得結果會是怎麼樣呢?我們可以得到期望的<b>, </b>, <i>, </i>這樣的結果嗎?

事實上的結果可能會有點出乎你的意料,因為 .* 這樣的匹配是“貪心”的,它會盡可能去得到較長的匹配結果,因此我們會得到的是一整個<b>foo</b> and <i>so on</i>作為匹配出的結果。如果我們希望獲得期望中的結果,我們就需要這個匹配是非貪心的,在正規表示式中,我們對於*+這種預設貪心的匹配可以加上?使之變為不貪心的。

也就是說,如果我們將(<.*>)改成(<.*?>),正規表示式會先匹配<b>,然後匹配</b>,接下來則分別是<i></i>。這樣的匹配結果與我們的預期完全一致。相應的,對於一些用到了+的情況,我們可以將+變為+?來進行非貪心的匹配。

第七章 Python中的物件導向程式設計

1、 物件導向程式設計與類

物件導向程式設計:就是把現實世界中的所有事物都當做是“物件”,包括有形的和無形的。
物件:現實中的一切都可以成為物件
:將抽象之後的資料和函式封裝在一起,就構成了類。類是對某一組具有相同屬性與方法的物件的抽象。

一個類會擁有一系列屬性方法——在 Python 中,我們可以把屬性看做類內定義的一個或者幾個變數(比如列表中的元素),而方法則相當於類內定義的一系列函式(比如列表的append函式)。

在 Python 中,我們不光可以使用系統定義的類,當然也可以自己定義類。這裡讓我們來看一個“大眾臉”的類定義。大家先直接看程式碼,接下來我們將會依次對其進行說明:

class Journalist:
    """
    以這個為例
    """
    def __init__(self,name):
        self.name=name
    def get_name(self):
        return self.name
    def speed(self,speed):
        d={}
        d[self.name]=speed
        return d 

現在,讓我們依次介紹這些程式碼的含義。

第一行class Journalist,類似於之前我們用def宣告函式一樣,是在宣告一個名為Journalist的類——其關鍵詞為class。通常情況下,類的名稱要用大寫字母開頭——如果是兩個單片語合的話,最好每個單詞都用大寫開頭,比如HongKongJournalist,這樣便於提高程式碼的可讀性。

宣告瞭類的名字之後,接下來就是類內部的程式碼塊了——大家已經知道 Python 使用縮排來區分名稱空間層次,之前我們在定義函式的時候,函式中的程式碼也是相對於宣告語句向前縮排的——現在類也是一樣。我們建議大家這個時候不要用Tab鍵,而是直接敲四個空格,免得當你的程式碼被別的文字編輯器開啟的時候縮排被搞亂。

類裡面的程式碼,定義的就是這個類的方法——這是一個物件導向程式設計的術語,實際上就相當於是類自己的函式。我們可以看到,方法的定義方式跟一般的函式,幾乎完全一樣。

不過仔細觀察的話,大家依然能很容易地發現一些區別:所有的函式,都包括一個引數self。這裡大家需要注意:類的所有方法,其引數列表都必須包括self,並且預設作為第一個引數。如果有同學接觸過 C++ 的話,這裡可以將其理解為 C++ 中的this指標——沒接觸過也無所謂,後面的課程中我們將對其進行展開介紹。

接下來,我們將對類的每一個方法都進行簡要的說明。

def __init__(self,name)這個方法比較特殊——它的命名方式就很特別,開頭和結尾都是雙下劃線。學習過 C++ 或者其他類似的物件導向程式設計語言的同學,對此一定會非常熟悉——這就是這個類的建構函式,也可以叫初始化函式

所謂初始化,就是讓一個類的例項物件在生成的時候,有一個基本的狀態,而不是空空如也。現實中我們做任何事情的時候也需要“初始化”——比如說如果我們要喝水的話,那麼我們需要一個裝滿水的杯子。然後“初始化”這個過程對應的就是我們把杯子拿過來倒滿水的過程。在 Python 的類中,初始化就擔負著類似的工作。要用類構建一個例項物件,就必須呼叫初始化函式。

大家可以看到,在這段程式碼中,初始化函式的引數除了self之外還有一個name——也就是說,當我們要初始化這個類的一個例項的時候,我們需要給它一個引數。在初始化函式中,self.name=name的含義是要建立例項的一個屬性。這個屬性的名字是name,它的值就是引數name傳入的值。學習過 C++ 的同學一定還記得 C++ 定義的類中可以宣告成員變數——而我們已經知道 Python 中的變數是不用宣告的,因此在 Python 中,我們使用這種直接賦值的方式來建立例項的屬性。

這裡需要說明的是,屬性self.name中的屬性名稱name,並不一定要跟初始化函式中的引數名字完全相同——這裡我們故意把函式的引數也命名為name僅僅是為了提升程式碼的可讀性,並不是必須這麼做的——如果你願意的話,self.X=name的寫法也是可以的。

def get_name(self)def color(self,color)是類裡的另外兩個方法——除了第一個函式引數必須是self之外,它們的定義方式跟一般的函式完全一樣。這裡需要說明的是,像self.name這樣的呼叫方法,只能在類中使用。

2、建立類的例項物件

類是物件的定義,而例項才是真實存在的物件——例如我們定義的“記者”就是一個類,然而抽象的“記者”這個概念並不代表具體的某個真實存在的記者,只有“華萊士”或者“張寶華”才是具體的某個記者——我們把“華萊士”,“張寶華”稱作“記者”這個類的例項。

跟之前我們學過的,建立一個列表的方法類似——我們可以用初始化函式建立一個類。請大家在if __name__=="__main__":的下方寫下:

western=Journalist("Wallace")
print(western.name)

現在點選執行,可以看到print函式已經輸出了western物件的name。接下來讓我們嘗試一下,使用一個變數來儲存這個屬性的值:

name=western.get_name()
print(name)

下面由大家來完成:建立一個變數his_speed,獲得western物件的speed方法的返回值——方法的引數設定為100


程式碼:

class Journalist:
    """
    Take this as an example
    """
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

    def speed(self, speed):
        d = {}
        d[self.name] = speed
        return d

if __name__ == "__main__":
    western = Journalist("Wallace")
    print(western.name)
    
    name = western.get_name()
    print(name)
    
    his_speed = western.speed(100)
    print (his_speed)  

3、類的方法詳細介紹

剛才我們已經初步體會了一下如何使用我們自己定義的Journalist類。現在我們將會依次講解之前我們所使用的那些方法。
之前的程式碼如上面所示,大家可以先回顧一下。

建立例項的過程是呼叫類Person(),先執行初始化函式——上述例子中的初始化函式有兩個引數selfname。其中,self作為預設引數,雖然我們在定義方法的時候需要顯式地將其寫出來,但是在呼叫的時候是不需要給它傳值的。而name則需要傳值——Journalist('Wallace')的樣式,就是為初始化函式中的name引數傳值。

對於有 C++ 或者 JAVA 基礎的同學,這裡我們需要說明一下——上面我們用了“傳值”的說法,但是實際上這是不嚴謹的——Python只允許傳引用。在後面的課程中,我們將會詳細地對這裡進行講解。

呼叫建構函式會返回一個例項物件的引用——這裡我們將其儲存在變數girl裡。然後我們就可以通過girl來使用這個例項物件了。
在建立一個例項物件的過程中,Python 直譯器首先執行了__init__(),通過引數name得到例項的屬性self.name='Wallace'。這裡我們簡單介紹一下self的作用——它實際上就是我們所建立的例項物件本身。當大家用例項呼叫方法的時候,直譯器會把例項傳遞給方法,因此我們不需要顯式地給self傳參。
這樣,當我們使用print(western.name)的時候,輸出的就是western的屬性name的值。不光是可以直接輸出,我們還可以用另一個變數name將它儲存起來。

另外,在呼叫方法的時候,根據方法的定義,我們也可以通過給方法傳遞引數來改變例項物件的屬性的值——例如下面的his_speed=western.speed(100)

上面我們所介紹的,就是之前我們寫下的程式碼的含義——通過類建立一個例項物件,然後再通過例項來呼叫它的屬性和方法。

4、類的屬性與資料

這節課,我們將對關於類的屬性的一些詳細的細節進行展示。

現在我們仍然使用剛才的Journalist類——可以看到,這裡我們直接定義了一個變數name,然後直接給它賦值了。
現在,讓我們來在類外輸出一下這個變數:

print(Journalist.name)
main.py

現在點選執行,可以看到print函式已經輸出了Wallace,也就是我們給類屬性賦的值。這樣直接定義的變數,被稱為類的屬性,注意是類的屬性而不是例項物件的屬性

這個屬性由整個類所共有,並不依賴於某個例項物件。它還有一個名字叫做 靜態變數 或者 靜態資料。看到這個名字,學習過 C++ 的同學一定不會陌生。實際上,它等價於我們在 C++ 中,用這樣的寫法來寫:

#include<string>
using std::string;
class Journalist {
public:
    static string name;
};
Journalist::name = "Wallace";

接下來,讓我們在程式碼末尾新增以下新的程式碼:

Journalist.speed = 100
print(Journalist.speed)
del Journalist.name
print(Journalist.name)

點選執行,我們可以看到,Python 的類的屬性是非常靈活的——對於一個已經定義好的類,我們不僅可以在類外修改它的屬性,而且也可以新增和刪除新的屬性。當直譯器執行了del Journalist.name之後,原來的name屬性就被刪掉了,然後下一句print(Journalist.name)就報錯了。

接下來讓我們刪掉最後兩句程式碼,然後寫入以下程式碼:

hongkong = Journalist()
print(hongkong.name)

點選“執行”,大家可以看到print(hongkong.name)輸出的內容,跟print(Journalist.name)是一樣的。

給一個類的例項物件新增屬性,除了我們之前已經見過的,在初始化函式中使用self引數之外,我們還可以定義類屬性——這樣一來,新建的例項物件會自動繼承所有的類屬性。
需要注意的是,如果我們修改繼承自類屬性的例項屬性的話,那麼系統為我們新建一個同名的屬性,並“覆蓋”掉原有的屬性。

接下來大家來嘗試在最後輸入以下程式碼:

hongkong.name="Zhangbaohua"
print(hongkong.name)
print(Journalist.name)

點選“執行”,大家可以看到print(hongkong.name)輸出的內容被修改了,而print(Journalist.name)輸出的內容跟原來一樣。

如果我們執行del hongkong.name之後,再一次print(hongkong.name),並不會報錯,而是重新顯示Journalist.name屬性的內容——也就是說,如果我們刪除一個例項物件的屬性,並且這個屬性覆蓋了一個類屬性的話,那麼刪除之後這個類屬性就會“暴露”出來。

之前我們新增的屬性都是 Python 內建的不可變型別——如果是可變型別的話,情況會有些不一樣。現在大家在類定義中新增這樣一行程式碼:

 height=[]

這一次我們在類定義中追加了一個 Python 自帶的列表,它是一個可變資料型別。
現在讓我們再試試通過例項物件對其進行操作,然後分別輸出例項屬性和類屬性:

hongkong.height.append(160)
print(hongkong.height)
print(Journalist.height)

點選“執行”,觀察輸出結果可以發現,當類中變數引用的是可變物件時,類屬性和例項屬性都能直接修改這個物件,從而影響另一方的值。
此外,需要說明的是,我們已經知道如何給類增加屬性了———通過類增加的屬性可以影響到例項物件。類似地,我們也可以通過同樣的方法給例項物件新增屬性——但是給例項物件新增的屬性無法影響到類

程式碼:

class Journalist:
    """
    Take this as an example
    """
    name = 'Wallace'
    height =[]
    
print (Journalist.name) 

Journalist.speed =100
print (Journalist.speed)
hongkong = Journalist()
print (hongkong.name)

hongkong.name = "Zhangbaohua"
print (hongkong.name)
print (Journalist.name)

hongkong.height.append(160)
print (hongkong.height)
print (Journalist.height)

5、關於方法的更多細節

我們已經知道,類裡面除了屬性之外還有方法——當然也會有註釋和文件,但那是給其他程式設計師看的,直譯器在執行程式碼的時候會直接忽略它們。

通常情況下,我們要使用例項物件來呼叫方法——具體的方式在之前我們已經介紹過了。現在,讓我們來介紹一些其他的呼叫方法的方式。

現在讓我們考慮這樣一個類:

class Elder:
    def get_name(self):
        print("Toad")

在當前類定義下,當我們建立一個例項化物件之後,我們就可以用物件來呼叫方法:

he = Elder()
print(he.get_name())  # 輸出 Toad

在類中的方法,本質上其實是一個函式——只不過這個函式的第一個引數必須是self。這個self參數列示呼叫方法的例項物件。從這個角度講,方法跟函式並沒有本質上的區別。

我們使用例項物件呼叫方法的時候,Python 直譯器會把例項物件作為第一個引數傳給該方法——這一點我們之前給大家說過。實際上,我們還可以顯式地給方法傳參:

print(Elder.get_name(he))  # 輸出 Toad

還有另一類方法,並不以self為第一個引數——現在考慮之前定義的Journalist類:

class Journalist:
    """
    Take this as an example
    """
    name = 'Wallace'
    def __init__(self):
        self.name = "Zhangbaohua"
    @classmethod
    def get_class_name(cls):
        return cls.name

大家可以看到我們新定義了一個方法get_class_name,它只有一個引數cls,並沒有引數self,上面還有一個@classmethod標記修飾——這就是所謂的類方法。它有一個引數cls(這裡的引數叫什麼都可以),作用是返回引數的name屬性。作為類方法,它既可以被類本身呼叫,也可以被例項物件呼叫:

hongkong = Journalist()
print(Journalist.get_class_name())  # 輸出 Wallace
print(hongkong.get_class_name())  # 輸出 Zhangbaohua

除了類方法之外,還有另一種類似的方法:靜態方法。參見以下程式碼:
關於方法的更多細節 意見反饋
本節內容計 1 分,完成後得滿分
除了類方法之外,還有另一種類似的方法:靜態方法。參見以下程式碼:

import random
class Elder:
    def __init__(self):
        self.life = get_num()
    def get_name(self):
        print("Toad")

在類中使用了類外面的函式get_num()——類和類外函式的耦合,在編碼上並不是一種很好的習慣:這種做法給程式的維護帶來了潛在的麻煩。而所謂“靜態方法”,就是把get_num()這樣的函式放進類中

import random
class Elder:
    def __init__(self):
        self.life = get_num()
    def get_name(self):
        print("Toad")
    @staticmethod
    def get_num():
        a = random.randint(1926, 10000)
        return a

使用了@staticmethod裝飾器之後,現在get_num是函式的一個靜態方法——它實際上相當於一個獨立的函式,跟類本身並沒有什麼關係。換而言之,我們用一種簡單粗暴的方法解決了上面的問題——用靜態方法,把函式“包裝”進了類。

6、物件導向程式設計的其他概念

現在,我們對於 Python 中的物件導向程式設計,已經有了一個初步的概念。接下來讓我們來介紹一下物件導向程式設計的四個主要特點。在這門課裡,我們只對這些特性做最為簡單的介紹——對於物件導向程式設計的思想和方法,大家還需要更進一步的學習實踐才能充分理解。

抽象:

現實中,我們認識問題最基本的手段之一便是抽象——而在物件導向方法中的抽象,是指對於一系列具體問題進行概括,抽出一類物件的公共性質並且加以描述的過程。
一般來講,對一個問題的抽象應該分為兩個方面:資料抽象行為抽象。前者描述某類物件的特性和狀態,而後者則描述某類物件的功能或者行為特性。
此外,對於同一個研究物件,如果研究側重點不同的話,也可能產生不同的抽象結果——比如說,同樣是“人”這個類,如果存在於一個企業開發的人事管理軟體的話,它還會有更多的成員變數,比如部門、工齡、工資等等……

封裝:

封裝就是將抽象出的資料和行為(或者功能)相結合,形成一個有機的整體,也就是 類(class),其中的屬性和方法都是類的成員。
比如說,假設我們使用 C++ 語言來定義一個時鐘clock類,給出如下程式碼:

class Clock {
public:
    Clock();  // C++ 中的初始化函式,相當於 Python 中的 __init__
    void setTime(int newH,int newM,int newS);//在 C++ 和 JAVA 等語言中,函式定義需要寫明引數的型別和返回值型別,這一點跟 Python 不一樣
    void showTime();
private:
    int hour,minute,second;
};

大家可以注意到publicprivate關鍵字,有 C++ 或者 JAVA 基礎的同學不難明白——這個定義的效果是:外界的程式不能直接訪問三個成員變數,只能通過兩個函式來間接檢視或者修改三個變數的值。這樣做就可以實現對成員訪問許可權的合理控制,讓不同類之間的相互影響(耦合)減少到最低限度,進而增強資料的安全性,並簡化程式編寫工作。

舉個簡單的例子:還是對於Clock類,如果不做封裝的話外界的程式直接對三個變數進行訪問——假設有人故意要搞破壞,把表示時間的變數的值設定成一個根本不可能出現的值(比如設定成 999999 小時 999999 分鐘),這就可能讓程式出錯。而進行封裝之後,外界想要重新設定時間就必須通過setTime()函式——這樣我們就可以在函式中增加檢查輸入合法性的程式碼,避免不合法資料的惡意輸入。

作為一種典型的物件導向程式語言,Python 當然也支援資料的封裝,也就是“私有化”,並且寫法比 C++ 更加簡單。同樣是上面的Clock類,我們用 Python 來寫,就是這個樣子:

class Clock:
    def __init__(self):
        self.__hour = 0
        self.__minute = 0
        self.__second = 0
    def showTime(self):
        print(self.__hour, self.__minute, self.__second)

可以看到,我們只要在一個想要私有化的屬性前面加上雙下劃線__就可以將其私有化,等同於 C++ 中的private關鍵字。這樣一來,當我們想要在外界直接訪問私有屬性的話,就會報錯:

C:\DATA\GitHub\jisuankeWork\gitlab\python_programming\python3\new_add\oop_in_py (new_add)
λ python test.py
Traceback (most recent call last):
  File "test.py", line 9, in <module>
    print(c.__hour)
AttributeError: 'Clock' object has no attribute '__hour'

私有屬性只能通過類內的方法來訪問,這就是所謂“封裝”。不光是有私有屬性,我們同樣也可以定義私有方法,定義的方式跟私有屬性相同:

class Clock:
    def __init__(self):
        self.__hour=0
        self.__minute=0
        self.__second=0
    def showTime(self):
        print(self.__hour,self.__minute,self.__second)
    def __getSecond(self): # 私有方法
        return self.__second

繼承與多型:

現實生活中的概念具有特殊與一般的關係——比如一般意義上,“人”這個概念都有姓名、性別、年齡等屬性,以及吃飯、走路之類的行為,但是呢,按照職業劃分的話,“人”這個廣泛的概念又可以進一步地細分,比如學生、教師、工程師、醫生之類的。每一類人又有自己的特殊行為與屬性,比如學生的學號、專業、畢業之類的特殊屬性和行為,就是醫生這個群體所不具備的。

對於物件導向程式語言,繼承 的含義是,新建一個繼承了原有類的新類,具有原有類的所有特徵的同時,又具有自身的新特性。通過類的這種層次結構,可以很好地反映出特殊概念與一般概念的對應關係。通常我們把被其他類繼承的類叫做父類或者基類,而繼承自一個父類的類則稱為子類或者派生類

在 Python 語言中,繼承可以通過這樣的寫法實現:

class P:
    pass
class C(P):
    pass

C是繼承自類P的一個子類——如果父類定義了一些屬性和方法,那麼子類就會擁有父類定義的所有內容。這就是繼承的簡單理解。
現在大家已經知道,在 Python 中,一切都是物件——事實上,我們自己定義的任何一個類,都是繼承自 Python 中內建的基類object。比如說,上面我們定義的類P,我們可以在互動式命令列下,用內建方法__base__檢視它的基類:

>>> class P:
...     pass
...
>>> P.__base__
<class 'object'>
>>>

多型 則是物件導向程式設計,對於人類思維方式的一種直接模擬——從廣義上說,多型性 指的是同一段程式,可以直接處理多種型別物件的能力。

比如說,漢語中的“打球”,可以分解成動詞“打”和名詞“球”——“打”這個動作可以對應多種球類運動,比如打籃球、打排球、打乒乓球等等,而每一種球類運動都有著截然不同的動作和規則——這就是對多種運動行為的抽象,對應到物件導向程式設計中就是多型性。

熟悉 C/C++ 或 JAVA 語言的同學一定知道,在這些語言中,變數需要先宣告型別,然後才能使用。如下所示:

int i;
i = 1;

而大家已經知道,在 Python 中,我們是不需要先宣告一個變數的型別的:

i=1

當然,現代 C++ 也提供了一些語法糖來達到類似於 Python 的效果:

auto i = 1;  // 注意必須在定義變數時初始化,形如 auto i; 的寫法是不允許的

由此可見,從型別系統的角度來看,C++ 和 JAVA 屬於靜態型別語言,而 Python 是一種動態型別語言,變數的型別是在直譯器執行的時候通過型別推導所確定的(需要注意的是直譯器實際上仍然會對程式碼進行編譯)。
因此,有一種觀點認為,Python 這種語言天生就具有多型性

比如說,我們可以定義這樣一個函式:

def print_length(item):
    print(len(item))

然後我們就可以把任何一種可以用len求長度的物件傳給print_length函式,得到正確的結果。

print_length("123")  # 輸出 3
print_length([1,2])  # 輸出 2

如果是 C++ 之類的語言,在定義函式的時候需要用到函式過載或者模板機制,不過對於 Python 來說顯然就方便多了。

繼承與多型是物件導向程式設計的兩個高階特性——這一門課程中,我們只要求大家對物件導向程式設計的基本概念、以及這些概念在 Python 中的實際應用有一個基本的瞭解與掌握。因此,這兩個特性在我們這門課程中只做簡要介紹,更多的內容大家在後續《物件導向程式設計》等相關課程中學習。
儘管很多關於物件導向程式設計高階內容的課程使用的不是 Python,但其核心思想仍然是一樣的。學會了最核心的思想方法,就可以較容易地將其應用在 Python 程式設計中。

7、HR 管理(題目)

為了幫助 HR 姐姐減輕工作負擔,請你用 Python 語言設計一個People類,這個類應該包含以下私有屬性:
編號self.ID
姓名self.name
性別self.gender
工資self.salary
你需要定義例項方法set_ID(ID)set_name(name)set_salary(number),引數分別為員工的編號,姓名和工資。這些方法的作用是對員工物件相應的屬性進行修改。同時,類也應該可以使用初始化方法直接設定這些屬性

另外,你還需要定義一個show()方法,按格式輸出員工物件的編號,姓名和工資——具體輸出格式可以參見輸出樣例。

本題的初始化程式碼已經給出,你不能修改它們

預期的輸出如下所示:

2010201326 elder Male 301.0
19260817 he Male 301301.0

樣例輸出

2010201326 elder Male 301.0
19260817 he Male 301301.0

程式碼:

#類部分


if __name__=="__main__":
    p1=People("elder", "Male", "2010201326", 301.0)
    p1.show()
    p1.set_ID("19260817")
    p1.set_name("he")
    p1.set_salary(301301.0)
    p1.show()

​第八章 Python 中的函式

1、函式概念的複習

現在讓我們先來簡單地回憶一下,在 Python 中是如何定義函式的。

def max_pow(a, b):
    if a > b:
        pow_ab = a ** b
        return pow_ab
    pow_ba = b ** a
    return pow_ba

我們已經知道,def這行以下的所有語句都是屬於名為max_pow的這個接收了被定名為ab兩個函式的引數形式的函式的定義部分。我們看到,在定義部分我們用到了可以被傳入的ab
對於max_pow這個函式,我們可以通過max_pow(3, 2)的形式進行 呼叫(call),而在這裡的 3 和 2 則是被傳入給引數形式的 實際引數(actual parameter)。我們定義的max_pow這個函式將會用 3 代替 a ,而用 2 代替 b ,並執行函式中定義的一系列的語句。如果有需要繼續使用函式中的結果,我們則可以寫上return並在 return 語句後緊跟定義的 函式被呼叫後的返回結果(return value)

現在再讓我們來看一個例子:

def add(x,y):
    return x + y

這個函式會返回引數x和y進行加法運算的結果(注意這裡我沒有說求和),呼叫add(1, 2)會返回兩數相加的和3,顯然這對於大家來說應該是很好理解的。

然而如果呼叫的引數不是整數或者浮點數,比如add("jisuanke", "python")的話,也能正確進行運算嗎?答案當然也是肯定的:返回結果會是jisuankepython,即兩個字串直接連線的結果。

這就是我們之前介紹過的,程式語言的多型性的體現——Python 天生就是一種多型的語言,xy兩個引數變數,不管它們的型別是什麼,只要可以進行+運算,就可以返回正確的結果。

當然,前提條件是xy之間可以進行+運算——如果x是整數,而y是字串的話,那麼就會出錯。

另外,在互動式環境中定義了函式add之後,大家可以試試這樣輸入:

>>> type(add)
<class 'function'>
>>>

還記得上一章中,我們向大家說過,在 Python 中,一切都是物件 嗎?沒錯,函式本身也是一種物件。在後續課程中,我們會向大家介紹,函式作為物件的一些特有性質。

2、函式如何定義(題目)

在之前的章節中,我們對於 Python 語言中的函式已經有了一個初步的瞭解——現在請從下面的選項中,選出你認為正確的答案吧!

(1)Python 語言中,函式可以沒有引數

(2)Python 語言的函式必須有返回值

(3)函式是程式的一個子模組

(4)在軟體開發中應儘可能避免函式的使用

(5)若函式的兩個引數在函式體中執行加法運算,則引數型別必須為整數或浮點數

3、關於函式的命名

現在我們已經自己編寫了一些 Python 程式——此前我們已經接觸過了一些函式和變數的命名了。以下是 Python 程式設計中的一些對命名的常見要求:

變數:變數名全部小寫,由下劃線連線各個單詞,不能與 Python 的保留字衝突
檔名:小寫,可使用下劃線
函式名:小寫,可以使用下劃線,也可以使用首字母大寫的方式(除了第一個單詞首字母小寫之外,剩下的單詞首字母大寫)。

在函式定義的時候,我們也可以呼叫另一個函式:

def extend():
    print('excited!')
        plus_one_sec()

如果被呼叫的函式,之前並沒有被定義的話,那麼執行的時候就會報錯:

>>> def extend():
...     print('excited!')
...     plus_one_sec()
...
>>> extend()
excited!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in extend
NameError: name 'plus_one_sec' is not defined
>>>

學習過 C++ 等語言的同學,一定知道什麼叫做“前向引用宣告”——也就是說,如果我們要呼叫的函式存在迴圈引用問題,我們就必須把函式的宣告寫在最前面。而 Python 不需要這麼做——只要函式定義了,那麼無論先後,都可以使用。

這裡如果我們繼續在互動式環境中給出函式定義的話(哪怕實際上什麼都不做),我們也可以正常執行一開始定義的函式了:

>>> def plus_one_sec():
...     pass
...
>>> extend()
excited!
>>>

4、函式的返回值

這一節我們將會對函式的返回值進行一些講解。
大家之前已經見過了返回一個返回值(可以是變數也可以是物件),實際上函式也可以返回多個值。現在大家可以試一試輸入以下程式碼:

def multi_return():
    return 1, 2, 3
re_val = multi_return()
print(re_val)

現在點選執行,可以看到print函式已經輸出了(1,2,3)——這說明函式以元組的形式返回了這三個數。而對於同一個函式,我們還可以以不同的形式來接收返回值。

現在,大家把re_val改成三個變數x,y,z,然後print中的引數改成x

def multi_return():
    return 1, 2, 3
x,y,z = multi_return()
print(x)

點選執行,可以看到print輸出了x的值1。
最後,我們已經知道return可以返回一個或者幾個變數或物件的值,而除此之外return還有另一個作用。現在,大家把原來的程式碼都刪掉,然後輸入以下程式碼:

def func():
    print("part A")
    return
    print("part B")
func()

點選“執行”之後,大家可以注意到,程式執行的時候只輸出了part A,也就是說,在return 之後的語句並沒有被執行。從這裡我們可以看出這樣一件事情:

對於不加引數的 return 其作用是直接結束正在執行的函式,並離開函式體,返回到呼叫開始的位置

5、函式的物件性

雖然我們寫程式,寫的是給計算機執行的程式碼,但實際上,程式在大部分時間裡是給人看的。所以我們需要給函式寫文件。

寫文件的格式如下所示——一般寫在函式名字的下面,主要用於說明函式的用途。

def func():
    """
        func() help
    """
    pass

定義好文件之後,在互動式命令列中,我們可以直接使用func.__doc__檢視函式的文件。

大家是不是覺得這種寫法非常眼熟?沒錯——正如我們前面所說的那樣,函式本身也是Python 中的物件。而“文件”其實就是函式物件的一個屬性

任何物件都具有屬性,函式自然也不例外——我們甚至還可以給函式定義額外的屬性,就跟物件一樣。比如對於已經定義好的函式func,我們可以這樣給它增加一個新的屬性:

func.attr = 10

接下來我們就可以直接呼叫這個屬性了:

print(func.attr)  # 輸出 10

在 Python 中內建了一個函式dir,可以用來檢視一個物件具有哪些屬性和方法。而我們前面用過的__doc__屬性,跟其他的一些物件自帶屬性一樣,使用雙下劃線開頭——這些屬性稱之為特殊屬性。同樣,函式作為物件也是擁有一些自帶的特殊方法 的。

有興趣的同學可以自己呼叫dir函式,看看一個函式物件具有哪些內建的屬性和方法,然後通過查閱 Python 文件來搜尋這些方法的說明。

6、函式引數

現在讓我們先來回顧一下已經學過的函式定義:

def max_pow(a, b):
    if a > b:
        pow_ab = a ** b
        return pow_ab
    pow_ba = b ** a
    return pow_ba

在函式呼叫中,max_pow(3,2)中的3,2是這個函式的實際引數——而在函式的定義中,max_pow(a,b)中的a,b,則是函式的形式引數。在函式呼叫中,實際引數被傳遞到函式內部,這一過程被稱為形實結合

在函式呼叫的過程中,有的時候我們會遇到這樣一種情況:我們無法判斷函式會有多少個引數。這個時候該怎麼辦呢?Python 提供了一種非常方便的機制:*args引數。在接下來的課程中,我們將會親自體驗它的用法。

下面我們將會向大家介紹如何讓一個函式可以讀取不定個數的引數。我們現在有一個函式app,它的第一個引數是一個列表,後面的引數則是若干個變數。它的作用是把這些變數都加入到列表裡(呼叫列表的append方法)。

這裡我們要使用 Python 提供的*args引數——我們這樣來定義函式:

def app(ls, *args):

這一步大家先把函式的定義寫在程式碼上方的空白處。

接下來我們開始實現函式的功能——函式的作用是,把從第二個引數開始的每一個引數,都加入進第一個引數(列表)中。

我們可以這樣來實現這個功能:

for item in args:
    #在列表中插入元素

大家來自己試著寫一下吧!

def app(ls, *args):
    for item in args:
        ls.append(item)
ls = []
app(ls, 1, 2)
print(ls)
ls2 = []
app(ls2, 1, 2, 3)
print(ls2)

點選執行,可以看到輸出ls的內容變成了[1,2],而ls2的內容變成了[1,2,3]。

接下來讓我們來實現另一個函式——學過 C/C++ 語言的同學一定會對這個swap函式的寫法非常熟悉:

def swap(a, b):
    temp = a
    a = b
    b = temp

現在讓我們把這個函式寫在app函式的下方,然後在程式碼的末尾寫下它的呼叫:

a = 1
b = 2
swap(a, b)
print(a, b)

點選“執行”之後,大家可以注意到,變數並沒有被交換!

def app(ls, *args):
    for item in args:
        ls.append(item)
def swap(a, b):
    temp = a
    a = b
    b = temp
    
ls = []
app(ls, 1, 2)
print(ls)
ls2 = []
app(ls2, 1, 2, 3)
print(ls2)

a = 1
b = 2
swap(a, b)
print(a, b)

這就引出了一個關於 Python 的引數傳遞機制的問題——在下一節中,我們將會對 Python 中,函式的引數傳遞方法做詳細的介紹。

7、Python 中的引用傳參

C/C++ 等語言是沒有垃圾回收機制,由程式設計師自己來管理記憶體。 JAVA/C# 等,都通過更高層次的抽象,遮蔽了跟記憶體有關的底層細節,讓程式設計師能專注於程式的邏輯本身。而 Python 也是這樣的一種語言。

在剛才的例子中,我們已經看到,使用swap函式交換兩個變數的值的時候,如果宣告函式直接寫成swap(a,b)——在 C++ 中的寫法是swap(int a,int b)的話,那麼交換是不會產生效果的——儘管同樣的演算法直接放在主程式裡就能正確地完成交換操作(大家可以自行嘗試)。這就是因為,我們使用的是值傳遞 的傳參方式。

具體而言,值傳遞就是當函式發生呼叫的時候,給形參分配記憶體空間,然後用實參的值來初始化形參——這是一個單向傳遞的過程。簡而言之,當我們寫下swap(x,y);的時候,系統會新建兩個變數a,b,然後把x,y的值分別賦給它們。你可以想象成,當我們在main函式中呼叫了swap函式的時候,首先執行了這樣的兩個語句:

int a = x;  // 跟 Python 不同,C++ 是一種靜態型別語言,強制要求寫明變數型別
int b = y;

換句話說,接下來我們進行的操作,改變的只是這兩個新建的變數a,b的值——它們已經與原來的實參x,y脫離了關係,不管我們怎麼改動它們的值,最後都不會影響到x,y

當函式呼叫結束的時候,臨時新建的變數a,b都被系統銷燬了——它們的記憶體空間被回收。於是,我們的演算法雖然是正確的,但是卻做了無用功,沒有對實參x,y產生任何實際的影響。

我們已經看到,值傳遞時引數是單向傳遞,而現在我們想要做的是對主函式中定義的實參進行修改——這個時候,就需要使用引用傳遞了。

引用是一種特殊型別的變數,可以被認為是另一個變數的別名——通過引用名與通過被引用的變數名直接訪問變數的效果是完全一樣的。例如:

int i, j;
int &ri = i;  // 建立一個 int 型別的引用 ri,並將其初始化為 i 的一個別名
j = 10;
ri = j;  //相當於 i = j;

在 C++ 中,我們可以這樣地定義一個函式:

void swap(int &a, int &b);

這樣一來,接下來我們在swap函式的函式體內操作的就是int的引用型別a,b——這樣一來,我們對a,b的所有操作都會最終落實到主函式中實際存在的實參x,y頭上,然後swap函式就可以正常工作了。具體過程如下圖所示:

介紹完了 C++ 的概念,下面讓我們來回到 Python 之中:Python 採用的是一種“傳物件引用”的引數傳遞方式——我們是不能自己決定如何傳參的。這種所謂“傳物件引用”,簡單地說,大家可以認為這是上面介紹的值傳遞和引用傳遞的結合體:
(1)對於不可變型別的引數,比如數字、字元或者元組,那麼函式傳參的時候採用的就是相當於值傳遞的傳參方式——我們不能直接修改原始物件。
(2)對於可變型別的引數,比如列表,字典等,那麼傳參的方式就類似於引用傳遞,我們可以直接修改原始物件。

回憶我們剛才寫過的函式:

def app(ls, *args):
    for item in args:
        ls.append(item)

因為ls作為列表,是一個可變引數,所以我們就可以通過函式來直接修改它的內容。

最後,大家肯定會問——如果我就想交換兩個變數的值,那該怎麼辦呢?很簡單,在 Python 中我們根本就不需要大費周章地寫一個函式,直接這麼寫就可以了:

a, b = b, a

題目:

在之前的章節中,我們對預設引數值與函式過載的概念已經有了一個初步的瞭解——現在請從下面的選項中,選出你認為正確的答案吧!

(1)Python 允許程式設計師自己決定如何傳遞引數

(2)Python 函式通過“傳物件引用”傳遞引數

(3)Python 的函式在任何情況下都無法修改引數對應的原始物件

(4)Python 傳遞不可變型別時不能修改原物件

(5)Python 中除非手動定義中間變數,否則用任何方式都不能交換兩個不可變物件的值

8、函式的遞迴呼叫

我們已經知道,在一個函式中,我們可以呼叫另一個函式:

除了巢狀呼叫其他函式之外,函式還可以直接或者間接地呼叫自身——這種呼叫稱為遞迴呼叫,簡稱遞迴。大部分圖靈完備的程式語言都提供了遞迴呼叫這個非常重要的功能,Python 自然也不例外。

所謂直接呼叫自身,就是指在一個函式的函式體中出現了對自身的呼叫表示式,例如

def fun1:
    fun1()

而下面的例子則是函式間接呼叫自身:

def fun2:
    fun1()
def fun1:
    pass

這裡兩個函式互相呼叫,同樣也構成了遞迴。

在程式設計中,遞迴是一種極其重要的程式設計思想,它對應的是演算法設計中的一種方法——分治法。它的實質,是將原有的問題分解成一個或者若干個新的,規模比原來更小的問題,而解決新問題時又用到了原有問題的解法,按照這個原則繼續分解下去,最後可以得到一個平凡的、可以直接解出的子問題,於是遞迴就此結束——這就是有限的遞迴呼叫。

而無限的遞迴呼叫,跟死迴圈一樣,永遠都得不到解,是沒有實際意義的——而且由於遞迴呼叫執行機制的原因,遞迴次數太多還會導致程式出錯(這個我們將在後面的章節詳細介紹)。

遞迴的過程有如下兩個階段:第一階段稱為遞推,也就是將原問題不斷地分解為新的子問題(這一操作稱為遞推步),逐漸從未知向已知邁進,最後達到已知的,可以直接求解的終點,稱為遞迴基。比如說,現在我們要求一個數n的階乘n!,根據階乘的定義,我們可以得到這樣一個關係式:

n! = n*(n-1)!

同時,當n=0時,我們可以直接得到已知解,即:

0! = 1

這樣一來,我們就可以用遞迴來求解這個問題——上面的兩個關係式,前者即為遞推步,後者即為遞迴基。對於一個遞迴函式來說,這兩者都是必不可少的。第二階段稱為迴歸,為遞推的逆過程——從已知條件出發,逐一求值迴歸,最後達到遞推的開始點,結束迴歸階段,完成遞迴呼叫。

題目

我們對遞迴的概念已經有了一個初步的瞭解——現在請從下面的選項中,選出你認為正確的答案吧!

(1)自己定義的函式只能呼叫系統庫中的函式

(2)程式可以無限遞迴

(3)遞迴函式必須要有遞推步與遞迴基

(4)遞迴函式只能以直接呼叫函式自身的形式實現

(5)遞迴的實質是把問題分解為規模更小的子問題

9、用遞迴函式計算階乘

現在,讓我來試著寫一個求階乘的計算程式——在之前的課程中,我們已經向大家介紹了階乘計算的思路,接下來讓我們來試一試使用遞迴對階乘進行計算。

主函式已經寫好,現在讓我們來開始吧!首先,讓我們定義一個函式fac,引數是一個數字n。這一步大家只要先定義好函式就可以了,快來試試吧!

很好,接下來讓我們首先來看一看遞迴基——這裡我們使用if來進行判斷。我們知道,對於階乘運算來說,遞迴基就是0 != 1,所以進入遞迴基的條件自然就是輸入的n0了。

這裡我們先把if的框架寫好,注意不要寫錯了縮排關係哦!

贊一個,接下來讓我們來實現else的內容——遞迴計算階乘的遞推公式可以表示為:

n!=n×(n−1)!

讓我們用函式來實現它吧!請把else的框架寫好,並且直接在else中寫下遞推式,直接返回遞推式,快來試試吧!注意不要搞錯縮排哦!

程式碼:

def fac(n):
    if n == 0:
        return 1
    else:
        return n * fac(n - 1)
print('Enter a positive integer:')
n = int(input())
y = fac(n)
print(y)

10、頭遞迴與尾遞迴

現在我們已經知道,該如何使用遞迴來進行階乘計算了:

def factorial(int n):
    if n == 1:
        return 1
    else:
        return factorial(n - 1) * n

我們之前已經學過 遞迴呼叫(recursive call)。老師設計的上面這個函式就是一個遞迴實現的計算階乘的函式。在一般條件(如這裡的n == 1)滿足時,返回一個確定的值,而在其他情況下,返回一個包含本身函式的遞迴呼叫的這種遞迴設計,被我們稱為 頭遞迴(head recursion)。

與頭遞迴相對應,我們還有一個概念叫 尾遞迴(tail recursion)。如果我們用尾遞迴實現求階乘的函式,我們可以進行類似下面這樣的實現:

def factorial(n,product):
    if n == 0:
        return product
    product = product * n
    return factorial(n - 1, product)

如果我們傳入給函式的n5product1,我們將得到:

factorial(5,1)
	=factorial(4,5)
	=factorial(3,5∗4)
	=factorial(2,5∗4∗3)
	=factorial(1,5∗4∗3∗2)
	=factorial(0,5∗4∗3∗2∗1)
	=5∗4∗3∗2∗1

在這裡,我們可以對比一下頭遞迴和尾遞迴實現的函式從被呼叫到返回的過程。

頭遞迴:
在這裡插入圖片描述

在頭遞迴的實現中,我們在進行下一層的呼叫前,沒有進行計算。在下一層返回後,我們才完成了這一層的計算。

尾遞迴:
在這裡插入圖片描述

在尾遞迴的實現中,我們在進行下一層的呼叫前,會先進行計算,而在最終一般條件滿足時,會將計算的結果逐層直接返回。

11、Python 中的迭代器

在 Python 中,如果要訪問一個物件中的每一個元素,那麼可以這麼做:(以下以列表為例)

lst = ['j','i','s','u','a','n']
for i in lst:
    print(i, end = ' ')

這是我們已經知道的,基於for迴圈的方法。

除此之外,我們還可以這麼做:

lst_iter = iter(lst)
print(lst_iter.__next__())  # 輸出 j
print(lst_iter.__next__())  # 輸出 i
...
iter函式:

iter函式是 Python 中內建的系統函式之一——它的作用是返回一個迭代器物件。所有的迭代器物件,在 Python3 中都有__next__方法,其作用是取得下一個元素。實際上,for迴圈的實現,就是通過迭代器來完成的。

12、函式作為引數傳遞

讓我們來考慮這樣的一個問題:現在有一個列表,ls = [11, 23, 33],我們要對列表中的每一個元素,都進行這樣三種運算,然後返回一個新的列表。三種運算如下:

將列表中的每一個元素都求平方
將列表中的每一個元素(數字)轉換為字串
給每一個元素的值加上 1

大家首先想到的是寫三個for迴圈的函式來完成這些任務。然而仔細觀察的話,我們不難發現,這些函式的邏輯其實可以分為兩個部分:

遍歷每一個元素,對每一個元素都進行某種操作
具體執行這種操作,也就是上面三種運算其中之一

換句話說,我們其實是可以把“執行某種操作”的步驟抽象出來的——這樣的話,我們就不用複製貼上三遍相同的程式碼來實現“遍歷所有元素”的部分了。這也就是我們這一節要向大家介紹的內容——把函式作為引數傳遞。

現在,讓我們來首先定義第一個函式:

def func_seq(func, ls):

函式func一共定義了兩個引數——第一個引數func就是我們要傳遞的函式引數,而第二個引數ls則是要進行操作的列表。這裡需要說明的是,具體哪個引數代表什麼意義,是由你自己決定的,引數也可以隨便寫成任何名字。

接下來我們開始實現整個函式——這裡我們使用一種比較方便的寫法,直接返回一個列表:

return [func(i) for i in ls]

大家可以看到,在func_seq函式內呼叫func的過程跟在外界呼叫func相差無幾。
接下來讓我們定義具體的三種運算所對應的函式——把以下三個函式寫在func_seq下方:

def sqrt(num):
    return num ** 2
def to_str(num):
    return str(num)
def plus(num):
    return num + 1

函式作為引數傳遞 意見反饋
本節內容計 5 分,完成後得滿分
最後一步,我們可以呼叫這些定義好的函式了:

print(func_seq(sqrt, ls))
print(func_seq(to_str, ls))
print(func_seq(plus, ls))

把以上程式碼寫在ls下方。

大家可以看到,函式作為引數被另一個函式呼叫,寫法跟函式傳遞其他型別的引數,並沒有明顯的區別。

靈活運用這種機制,可以更好地降低重複勞動,提高程式可靠性——比如說,如果我們需要對某些更加複雜的資料結構進行遍歷的話,那麼一遍一遍地複製貼上就會變得非常麻煩,也很容易出錯。而如果我們把“遍歷”和“對元素執行操作”分別抽象成兩個不同的函式的話,那麼這樣一來就更加方便,也更加可靠了。

程式碼:

def func_seq(func, ls):
    return [func(i) for i in ls]
def sqrt(num):
    return num ** 2
def to_str(num):
    return str(num)
def plus(num):
    return num + 1
ls = [11, 23, 33]
print(func_seq(sqrt, ls))
print(func_seq(to_str, ls))
print(func_seq(plus, ls))

13、 lambda、map和reduce

在 Python 中,有一系列特殊的函式——它們的存在可以讓 Python 具備所謂函數語言程式設計的能力。函數語言程式設計是一種程式設計正規化,跟我們之前學習過的程式導向程式設計物件導向程式設計具有並列關係。

這裡,我們先不展開介紹關於函數語言程式設計的更多內容,只介紹這幾個特殊的函式:lambda,mapreduce。這一節先介紹lambda,之後再依次介紹mapreduce

lambda函式:

讓我們考慮這樣一段程式碼:

def func_seq(func, ls):
    return [func(i) for i in ls]

這就是我們之前定義的對列表進行操作的函式——對於比較複雜的運算來說,單獨定義一個func函式,顯然可以有效提高抽象層次,減少重複勞動。而如果我們要進行的運算比較簡單的話,再寫一個函式就有點麻煩了。為此 Python 提供了一種特殊的函式lambda

比如說,如果我們想要定義一個函式,返回引數的值的平方,那麼我們可以直接這樣寫:

sqrt= lambda x : x ** 2

然後我們就可以像呼叫普通函式一樣呼叫他:

print(sqrt(2))

lambda這種語言特性,很早就出現在了 Python 之中——其他的一些語言也隨著標準的更新,增加了 lambda 表示式的支援,比如 C++ 11、JAVA 8 和 C# 3.0 等。

在 Python 中,lambda函式的正式定義如下所示:

lambda arg1, args, ... argN: expressions_with_args

可見lambda函式可以接受任意多個引數。比如這樣:

func = lambda x, y : x + y  # 返回 x + y 的值

這裡需要說明的是,lambda包含的表示式不能超過一個——如果你想定義更復雜的東西,那最好還是定義一個普通函式。

map函式:

繼續讓我們考慮之前的程式碼:

def func_seq(func, ls):
    return [func(i) for i in ls]

我們已經知道,這個函式的作用是對一個列表中的每一個元素應用func函式,然後用得到的新元素建立一個新的列表,並直接返回。而接下來要介紹的map函式,實際上跟我們自己定義的func_seq的作用是一樣的——而 map 作為 Python 內建的函式,顯然給我們省下了很多麻煩。

map函式的使用方式,跟我們自己定義的func_seq函式是完全一樣的,語法如下所示:

map(function, iterable, ...)

第一個引數function表示一個函式,而iterable則表示一個可迭代物件(如列表)。

舉個簡單的例子——跟之前一樣,現在我們有一個列表,並定義了一個返回一個數加 1 的函式:

ls = [11,22,33]
plus = lambda x : x + 1

在 Python 3 中,當我們呼叫map函式的時候,我們會得到一個迭代器

print(map(plus, ls))  # <map object at 0x000001BF7F46C6A0>

當我們需要使用map的結果時,我們可以直接用list函式將其轉化為一個列表:

>>> list(map(plus, ls01)
[2, 2, 3, 4]

細心的同學這個時候也許會注意到,上面我們給出的map函式用法中,iterable後面有幾個省略號——事實上這也就意味著,map函式可以使用的可迭代物件引數個數並不是只有一個

我們可以參見這樣一段程式碼:

>>> def abc(a, b, c):
...     return a * 10000 + b * 100 + c
...
>>> list1 = [11, 22, 33]
>>> list2 = [44, 55, 66]
>>> list3 = [77, 88, 99]
>>> list(map(abc, list1, list2, list3))
[114477, 225588, 336699]

function的引數不止一個的時候,map函式將會從後面的可迭代物件裡依次選取相同下標的元素,然後“並行”應用於函式function。這裡的“並行”是 Python 官方文件原文的直譯。

事實上,map的function也可以是None——這個時候函式的作用,就是把多個列表相同位置的元素歸併到一個元組,如下所示:

>>> list1 = [11, 22, 33]
>>> list2 = [44, 55, 66]
>>> list3 = [77, 88, 99]
>>> list(map(None, list1, list2, list3))
[(11, 44, 77), (22, 55, 88), (33, 66, 99)]

reduce函式:

除了map之外,Python 中還有另一個類似的函式reduce——它也有兩個引數:一個函式f,一個list,但行為和 map()不同,reduce()傳入的函式 f 必須接收兩個引數reduce()list的每個元素反覆呼叫函式f,並返回最終結果值。

舉個簡單的例子——對於求和函式f:

def f(a, b):
    return a + b
reduce(f, [1, 3, 5, 7, 9]),將依次執行以下運算:

(1)先計算頭兩個元素:f(1, 3),結果為4;
(2)把結果和第33個元素計算:f(4, 5),結果為9;
(3)以此類推——f(9, 7),結果為16;
(4)f(16, 9),結果為25;
(5)所有元素都用過了,計算結束,返回結果25。

14、 漢諾塔問題 (題目)

漢諾塔(又稱河內塔)問題是源於印度一個古老傳說的益智玩具。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞著 64 片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤,如圖所示:hanoi.png

現在請試著編寫一個程式,對於一個有 nn 個盤子的漢諾塔,列舉將這 nn 個盤子從柱子 A 移動到柱子 C 需要的所有移動步驟,每個步驟佔一行。例如,將一個盤子從 A 移動到 C,即表示為:

A-->C

輸入格式
輸入一個整數 n(1≤n≤5)。

輸出格式
輸出若干行,表示所有移動步驟。

樣例輸入

3

樣例輸出

A-->C
A-->B
C-->B
A-->C
B-->A
B-->C
A-->C

在這裡插入圖片描述

相關文章