《python》-6:函數(shù)和模塊的使用

每一個(gè)不曾起舞的日子搔弄,都是對(duì)生命的辜負(fù)日熬!

函數(shù)和模塊的使用

在講解本章節(jié)的內(nèi)容之前携冤,我們先來(lái)研究一道數(shù)學(xué)題卵皂,請(qǐng)說(shuō)出下面的方程有多少組正整數(shù)解秩铆。

x1 + x2 + x3 + x4 = 8

事實(shí)上,上面的問(wèn)題等同于將8個(gè)蘋(píng)果分成四組每組至少一個(gè)蘋(píng)果有多少種方案灯变。想到這一點(diǎn)問(wèn)題的答案就呼之欲出了殴玛。

這是數(shù)學(xué)中的函數(shù),其實(shí)每種編程語(yǔ)言都有函數(shù)添祸,當(dāng)然有區(qū)別于數(shù)學(xué)意義上的函數(shù)滚粟。


"""
輸入M和N計(jì)算C(M,N)
"""

m = int(input('m = '))
n = int(input('n = '))
fm = 1
for num in range(1, m + 1):
    fm *= num
fn = 1
for num in range(1, n + 1):
    fn *= num
fmn = 1
for num in range(1, m - n + 1):
    fmn *= num
print(fm // fn // fmn)

函數(shù)的作用

不知道大家是否注意到,在上面的代碼中刃泌,我們做了3次求階乘凡壤,這樣的代碼實(shí)際上就是重復(fù)代碼。編程大師Martin Fowler先生曾經(jīng)說(shuō)過(guò):“代碼有很多種壞味道耙替,重復(fù)是最壞的一種亚侠!”,要寫(xiě)出高質(zhì)量的代碼首先要解決的就是重復(fù)代碼的問(wèn)題俗扇。對(duì)于上面的代碼來(lái)說(shuō)硝烂,我們可以將計(jì)算階乘的功能封裝到一個(gè)稱之為“函數(shù)”的功能模塊中,在需要計(jì)算階乘的地方铜幽,我們只需要“調(diào)用”這個(gè)“函數(shù)”就可以了滞谢。

定義函數(shù)

在Python中可以使用def關(guān)鍵字來(lái)定義函數(shù)串稀,和變量一樣每個(gè)函數(shù)也有一個(gè)響亮的名字,而且命名規(guī)則跟變量的命名規(guī)則是一致的狮杨。在函數(shù)名后面的圓括號(hào)中可以放置傳遞給函數(shù)的參數(shù)厨诸,這一點(diǎn)和數(shù)學(xué)上的函數(shù)非常相似,程序中函數(shù)的參數(shù)就相當(dāng)于是數(shù)學(xué)上說(shuō)的函數(shù)的自變量禾酱,而函數(shù)執(zhí)行完成后我們可以通過(guò)return關(guān)鍵字來(lái)返回一個(gè)值,這相當(dāng)于數(shù)學(xué)上說(shuō)的函數(shù)的因變量绘趋。

在了解了如何定義函數(shù)后颤陶,我們可以對(duì)上面的代碼進(jìn)行重構(gòu),所謂重構(gòu)就是在不影響代碼執(zhí)行結(jié)果的前提下對(duì)代碼的結(jié)構(gòu)進(jìn)行調(diào)整陷遮,重構(gòu)之后的代碼如下所示滓走。

def factorial(num):
    """
    求階乘
    
    :param num: 非負(fù)整數(shù)
    :return: num的階乘
    """
    result = 1
    for n in range(1, num + 1):
        result *= n
    return result


m = int(input('m = '))
n = int(input('n = '))
# 當(dāng)需要計(jì)算階乘的時(shí)候不用再寫(xiě)循環(huán)求階乘而是直接調(diào)用已經(jīng)定義好的函數(shù)
print(factorial(m) // factorial(n) // factorial(m - n))

說(shuō)明:Python的math模塊中其實(shí)已經(jīng)有一個(gè)factorial函數(shù)了,事實(shí)上要計(jì)算階乘可以直接使用這個(gè)現(xiàn)成的函數(shù)而不用自己定義帽馋。下面例子中的某些函數(shù)其實(shí)Python中也是內(nèi)置了搅方,我們這里是為了講解函數(shù)的定義和使用才把它們又實(shí)現(xiàn)了一遍,實(shí)際開(kāi)發(fā)中不建議做這種低級(jí)的重復(fù)性的工作绽族。

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

函數(shù)是絕大多數(shù)編程語(yǔ)言中都支持的一個(gè)代碼的“構(gòu)建塊”姨涡,但是Python中的函數(shù)與其他語(yǔ)言中的函數(shù)還是有很多不太相同的地方,其中一個(gè)顯著的區(qū)別就是Python對(duì)函數(shù)參數(shù)的處理吧慢。在Python中涛漂,函數(shù)的參數(shù)可以有默認(rèn)值,也支持使用可變參數(shù)检诗,所以Python并不需要像其他語(yǔ)言一樣支持函數(shù)的重載匈仗,因?yàn)槲覀冊(cè)诙x一個(gè)函數(shù)的時(shí)候可以讓它有多種不同的使用方式,下面是兩個(gè)小例子逢慌。

from random import randint


def roll_dice(n=2):
    """
    搖色子
    
    :param n: 色子的個(gè)數(shù)
    :return: n顆色子點(diǎn)數(shù)之和
    """
    total = 0
    for _ in range(n):
        total += randint(1, 6)
    return total


def add(a=0, b=0, c=0):
    return a + b + c


# 如果沒(méi)有指定參數(shù)那么使用默認(rèn)值搖兩顆色子
print(roll_dice())
# 搖三顆色子
print(roll_dice(3))
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
# 傳遞參數(shù)時(shí)可以不按照設(shè)定的順序進(jìn)行傳遞
print(add(c=50, a=100, b=200))

我們給上面兩個(gè)函數(shù)的參數(shù)都設(shè)定了默認(rèn)值悠轩,這也就意味著如果在調(diào)用函數(shù)的時(shí)候如果沒(méi)有傳入對(duì)應(yīng)參數(shù)的值時(shí)將使用該參數(shù)的默認(rèn)值,所以在上面的代碼中我們可以用各種不同的方式去調(diào)用add函數(shù)攻泼,這跟其他很多語(yǔ)言中函數(shù)重載的效果是一致的火架。

其實(shí)上面的add函數(shù)還有更好的實(shí)現(xiàn)方案,因?yàn)槲覀兛赡軙?huì)對(duì)0個(gè)或多個(gè)參數(shù)進(jìn)行加法運(yùn)算忙菠,而具體有多少個(gè)參數(shù)是由調(diào)用者來(lái)決定距潘,我們作為函數(shù)的設(shè)計(jì)者對(duì)這一點(diǎn)是一無(wú)所知的,因此在不確定參數(shù)個(gè)數(shù)的時(shí)候只搁,我們可以使用可變參數(shù)音比,代碼如下所示。

# 在參數(shù)名前面的*表示args是一個(gè)可變參數(shù)
# 即在調(diào)用add函數(shù)時(shí)可以傳入0個(gè)或多個(gè)參數(shù)
def add(*args):
    total = 0
    for val in args:
        total += val
    return total


print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))

用模塊管理函數(shù)

對(duì)于任何一種編程語(yǔ)言來(lái)說(shuō)氢惋,給變量洞翩、函數(shù)這樣的標(biāo)識(shí)符起名字都是一個(gè)讓人頭疼的問(wèn)題稽犁,因?yàn)槲覀儠?huì)遇到命名沖突這種尷尬的情況。最簡(jiǎn)單的場(chǎng)景就是在同一個(gè).py文件中定義了兩個(gè)同名函數(shù)骚亿,由于Python沒(méi)有函數(shù)重載的概念已亥,那么后面的定義會(huì)覆蓋之前的定義,也就意味著兩個(gè)函數(shù)同名函數(shù)實(shí)際上只有一個(gè)是存在的来屠。

def foo():
    print('hello, world!')


def foo():
    print('goodbye, world!')


# 下面的代碼會(huì)輸出什么呢虑椎?
foo()

當(dāng)然上面的這種情況我們很容易就能避免,但是如果項(xiàng)目是由多人協(xié)作進(jìn)行團(tuán)隊(duì)開(kāi)發(fā)的時(shí)候俱笛,團(tuán)隊(duì)中可能有多個(gè)程序員都定義了名為foo的函數(shù)捆姜,那么怎么解決這種命名沖突呢?答案其實(shí)很簡(jiǎn)單迎膜,Python中每個(gè)文件就代表了一個(gè)模塊(module)泥技,我們?cè)诓煌哪K中可以有同名的函數(shù),在使用函數(shù)的時(shí)候我們通過(guò)import關(guān)鍵字導(dǎo)入指定的模塊就可以區(qū)分到底要使用的是哪個(gè)模塊中的foo函數(shù)磕仅,代碼如下所示珊豹。

module1.py

def foo():
    print('hello, world!')

module2.py

def foo():
    print('goodbye, world!')

test.py

from module1 import foo

# 輸出hello, world!
foo()

from module2 import foo

# 輸出goodbye, world!
foo()

也可以按照如下所示的方式來(lái)區(qū)分到底要使用哪一個(gè)foo函數(shù)。
test.py

import module1 as m1
import module2 as m2

m1.foo()
m2.foo()

但是如果將代碼寫(xiě)成了下面的樣子榕订,那么程序中調(diào)用的是最后導(dǎo)入的那個(gè)foo店茶,因?yàn)楹髮?dǎo)入的foo覆蓋了之前導(dǎo)入的foo。
test.py

from module1 import foo
from module2 import foo

# 輸出goodbye, world!
foo()

test.py

from module2 import foo
from module1 import foo

# 輸出hello, world!
foo()

需要說(shuō)明的是劫恒,如果我們導(dǎo)入的模塊除了定義函數(shù)之外還中有可以執(zhí)行代碼忽妒,那么Python解釋器在導(dǎo)入這個(gè)模塊時(shí)就會(huì)執(zhí)行這些代碼,事實(shí)上我們可能并不希望如此兼贸,因此如果我們?cè)谀K中編寫(xiě)了執(zhí)行代碼段直,最好是將這些執(zhí)行代碼放入如下所示的條件中,這樣的話除非直接運(yùn)行該模塊溶诞,if條件下的這些代碼是不會(huì)執(zhí)行的鸯檬,因?yàn)橹挥兄苯訄?zhí)行的模塊的名字才是“__main__”
module3.py

def foo():
    pass


def bar():
    pass


# __name__是Python中一個(gè)隱含的變量它代表了模塊的名字
# 只有被Python解釋器直接執(zhí)行的模塊的名字才是__main__
if __name__ == '__main__':
    print('call foo()')
    foo()
    print('call bar()')
    bar()

test.py

import module3

# 導(dǎo)入module3時(shí) 不會(huì)執(zhí)行模塊中if條件成立時(shí)的代碼 因?yàn)槟K的名字是module3而不是__main__

練習(xí)

練習(xí)1:實(shí)現(xiàn)計(jì)算求最大公約數(shù)和最小公倍數(shù)的函數(shù)螺垢。

def gcd(x, y):
    (x, y) = (y, x) if x > y else (x, y)
    for factor in range(x, 0, -1):
        if x % factor == 0 and y % factor == 0:
            return factor


def lcm(x, y):
    return x * y // gcd(x, y)
    

練習(xí)2:實(shí)現(xiàn)判斷一個(gè)數(shù)是不是回文數(shù)的函數(shù)喧务。

def is_palindrome(num):
    temp = num
    total = 0
    while temp > 0:
        total = total * 10 + temp % 10
        temp //= 10
    return total == num

練習(xí)3: 實(shí)現(xiàn)判斷一個(gè)數(shù)是不是素?cái)?shù)的函數(shù)。

def is_prime(num):
    for factor in range(2, num):
        if num % factor == 0:
            return False
    return True if num != 1 else False

練習(xí)4:寫(xiě)一個(gè)程序判斷輸入的正整數(shù)是不是回文素?cái)?shù)枉圃。

if __name__ == '__main__':
    num = int(input('請(qǐng)輸入正整數(shù): '))
    if is_palindrome(num) and is_prime(num):
        print('%d是回文素?cái)?shù)' % num)

通過(guò)上面的程序可以看出功茴,當(dāng)我們將代碼中重復(fù)出現(xiàn)的和相對(duì)獨(dú)立的功能抽取成函數(shù)后,我們可以組合使用這些函數(shù)來(lái)解決更為復(fù)雜的問(wèn)題孽亲,這也是我們?yōu)槭裁匆x和使用函數(shù)的一個(gè)非常重要的原因坎穿。

最后,我們來(lái)討論一下Python中有關(guān)變量作用域的問(wèn)題。

def foo():
    b = 'hello'

    def bar():  # Python中可以在函數(shù)內(nèi)部再定義函數(shù)
        c = True
        print(a)
        print(b)
        print(c)

    bar()
    # print(c)  # NameError: name 'c' is not defined


if __name__ == '__main__':
    a = 100
    # print(b)  # NameError: name 'b' is not defined
    foo()

上面的代碼能夠順利的執(zhí)行并且打印出100“hello”玲昧,但我們注意到了栖茉,在bar函數(shù)的內(nèi)部并沒(méi)有定義ab兩個(gè)變量,那么ab是從哪里來(lái)的孵延。我們?cè)谏厦娲a的if分支中定義了一個(gè)變量a吕漂,這是一個(gè)全局變量(global variable),屬于全局作用域尘应,因?yàn)樗鼪](méi)有定義在任何一個(gè)函數(shù)中惶凝。在上面的foo函數(shù)中我們定義了變量b,這是一個(gè)定義在函數(shù)中的局部變量(local variable)犬钢,屬于局部作用域苍鲜,在foo函數(shù)的外部并不能訪問(wèn)到它;但對(duì)于foo函數(shù)內(nèi)部的bar函數(shù)來(lái)說(shuō)娜饵,變量b屬于嵌套作用域,在bar函數(shù)中我們是可以訪問(wèn)到它的官辈。bar函數(shù)中的變量c屬于局部作用域箱舞,在bar函數(shù)之外是無(wú)法訪問(wèn)的。事實(shí)上拳亿,Python查找一個(gè)變量時(shí)會(huì)按照“局部作用域”晴股、“嵌套作用域”、“全局作用域”和“內(nèi)置作用域”的順序進(jìn)行搜索肺魁,前三者我們?cè)谏厦娴拇a中已經(jīng)看到了电湘,所謂的“內(nèi)置作用域”就是Python內(nèi)置的那些隱含標(biāo)識(shí)符min、len等都屬于內(nèi)置作用域)鹅经。

再看看下面這段代碼寂呛,我們希望通過(guò)函數(shù)調(diào)用修改全局變量a的值,但實(shí)際上下面的代碼是做不到的瘾晃。

def foo():
    a = 200
    print(a)  # 200


if __name__ == '__main__':
    a = 100
    foo()
    print(a)  # 100

在調(diào)用foo函數(shù)后贷痪,我們發(fā)現(xiàn)a的值仍然是100,這是因?yàn)楫?dāng)我們?cè)?code>函數(shù)foo中寫(xiě)a = 200的時(shí)候蹦误,是重新定義了一個(gè)名字為a的局部變量劫拢,它跟全局作用域的a并不是同一個(gè)變量,因?yàn)榫植孔饔糜蛑杏辛俗约旱淖兞縜强胰,因此foo函數(shù)不再搜索全局作用域中的a舱沧。如果我們希望在foo函數(shù)中修改全局作用域中的a,代碼如下所示偶洋。

我們可以使用global關(guān)鍵字來(lái)指示foo函數(shù)中的變量a來(lái)自于全局作用域熟吏,如果全局作用域中沒(méi)有a,那么下面一行的代碼就會(huì)定義變量a并將其置于全局作用域玄窝。同理分俯,如果我們希望函數(shù)內(nèi)部的函數(shù)能夠修改嵌套作用域中的變量肾筐,可以使用nonlocal關(guān)鍵字來(lái)指示變量來(lái)自于嵌套作用域,請(qǐng)大家自行試驗(yàn)缸剪。

在實(shí)際開(kāi)發(fā)中吗铐,我們應(yīng)該盡量減少對(duì)全局變量的使用,因?yàn)槿肿兞康淖饔糜蚝陀绊戇^(guò)于廣泛杏节,可能會(huì)發(fā)生意料之外的修改和使用唬渗,除此之外全局變量比局部變量擁有更長(zhǎng)的生命周期,可能導(dǎo)致對(duì)象占用的內(nèi)存長(zhǎng)時(shí)間無(wú)法被垃圾回收镊逝。事實(shí)上,減少對(duì)全局變量的使用嫉鲸,也是降低代碼之間耦合度的一個(gè)重要舉措,同時(shí)也是對(duì)迪米特法則的踐行玄渗。減少全局變量的使用就意味著我們應(yīng)該盡量讓變量的作用域在函數(shù)的內(nèi)部,但是如果我們希望將一個(gè)局部變量的生命周期延長(zhǎng)藤树,使其在函數(shù)調(diào)用結(jié)束后依然可以訪問(wèn)浴滴,這時(shí)候就需要使用閉包岁钓,這個(gè)我們?cè)诤罄m(xù)的內(nèi)容中進(jìn)行講解。

說(shuō)明:很多人經(jīng)常會(huì)將“閉包”一詞和“匿名函數(shù)”混為一談屡限,但實(shí)際上它們是不同的概念品嚣,如果想提前了解這個(gè)概念钧大,推薦看看維基百科或者知乎上對(duì)這個(gè)概念的討論。

說(shuō)了那么多拓型,其實(shí)結(jié)論很簡(jiǎn)單额嘿,從現(xiàn)在開(kāi)始我們可以將Python代碼按照下面的格式進(jìn)行書(shū)寫(xiě),這一點(diǎn)點(diǎn)的改進(jìn)其實(shí)就是在我們理解了函數(shù)和作用域的基礎(chǔ)上跨出的巨大的一步劣挫。

def main():
    # Todo: Add your code here
    pass


if __name__ == '__main__':
    main()

exercise python 100 days from https://github.com/jackfrued/Python-100-Days

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末册养,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子压固,更是在濱河造成了極大的恐慌球拦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坎炼,居然都是意外死亡愧膀,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門谣光,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)檩淋,“玉大人,你說(shuō)我怎么就攤上這事萄金◇霸茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵氧敢,是天一觀的道長(zhǎng)日戈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)孙乖,這世上最難降的妖魔是什么浙炼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮唯袄,結(jié)果婚禮上弯屈,老公的妹妹穿的比我還像新娘。我一直安慰自己越妈,他們只是感情好季俩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布钮糖。 她就那樣靜靜地躺著梅掠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪店归。 梳的紋絲不亂的頭發(fā)上阎抒,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音消痛,去河邊找鬼且叁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秩伞,可吹牛的內(nèi)容都是我干的逞带。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纱新,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼展氓!你這毒婦竟也來(lái)了脸爱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤空入,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后歪赢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迂烁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年递鹉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躏结。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡媳拴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出屈溉,到底是詐尸還是另有隱情,我是刑警寧澤子巾,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站椰于,受9級(jí)特大地震影響仪搔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烤咧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望笛谦。 院中可真熱鬧,春花似錦揪罕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至椰弊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秉版,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工并蝗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滚停。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓粥惧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親突雪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355