python程式設計:從入門到實踐 (第一版) 第八章學習筆記

gh0stf1re發表於2020-12-27

第8章:函式

8.1 定義函式

def greet_user():
	"""顯示簡單的問候語"""
	print("Hello!")
greet_user()
8.1.1 向函式傳遞資訊
def greet_user(username):
	"""顯示簡單的問候語"""
	print("Hello, " + username.title() + ".")

greet_user('bruce')
8.1.2 形參和實參

在函式greet_user() 的定義中,變數username 是一個形參 ——函式完成其工作所需的一項資訊。在程式碼greet_user(‘bruce’) 中,值’bruce’ 是一個實參 。實參是呼叫函式時傳遞給函式的資訊。我們呼叫函式時,將要讓函式使用的資訊放在括號內。在greet_user(‘bruce’) 中,將實參’bruce’ 傳遞給了函式greet_user() ,這個值被儲存在形參username 中。

8.2 傳遞實參

鑑於函式定義中可能包含多個形參,因此函式呼叫中也可能包含多個實參。向函式傳遞實參的方式很多,可使用位置實參 ,這要求實參的順序與形參的順序相同;也可使用關鍵字實參 ,其中每個實參都由變數名和值組成;還可使用列表和字典。下面來依次介紹這些方式。

8.2.1 位置實參
示例
def discribe_pet(animal_type, pet_name):
	"""顯示寵物資訊"""
	print("I have a " + animal_type +".")
	print("My " + animal_type + "'s name is " + pet_name.title() + ".")

discribe_pet('cat', 'tom')

輸出:

I have a cat.
My cat's name is Tom.
呼叫函式多次
def discribe_pet(animal_type, pet_name):
	"""顯示寵物資訊"""
	print("I have a " + animal_type +".")
	print("My " + animal_type + "'s name is " + pet_name.title() + ".")

discribe_pet('cat', 'tom')
discribe_pet('mouse', 'jerry')
位置實參的順序很重要

使用位置實參來呼叫函式時,如果實參的順序不正確,結果可能出乎意料:

def discribe_pet(animal_type, pet_name):
	"""顯示寵物資訊"""
	print("I have a " + animal_type +".")
	print("My " + animal_type + "'s name is " + pet_name.title() + ".")

discribe_pet('tom', 'cat')

輸出:

I have a tom.
My tom's name is Cat.
8.2.2 關鍵字實參

關鍵字實參是傳遞給函式的名稱—值對。你直接在實參中將名稱和值關聯起來了,因此向函式傳遞實參時不會混淆

def discribe_pet(animal_type, pet_name):
	"""顯示寵物資訊"""
	print("I have a " + animal_type +".")
	print("My " + animal_type + "'s name is " + pet_name.title() + ".")

discribe_pet(animal_type = 'cat', pet_name = 'tom')
print("=====================")
discribe_pet(pet_name = 'tom', animal_type = 'cat')

輸出:

I have a cat.
My cat's name is Tom.
=====================
I have a cat.
My cat's name is Tom.

關鍵字實參的順序無關緊要,因為Python知道各個值該儲存到哪個形參中。由上面的結果可以看到,下面兩個函式呼叫是等價的:

discribe_pet(animal_type = 'cat', pet_name = 'tom')
discribe_pet(pet_name = 'tom', animal_type = 'cat')
8.2.3 預設值

編寫函式時,可給每個形參指定預設值 。在呼叫函式中給形參提供了實參時,Python將使用指定的實參值。

def describe_pet(pet_name, animal_type='dog'):
    """顯示寵物的資訊"""
    print("\nI have a " + animal_type + ".")
    print("My " + animal_type + "'s name is " + pet_name.title() + ".")
    
describe_pet(pet_name='willie')

輸出:

I have a dog.
My dog's name is Willie.

如果要描述的動物不是小狗,可使用類似於下面的函式呼叫:

describe_pet(pet_name='jerry', animal_type='mouse')

由於顯式地給animal_type 提供了實參,因此Python將忽略這個形參的預設值。
注意: 使用預設值時,在形參列表中必須先列出沒有預設值的形參,再列出有預設值的實參。這讓Python依然能夠正確地解讀位置實參。

8.2.4 等效的函式呼叫

鑑於可混合使用位置實參、關鍵字實參和預設值,通常有多種等效的函式呼叫方式。請看下面的函式describe_pets() 的定義,其中給一個形參提供了預設值:

def describe_pet(pet_name, animal_type='dog'):

幾種呼叫的示例:

# 一條名為Willie的小狗
describe_pet('willie')
describe_pet(pet_name='willie')
# 一隻名為Harry的倉鼠
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
8.2.5 避免實參錯誤

你提供的實參多於或少於函式完成其工作所需的資訊時,將出現實參不匹配錯誤。這時python會報錯,可以根據traceback 來分析

8.3 返回值

函式並非總是直接顯示輸出,相反,它可以處理一些資料,並返回一個或一組值。函式返回的值被稱為返回值 。在函式中,可使用return 語句將值返回到呼叫函式的程式碼行。返回值讓你能夠將程式的大部分繁重工作移到函式中去完成,從而簡化主程式。

8.3.1 返回簡單值
def get_formatted_name(first_name, last_name):
    """返回整潔的姓名"""
    full_name = first_name + " " + last_name
    return full_name.title()

musician = get_formatted_name('bruce', 'zhang')
print(musician)
8.3.2 讓實參變成可選的

有時候,需要讓實參變成可選的,這樣使用函式的人就只需在必要時才提供額外的資訊。可使用預設值來讓實參變成可選的。
例如,假設我們要擴充套件函式get_formatted_name() ,使其還處理中間名。為此,可將其修改成類似於下面這樣:

def get_formatted_name(first_name, last_name, middle_name=''):
    """返回整潔的姓名""" 
    if middle_name:
        full_name = first_name + ' ' + middle_name + ' ' + last_name 
    else:
        full_name = first_name + ' ' + last_name
    return full_name.title()

musician = get_formatted_name('jimi', 'hendrix')
print(musician)
musician = get_formatted_name('john', 'hooker', 'lee')
print(musician)

呼叫這個函式時,如果只想指定名和姓,呼叫起來將非常簡單。如果還要指定中間名,就必須確保它是最後一個實參,這樣Python才能正確地將位置實參關聯到形參。

8.3.3 返回字典

函式可返回任何型別的值,包括列表和字典等較複雜的資料結構。

def build_person(first_name, last_name):
    person = {'first': first_name, 'last': last_name}
    return person

musician = build_person('bruce', 'zhang')
print(musician)

你可以輕鬆地擴充套件這個函式,使其接受可選值,如中間名、年齡、職業或你要儲存的其他任何資訊。例如,下面的修改讓你還能儲存年齡:

def build_person(first_name, last_name, age=''):
    person = {'first': first_name, 'last': last_name}
    if age:
        person['age'] = age
    return person

musician = build_person('bruce', 'zhang', 22)
print(musician)
8.3.4 結合使用函式和while 迴圈
def get_formatted_name(first_name, last_name):
    """返回整潔的姓名"""
    full_name = first_name.title() + " " + last_name.title()
    return full_name

while True:
    print("Please tell me your name: ")
    print("Enter 'q' anytime to quit!")
    f_name = input("First_name: ")
    if f_name == 'q':
        break
    l_name = input("last_name: ")
    if l_name == 'q':
        break
    formatted_name = get_formatted_name(f_name, l_name)
    print(formatted_name)

8.4 傳遞列表

假設有一個使用者列表,我們要問候其中的每位使用者

def greet_users(names):
    """向列表中的每位使用者都發出簡單的問候"""
    for name in names:
        print("Hello, " + name.title() + ",welcome to you.")
    
names = ['tom', 'jack', 'bruce']
greet_users(names)
8.4.1 在函式中修改列表

來看一家為使用者提交的設計製作3D列印模型的公司。需要列印的設計儲存在一個列表中,列印後移到另一個列表中。

# 首先建立一個列表,其中包含一些要列印的設計
unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron']
completed_models = []
# 模擬列印每個設計,直到沒有未列印的設計為止
# 列印每個設計後,都將其移到列表completed_models中
while unprinted_designs:
    current_design = unprinted_designs.pop()
    #模擬根據設計製作3D列印模型的過程
    print("Printing model: " + current_design)
    completed_models.append(current_design)
# 顯示列印好的所有模型
print("\nThe following models have been printed:")
for completed_model in completed_models:
    print(completed_model)

為重新組織這些程式碼,我們可編寫兩個函式,每個都做一件具體的工作。第一個函式將負責處理列印設計的工作,而第二個將概述列印了哪些設計:

def print_models(unprinted_designs, completed_models):
    """# 列印每個設計後,都將其移到列表completed_models中"""
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        #模擬根據設計製作3D列印模型的過程
        print("Printing model: " + current_design)
        completed_models.append(current_design)

def show_completed_models(completed_models):
    """顯示列印好的所有模型"""
    for completed_model in completed_models:
        print(completed_model)

unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
8.4.2 禁止函式修改列表

你可能會做出這樣的決定:即便列印所有設計後,也要保留原來的未列印的設計列表,以供備案。
要將列表的副本傳遞給函式,可以像下面這樣做:

function_name(list_name[:])

在8.4.1中,如果不想清空未列印的設計列表,可像下面這樣呼叫print_models() :

print_models(unprinted_designs[:], completed_models)

8.5 傳遞任意數量的實參

有時候,你預先不知道函式需要接受多少個實參,好在Python允許函式從呼叫語句中收集任意數量的實參。

def make_pizza(*toppings):
    """列印顧客點的所有配料"""
    print(toppings)

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

輸出:

('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')

形參名*toppings 中的星號讓Python建立一個名為toppings 的空元組,並將收到的所有值都封裝到這個元組中。

def make_pizza(*toppings):
    """列印顧客點的所有配料"""
    print("\nMaking a pizza with the following toppings:")
    for topping in toppings:
        print("-" + topping)

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
8.5.1 結合使用位置實參和任意數量實參

如果要讓函式接受不同型別的實參,必須在函式定義中將接納任意數量實參的形參放在最後。Python先匹配位置實參和關鍵字實參,再將餘下的實參都收集到最後一個形參中。
如果前面的函式還需要一個表示比薩尺寸的實參,必須將該形參放在形參*toppings 的前面:

def make_pizza(size, *toppings):
    """列印顧客點的所有配料"""
    print("\nMaking a " + str(size) +"-inch pizza with the following toppings:")
    for topping in toppings:
        print("-" + topping)

make_pizza(9, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
8.5.2 使用任意數量的關鍵字實參

有時候,需要接受任意數量的實參,但預先不知道傳遞給函式的會是什麼樣的資訊。在這種情況下,可將函式編寫成能夠接受任意數量的鍵—值對——呼叫語句提供了多少就接受多少。

def build_profile(first, last, **user_profile):
    """建立一個字典,其中包含我們知道的有關使用者的一切"""
    profile = {}
    profile['first'] = first
    profile['last'] = last
    for key, value in user_profile.items():
        profile[key] = value
    return profile

user_profile = build_profile('albert', 'einstein', location='princeton', field='physics')
print(user_profile)

形參**user_info 中的兩個星號讓Python建立一個名為user_info 的
空字典,並將收到的所有名稱—值對都封裝到這個字典中。

8.6 將函式儲存在模組中

函式的優點之一是,使用它們可將程式碼塊與主程式分離。通過給函式指定描述性名稱,可讓主程式容易理解得多。你還可以更進一步,將函式儲存在被稱為模組 的獨立檔案中,再將模組匯入 到主程式中。import 語句允許在當前執行的程式檔案中使用模組中的程式碼。
通過將函式儲存在獨立的檔案中,可隱藏程式程式碼的細節,將重點放在程式的高層邏輯上。這還能讓你在眾多不同的程式中重用函式。將函式儲存在獨立檔案中後,可與其他程式設計師共享這些檔案而不是整個程式。知道如何匯入函式還能讓你使用其他程式設計師編寫的函式庫。

8.6.1 匯入整個模組

要讓函式是可匯入的,得先建立模組。模組 是副檔名為.py的檔案,包含要匯入到程式中的程式碼。
pizza.py

def make_pizza(size, *toppings):
    """概述要製作的披薩"""
    print("\nMaking a " + str(size) + "-inch pizza with the following toppings:")
    for topping in toppings:
        print("-" + topping)

我們在pizza.py所在的目錄中建立另一個名為making_pizzas.py的檔案,這個檔案匯入剛建立的模組,再呼叫make_pizza() 兩次:
making_pizzas.py

import pizza
pizza.make_pizza(12, 'pepperoni')
pizza.make_pizza(16, 'mushrooms', 'green peppers', 'extra cheese')

這就是一種匯入方法:只需編寫一條import 語句並在其中指定模組名,就可在程式中使用該模組中的所有函式。如果你使用這種import 語句匯入了名為module_name.py 的整個模組,就可使用下面的語法來使用其中任何一個函式:

module_name.function_name()
8.6.2 匯入特定的函式

你還可以匯入模組中的特定函式,這種匯入方法的語法如下:

from module_name import function_name

通過用逗號分隔函式名,可根據需要從模組中匯入任意數量的函式:

from module_name import function_1,function_2,function_3...

對於前面的making_pizzas.py示例,如果只想匯入要使用的函式,程式碼將類似於下面這樣:

from pizza import make_pizza
make_pizza(12, 'pepperoni')
make_pizza(16, 'mushrooms', 'green peppers', 'extra cheese')

若使用這種語法,呼叫函式時就無需使用句點。由於我們在import 語句中顯式地匯入了函式make_pizza() ,因此呼叫它時只需指定其名稱。

8.6.3 使用as 給函式指定別名

如果要匯入的函式的名稱可能與程式中現有的名稱衝突,或者函式的名稱太長,可指定簡短而獨一無二的別名 ——函式的另一個名稱,類似於外號。要給函式指定這種特殊外號,需要在匯入它時這樣做。
下面給函式make_pizza() 指定了別名mp() 。這是在import 語句中使用make_pizza as mp 實現的,關鍵字as 將函式重新命名為你提供的別名:

from pizza import make_pizza as mp
mp(12, 'pepperoni')
mp(16, 'mushrooms', 'green peppers', 'extra cheese')

指定別名的通用語法如下:

from module_name import function_name as fn
8.6.4 使用as 給模組指定別名

你還可以給模組指定別名。通過給模組指定簡短的別名(如給模組pizza 指定別名p ),讓你能夠更輕鬆地呼叫模組中的函式。

import pizza as p
p.make_pizza(12, 'pepperoni')
p.make_pizza(16, 'mushrooms', 'green peppers', 'extra cheese')

給模組指定別名的通用語法如下:

import module_name as mn
8.6.5 匯入模組中的所有函式

使用星號(* )運算子可讓Python匯入模組中的所有函式:

from pizza import *
make_pizza(12, 'pepperoni')
make_pizza(16, 'mushrooms', 'green peppers', 'extra cheese')

使用並非自己編寫的大型模組時,最好不要採用這種匯入方法:如果模組中有函式的名稱與你的專案中使用的名稱相同,可能導致意想不到的結果:Python可能遇到多個名稱相同的函式或變數,進而覆蓋函式,而不是分別匯入所有的函式。

最佳的做法是,要麼只匯入你需要使用的函式,要麼匯入整個模組並使用句點表示法。
匯入模組中的所有函式的通用語法如下:

from module_name import *

8.7 函式編寫指南

編寫函式時,需要牢記幾個細節。應給函式指定描述性名稱,且只在其中使用小寫字母和下劃線。描述性名稱可幫助你和別人明白程式碼想要做什麼。給模組命名時也應遵循上述約定。

給形參指定預設值時,等號兩邊不要有空格:

def function_name(parameter_0, parameter_1='default value')

對於函式呼叫中的關鍵字實參,也應遵循這種約定:

function_name(value_0, parameter_1='value')

習題

8-1
def display_message():
    print("害,本章學了好多東西。")

display_message()
8-2
def favorite_book(title):
    print("One of my favorite book is " + title + ".")

favorite_book("Harry Potter")
8-3
def make_shirt(size, typeface):
    print("This is a " + size + " shirt.\nIts typeface is " + typeface + ".")

#位置實參
make_shirt('L','GOOGLE')
print("=====================")
#關鍵字實參
make_shirt(size='L', typeface='GOOGLE')
8-4
def make_shirt(size, typeface='I love Python'):
    print("This is a " + size + " shirt.\nIts typeface is " + typeface + ".")

make_shirt('L')
print("=================")
make_shirt('M')
print("=================")
make_shirt('S', 'I know nothing')
8-5
def discribe_city(city, country='China'):
    print(city.title() + " is in " + country + ".")

discribe_city('wuhan')
discribe_city('beijing')
discribe_city('tokyo', 'Japan')
8-6
def city_country(city, country):
    full_city_country = city + ", " + country
    return full_city_country
    
print(city_country('Beijing', 'China'))
print(city_country('Tokyo', 'japan'))
print(city_country('Paris', 'France'))
8-7
def make_album(singer, album_name):
	"""製作一張專輯"""
    album = {'singer': singer, 'album_name': album_name}
    return album

print(make_album('林俊杰', '不為誰而作的歌'))
print(make_album('back number', 'Sister'))
print(make_album('周杰倫', '八度空間'))
# 可選形參
def make_album(singer, album_name, number=''):
	"""製作一張專輯"""
    album = {'singer': singer, 'album_name': album_name}
    if number:
        album['number'] = number
    return album

print(make_album('林俊杰', '不為誰而作的歌', 8))
print(make_album('back number', 'Sister'))
print(make_album('周杰倫', '八度空間'))
8-8
def make_album(singer, album_name, number=''):
    album = {'singer': singer, 'album_name': album_name}
    if number:
        album['number'] = number
    return album

while True:
    print("您好,可以問一下您最喜歡的專輯嗎?(輸入'q'可以退出程式)")
    singer = input("singer: ")
    if singer == 'q':
        break
    album_name = input("album_name: ")
    if album_name == 'q':
        break
    your_album = make_album(singer, album_name)
    print(your_album)
8-9
def show_magicians(names):
    for name in names:
        print(name.title())

magicians = ['tom', 'jack', 'bruce']
show_magicians(magicians)
8-10
# 先貼上我的錯誤做法,無法改變原列表magicians,不符合題目要求
def show_magicians(names):
    for name in names:
        print(name.title())

def make_great(magicians):
    for magician in magicians:
        magician = "the Great " + magician

magicians = ['tom', 'jack', 'bruce']
make_great(magicians)
show_magicians(magicians)

發現知乎上有關於這題的討論,參考連結
要點: 要修改列表元素的值,必須使用索引,即:

magicians[i] = "The great " + magicians[i]

下面是符合題目要求的做法:

def show_magicians(names):
    for name in names:
        print(name.title())

def make_great(magicians):
    for i in range(len(magicians)):
        magicians[i] = "the Great " + magicians[i]

magicians = ['tom', 'jerry', 'jack']
make_great(magicians)
show_magicians(magicians)
8-11
#傳遞magicians的副本就可以
def show_magicians(names):
    for name in names:
        print(name.title())

def make_great(magicians, great_magicians):
    for i in range(len(magicians)):
        magicians[i] = "the Great " + magicians[i]
        great_magicians.append(magicians[i])

magicians = ['tom', 'jack', 'bruce']
great_magicians = []
make_great(magicians[:], great_magicians)
show_magicians(great_magicians)
show_magicians(magicians)
8-12
def make_sandwich(*toppings):
    print("\nWe will add these things for your sandwich:")
    for topping in toppings:
        print("-" + topping)

make_sandwich('mushrooms', 'green peppers')
make_sandwich('mushrooms', 'pepperoni', 'extra cheese')
8-13
def build_profile(first, last, **user_info):
    """建立一個字典,其中包含我們知道的有關使用者的一切"""
    profile = {}
    profile['first_name'] = first
    profile['last_name'] = last 
    for key, value in user_info.items():
        profile[key] = value
    return profile

user_profile = build_profile('bruce', 'zhang',
        country='China',
        province='hubei',
        age=18)
print(user_profile)
8-14
def make_car(manufacturer, car_type, **others):
    """建立一個字典,其中包含我們知道的關於車的一切"""
    profile = {}
    profile['manufacturer'] = manufacturer
    profile['car_type'] = car_type
    for key, value in others.items():
        profile[key] = value
    return profile

car_profile = make_car('subaru', 'outback', color='blue', tow_package=True)
print(car_profile)

相關文章