Python學習筆記(五)

第五章 函數(shù)和代碼復用

函數(shù)的基本使用

函數(shù)的定義

  • 函數(shù)是一段具有特定功能的、可重用的語句組,用函數(shù)名來表示并通過函數(shù)名進行完成功能調(diào)用。
  • 函數(shù)也可以看作是一段具有名字的子程序,可以在需要的地方調(diào)用執(zhí)行,不需要在每個執(zhí)行地方重復編寫這些語句。每次使用函數(shù)可以提供不同的參數(shù)作為輸入,以實現(xiàn)對不同數(shù)據(jù)的處理;函數(shù)執(zhí)行后,還可以反饋相應的處理結果。

Python定義一個函數(shù)使用def保留字,語法形式如下:

def <函數(shù)名>(<參數(shù)列表>):
   <函數(shù)體>
   return <返回值列表>

微實例5.1:生日歌。

過生日時要為朋友唱生日歌,歌詞為:
Happy birthday to you!
Happy birthday to you!
Happy birthday, dear <名字>
Happy birthday to you!
編寫程序為Mike和Lily輸出生日歌。最簡單的實現(xiàn)方法是重復使用print()語句

#需要打印所有變量(而不只是最后一個)
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
#重復使用print()語句
print("Happy birthday to you!")
print("Happy birthday to you!")
print("Happy birthday, dear Mike!")
print("Happy birthday to you!")

Happy birthday to you!
Happy birthday to you!
Happy birthday, dear Mike!
Happy birthday to you!
#使用函數(shù)
def happy():
    print("Happy birthday to you!")
def happyB(name):
    happy()
    happy()
    print("Happy birthday, dear {}!".format(name))
    happy()
happyB("Mike")
print()
happyB("Lily")

Happy birthday to you!
Happy birthday to you!
Happy birthday, dear Mike!
Happy birthday to you!

Happy birthday to you!
Happy birthday to you!
Happy birthday, dear Lily!
Happy birthday to you!

函數(shù)調(diào)用的過程

程序調(diào)用一個函數(shù)需要執(zhí)行以下四個步驟:
(1)調(diào)用程序在調(diào)用處暫停執(zhí)行;
(2)在調(diào)用時將實參復制給函數(shù)的形參;
(3)執(zhí)行函數(shù)體語句;
(4)函數(shù)調(diào)用結束給出返回值,程序回到調(diào)用前的暫停處繼續(xù)執(zhí)行。

lambda函數(shù)

Python的有33個保留字,其中一個是lambda,該保留字用于定義一種特殊的函數(shù)——匿名函數(shù),又稱lambda函數(shù)。
匿名函數(shù)并非沒有名字,而是將函數(shù)名作為函數(shù)結果返回,如下:
<函數(shù)名> = lambda <參數(shù)列表>: <表達式>
lambda函數(shù)與正常函數(shù)一樣,等價于下面形式:

def <函數(shù)名>(<參數(shù)列表>):
     return <表達式>

簡單說,lambda函數(shù)用于定義簡單的、能夠在一行內(nèi)表示的函數(shù),返回一個函數(shù)類型,實例如下。

f = lambda x, y : x + y
type(f)
f(10,12)
function






22

函數(shù)的參數(shù)傳遞

可選參數(shù)和可變數(shù)量參數(shù)

在定義函數(shù)時,有些參數(shù)可以存在默認值

def dup(str, times = 2):
        print(str*times)
dup("knock~")
dup("knock~",4)

knock~knock~
knock~knock~knock~knock~

在函數(shù)定義時,可以設計可變數(shù)量參數(shù),通過參數(shù)前增加星號(*)實現(xiàn)

def vfunc(a, *b):
        print(type(b))
        print(b)
        for n in b:
             a += n
        return a
vfunc(1,2,3,4,5)

<class 'tuple'>
(2, 3, 4, 5)





15

參數(shù)的位置和名稱傳遞

Python提供了按照形參名稱輸入實參的方式,調(diào)用如下:

result = func(x2=4, y2=5, z2=6, x1=1, y1=2, z1=3)

由于調(diào)用函數(shù)時指定了參數(shù)名稱,所以參數(shù)之間的順序可以任意調(diào)整。

變量的返回值

  • return語句用來退出函數(shù)并將程序返回到函數(shù)被調(diào)用的位置繼續(xù)執(zhí)行。
  • return語句同時可以將0個、1個或多個函數(shù)運算完的結果返回給函數(shù)被調(diào)用處的變量,例如。
def func(a, b):
    return a*b
s = func("knock~", 2)
print(s)

knock~knock~

函數(shù)可以沒有return,此時函數(shù)并不返回值,如微實例5.1的happy()函數(shù)。函數(shù)也可以用return返回多個值,多個值以元組類型保存,例如。

def func(a, b):
        return b,a
s = func("knock~", 2)
print(s, type(s))

(2, 'knock~') <class 'tuple'>

函數(shù)對變量的作用

一個程序中的變量包括兩類:全局變量和局部變量。

  • 全局變量指在函數(shù)之外定義的變量,一般沒有縮進,在程序執(zhí)行全過程有效。
  • 局部變量指在函數(shù)內(nèi)部使用的變量,僅在函數(shù)內(nèi)部有效,當函數(shù)退出時變量將不存在。
n = 1    #n是全局變量
def func(a, b):
    c = a * b     #c是局部變量,a和b作為函數(shù)參數(shù)也是局部變量
    return c
s = func("knock~", 2)
print(c)

---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-12-b329afe441ac> in <module>()
      4     return c
      5 s = func("knock~", 2)
----> 6 print(c)


NameError: name 'c' is not defined

這個例子說明,當函數(shù)執(zhí)行完退出后,其內(nèi)部變量將被釋放。如果函數(shù)內(nèi)部使用全局變量呢?

n = 1    #n是全局變量
def func(a, b):
    n = b     #這個n是在函數(shù)內(nèi)存中新生成的局部變量,不是全局變量 
    return a*b
s = func("knock~", 2)
print(s, n)  #測試一下n值是否改變

knock~knock~ 1

函數(shù)func()內(nèi)部使用了變量n,并且將變量參數(shù)b賦值給變量n,為何全局變量n值沒有改變?
如果希望讓func()函數(shù)將n當作全局變量,需要在變量n使用前顯式聲明該變量為全局變量,代碼如下。

n = 1    #n是全局變量
def func(a, b):
    global n
    n = b     #將局部變量b賦值給全局變量n 
    return a*b
s = func("knock~", 2)
print(s, n)  #測試一下n值是否改變

knock~knock~ 2

如果此時的全局變量不是整數(shù)n,而是列表類型ls,會怎么樣呢?理解如下代碼。

ls = []    #ls是全局列表變量
def func(a, b):
    ls.append(b)   #將局部變量b增加到全局列表變量ls中 
    return a*b
s = func("knock~", 2)
print(s, ls)  #測試一下ls值是否改變

knock~knock~ [2]

如果func()函數(shù)內(nèi)部存在一個真實創(chuàng)建過且名稱為ls的列表,則func()將操作該列表而不會修改全局變量,例子如下。

ls = []    #ls是全局列表變量
def func(a, b):
    ls = []     #創(chuàng)建了名稱為ls的局部列表變量列
    ls.append(b)   #將局部變量b增加到全局列表變量ls中 
    return a*b
s = func("knock~", 3)
print(s, ls)  #測試一下ls值是否改變

knock~knock~knock~ []

Python函數(shù)對變量的作用遵守如下原則:

  • 簡單數(shù)據(jù)類型變量無論是否與全局變量重名,僅在函數(shù)內(nèi)部創(chuàng)建和使用,函數(shù)退出后變量被釋放;
  • 簡單數(shù)據(jù)類型變量在用global保留字聲明后,作為全局變量;
  • 對于組合數(shù)據(jù)類型的全局變量,如果在函數(shù)內(nèi)部沒有被真實創(chuàng)建的同名變量,則函數(shù)內(nèi)部可直接使用并修改全局變量的值;
  • 如果函數(shù)內(nèi)部真實創(chuàng)建了組合數(shù)據(jù)類型變量,無論是否有同名全局變量,函數(shù)僅對局部變量進行操作。

datetime庫的使用

datetime庫概述

以不同格式顯示日期和時間是程序中最常用到的功能。Python提供了一個處理時間的標準函數(shù)庫datetime,它提供了一系列由簡單到復雜的時間處理方法。datetime庫可以從系統(tǒng)中獲得時間,并以用戶選擇的格式輸出。
datetime庫以類的方式提供多種日期和時間表達方式:

  • datetime.date:日期表示類,可以表示年、月、日等
  • datetime.time:時間表示類,可以表示小時、分鐘、秒、毫秒等
  • datetime.datetime:日期和時間表示的類,功能覆蓋date和time類
  • datetime.timedelta:時間間隔有關的類
  • datetime.tzinfo:與時區(qū)有關的信息表示類

datetime庫解析

使用datetime.now()獲得當前日期和時間對象,使用方法如下:

 datetime.now()

作用:返回一個datetime類型,表示當前的日期和時間,精確到微秒。

from datetime import datetime
today = datetime.now()
today

datetime.datetime(2017, 7, 16, 15, 29, 51, 458207)

也可以直接使用datetime()構造一個日期和時間對象,使用方法如下:

datetime(year, month, day, hour=0, minute=0,second=0, microsecond=0)

作用:返回一個datetime類型,表示指定的日期和時間,可以精確到微秒。

#調(diào)用datetime()函數(shù)直接創(chuàng)建一個datetime對象,表示2016年9月16日22:33,32秒7微秒
someday = datetime(2016,9,16,22,33,32,7)
someday
#datetime對象有3個常用的時間格式化方法
someday.isoformat()
someday.isoweekday()
someday.strftime("%Y-%m-%d %H:%M:%S")

datetime.datetime(2016, 9, 16, 22, 33, 32, 7)






'2016-09-16T22:33:32.000007'






5






'2016-09-16 22:33:32'

strftime()格式化字符串的數(shù)字左側會自動補零,上述格式也可以與print()的格式化函數(shù)一起使用

from datetime import datetime
now = datetime.now()
now.strftime("%Y-%m-%d")
now.strftime("%A, %d. %B %Y %I:%M%p")
print("今天是{0:%Y}年{0:%m}月{0:%d}日".format(now))

'2017-07-16'






'Sunday, 16. July 2017 03:34PM'



今天是2017年07月16日

七段數(shù)碼管繪制

七段數(shù)碼管(seven-segment indicator)由7段數(shù)碼管拼接而成,每段有亮或不亮兩種情況,改進型的七段數(shù)碼管還包括一個小數(shù)點位置。
七段數(shù)碼管能形成27=128種不同狀態(tài),其中部分狀態(tài)能夠顯示易于人們理解的數(shù)字或字母含義,因此被廣泛使用
每個0到9的數(shù)字都有相同的七段數(shù)碼管樣式,因此,可以通過設計函數(shù)復用數(shù)字的繪制過程。進一步,每個七段數(shù)碼管包括7個數(shù)碼管樣式,除了數(shù)碼管位置不同外,繪制風格一致,也可以通過函數(shù)復用單個數(shù)碼段的繪制過程。

import turtle, datetime
def drawLine(draw):   #繪制單段數(shù)碼管
    turtle.pendown() if draw else turtle.penup()
    turtle.fd(40)
    turtle.right(90)
def drawDigit(d): #根據(jù)數(shù)字繪制七段數(shù)碼管
    drawLine(True) if d in [2,3,4,5,6,8,9] else drawLine(False)
    drawLine(True)  if d in [0,1,3,4,5,6,7,8,9] else drawLine(False)
    drawLine(True) if d in [0,2,3,5,6,8,9] else drawLine(False)
    drawLine(True) if d in [0,2,6,8] else drawLine(False)
    turtle.left(90)
    drawLine(True) if d in [0,4,5,6,8,9] else drawLine(False)
    drawLine(True) if d in [0,2,3,5,6,7,8,9] else drawLine(False)
    drawLine(True) if d in [0,1,2,3,4,7,8,9] else drawLine(False)
    turtle.left(180)
    turtle.penup()
    turtle.fd(20) 
def drawDate(date):  #獲得要輸出的數(shù)字
    for i in date:
        drawDigit(eval(i))  #注意: 通過eval()函數(shù)將數(shù)字變?yōu)檎麛?shù)
def main():
    turtle.setup(800, 350, 200, 200)
    turtle.penup()
    turtle.fd(-300)
    turtle.pensize(5)
    drawDate(datetime.datetime.now().strftime('%Y%m%d'))
    turtle.hideturtle()
main()

改進的代碼:

import turtle, datetime
def drawGap(): #繪制數(shù)碼管間隔
    turtle.penup()
    turtle.fd(5)
def drawLine(draw):   #繪制單段數(shù)碼管
    drawGap()
    turtle.pendown() if draw else turtle.penup()
    turtle.fd(40)
    drawGap()    
    turtle.right(90)
def drawDigit(d): #根據(jù)數(shù)字繪制七段數(shù)碼管
    drawLine(True) if d in [2,3,4,5,6,8,9] else drawLine(False)
    drawLine(True) if d in [0,1,3,4,5,6,7,8,9] else drawLine(False)
    drawLine(True) if d in [0,2,3,5,6,8,9] else drawLine(False)
    drawLine(True) if d in [0,2,6,8] else drawLine(False)
    turtle.left(90)
    drawLine(True) if d in [0,4,5,6,8,9] else drawLine(False)
    drawLine(True) if d in [0,2,3,5,6,7,8,9] else drawLine(False)
    drawLine(True) if d in [0,1,2,3,4,7,8,9] else drawLine(False)
    turtle.left(180)
    turtle.penup()
    turtle.fd(20) 
def drawDate(date):
    turtle.pencolor("red")
    for i in date:
        if i == '-':
            turtle.write('年',font=("Arial", 18, "normal"))
            turtle.pencolor("green")
            turtle.fd(40) 
        elif i == '=':
            turtle.write('月',font=("Arial", 18, "normal"))
            turtle.pencolor("blue")
            turtle.fd(40)
        elif i == '+':
            turtle.write('日',font=("Arial", 18, "normal"))
        else:
            drawDigit(eval(i))
def main():
    turtle.setup(800, 350, 200, 200)
    turtle.penup()
    turtle.fd(-350)
    turtle.pensize(5)
    drawDate(datetime.datetime.now().strftime('%Y-%m=%d+'))
    turtle.hideturtle()
main()

代碼的復用和模塊化設計

函數(shù)是程序的一種基本抽象方式,它將一系列代碼組織起來通過命名供其他程序使用。函數(shù)封裝的直接好處是代碼復用,任何其他代碼只要輸入?yún)?shù)即可調(diào)用函數(shù),從而避免相同功能代碼在被調(diào)用處重復編寫。代碼復用產(chǎn)生了另一個好處,當更新函數(shù)功能時,所有被調(diào)用處的功能都被更新。
當程序的長度在百行以上,如果不劃分模塊就算是最好的程序員也很難理解程序含義程序的可讀性就已經(jīng)很糟糕了。解決這一問題的最好方法是將一個程序分割成短小的程序段,每一段程序完成一個小的功能。無論面向過程和面向?qū)ο缶幊蹋瑢Τ绦蚝侠韯澐止δ苣K并基于模塊設計程序是一種常用方法,被稱為“模塊化設計”。
模塊化設計一般有兩個基本要求:

  • 緊耦合:盡可能合理劃分功能塊,功能塊內(nèi)部耦合緊密;
  • 松耦合:模塊間關系盡可能簡單,功能塊之間耦合度低。

使用函數(shù)只是模塊化設計的必要非充分條件,根據(jù)計算需求合理劃分函數(shù)十分重要。一般來說,完成特定功能或被經(jīng)常復用的一組語句應該采用函數(shù)來封裝,并盡可能減少函數(shù)間參數(shù)和返回值的數(shù)量。

函數(shù)的遞歸

遞歸的定義

函數(shù)作為一種代碼封裝,可以被其他程序調(diào)用,當然,也可以被函數(shù)內(nèi)部代碼調(diào)用。這種函數(shù)定義中調(diào)用函數(shù)自身的方式稱為遞歸。就像一個人站在裝滿鏡子的房間中,看到的影像就是遞歸的結果。遞歸在數(shù)學和計算機應用上非常強大,能夠非常簡潔的解決重要問題。
數(shù)學上有個經(jīng)典的遞歸例子叫階乘。
階乘的例子揭示了遞歸的2個關鍵特征:
(1)存在一個或多個基例,基例不需要再次遞歸,它是確定的表達式;
(2)所有遞歸鏈要以一個或多個基例結尾。

遞歸的使用方法

微實例5.21:階乘的計算。

根據(jù)用戶輸入的整數(shù)n,計算并輸出n的階乘值。

def fact(n):
    if n == 0:
        return 1
    else:
        return n * fact(n-1)
num = eval(input("請輸入一個整數(shù): "))
print(fact(abs(int(num))))

請輸入一個整數(shù): 6
720

微實例5.32:字符串反轉(zhuǎn)。

對于用戶輸入的字符串s,輸出反轉(zhuǎn)后的字符串。
解決這個問題的基本思想是把字符串看作一個遞歸對象。

def reverse(s):
    return reverse(s[1:]) + s[0]
reverse("ABC")

---------------------------------------------------------------------------

RecursionError                            Traceback (most recent call last)

<ipython-input-33-74f5c92adb66> in <module>()
      1 def reverse(s):
      2     return reverse(s[1:]) + s[0]
----> 3 reverse("ABC")


<ipython-input-33-74f5c92adb66> in reverse(s)
      1 def reverse(s):
----> 2     return reverse(s[1:]) + s[0]
      3 reverse("ABC")


... last 1 frames repeated, from the frame below ...


<ipython-input-33-74f5c92adb66> in reverse(s)
      1 def reverse(s):
----> 2     return reverse(s[1:]) + s[0]
      3 reverse("ABC")


RecursionError: maximum recursion depth exceeded

錯誤的原因是沒有基例,修改如下:

def reverse(s):
    if s == "":
        return s
    else:
        return reverse(s[1:]) + s[0]
reverse("ABC")
'CBA'

觀察這個函數(shù)的工作過程。s[0]是首字符,s[1:]是剩余字符串,將它們反向連接,可以得到反轉(zhuǎn)字符串。

科赫曲線繪制

自然界有很多圖形很規(guī)則,符合一定的數(shù)學規(guī)律,例如,蜜蜂蜂窩是天然的等邊六角形等。科赫(Koch)曲線在眾多經(jīng)典數(shù)學曲線中非常著名,由瑞典數(shù)學家馮·科赫(H·V·Koch)于1904年提出,由于其形狀類似雪花,也被稱為雪花曲線。
科赫曲線的基本概念和繪制方法如下:
正整數(shù)n代表科赫曲線的階數(shù),表示生成科赫曲線過程的操作次數(shù)。科赫曲線初始化階數(shù)為0,表示一個長度為L的直線。對于直線L,將其等分為三段,中間一段用邊長為L/3的等邊三角形的兩個邊替代,得到1階科赫曲線,它包含四條線段。進一步對每條線段重復同樣的操作后得到2階科赫曲線。繼續(xù)重復同樣的操作n次可以得到n階科赫曲線。
科赫曲線屬于分形幾何分支,它的繪制過程體現(xiàn)了遞歸思想,繪制過程代碼。

import turtle
def koch(size, n):
    if n == 0:
        turtle.fd(size)
    else:
        for angle in [0, 60, -120, 60]:
           turtle.left(angle)
           koch(size/3, n-1) 
def main():
    turtle.setup(800,400)
    turtle.speed(0)  #控制繪制速度
    turtle.penup()
    turtle.goto(-300, -50)
    turtle.pendown()
    turtle.pensize(2)
    koch(600,3)     # 0階科赫曲線長度,階數(shù)
    turtle.hideturtle()
main()

科赫曲線的雪花效果

import turtle
def koch(size, n):
    if n == 0:
        turtle.fd(size)
    else:
        for angle in [0, 60, -120, 60]:
           turtle.left(angle)
           koch(size/3, n-1) 
def main():
    turtle.setup(600,600)
    turtle.speed(0)
    turtle.penup()
    turtle.goto(-200, 100)
    turtle.pendown()
    turtle.pensize(2)
    level = 5
    koch(400,level)
    turtle.right(120)
    koch(400,level)
    turtle.right(120)
    koch(400,level)
    turtle.hideturtle()
main()

Python內(nèi)置函數(shù)

Python解釋器提供了68個內(nèi)置函數(shù),其中,前36個已經(jīng)或?qū)⒃谡n程中出現(xiàn),需要掌握。

ls = [1,2,5,0]
all(ls)
any(ls)
hash("中國,你好")
id(ls)
id("中國,你好")
False






True






-1553274804152303886






1965726597064






1965750150104
list(reversed(ls))
sorted(ls)
ls
sorted(ls,reverse=True)
[0, 5, 2, 1]






[0, 1, 2, 5]






[1, 2, 5, 0]






[5, 2, 1, 0]

練習題


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容