Python進(jìn)階-自定義函數(shù)基礎(chǔ)

本文為《爬著學(xué)Python》系列第十篇文章。


在實(shí)際操作中反砌,可能函數(shù)是我們幾乎唯一的實(shí)現(xiàn)操作的方式,這是因?yàn)楹瘮?shù)能夠構(gòu)造一個(gè)高度集中的變量環(huán)境葱轩,在合理的設(shè)計(jì)下,它能使程序思路更加清晰的同時(shí)更利于調(diào)整與修改藐握。幾乎沒(méi)有哪個(gè)程序設(shè)計(jì)語(yǔ)言會(huì)不涉及自定義函數(shù)的靴拱。

在上一篇文章中我們留了許多內(nèi)容說(shuō)要在本文中介紹,它們是一些和函數(shù)參數(shù)相關(guān)的問(wèn)題猾普。函數(shù)是我們的對(duì)操作方式的一種整合袜炕,因此我們會(huì)通過(guò)函數(shù)來(lái)進(jìn)行運(yùn)算或者完成某些功能,這些功能涉及到變量時(shí)初家,我們必須清楚到底發(fā)生了哪些事情偎窘。廢話少說(shuō)吧。

創(chuàng)建自定義函數(shù)

Python的自定義函數(shù)格式中規(guī)中矩

def func_name(arg1):
    pass

def引導(dǎo)自定義函數(shù)名溜在,用括號(hào)給出該函數(shù)的參數(shù)陌知,在冒號(hào)后換行通過(guò)縮進(jìn)確定函數(shù)體。在格式上和條件判斷語(yǔ)句有些相似掖肋。

當(dāng)然纵诞,我們從簡(jiǎn)單的開(kāi)始講起,這是Python自定義函數(shù)的簡(jiǎn)單形式培遵。一般能“動(dòng)手腳”的地方只有三個(gè),一個(gè)是def前面可以用裝飾器(詳見(jiàn)我的另一篇文章Python精進(jìn)-裝飾器與函數(shù)對(duì)象)登刺,一個(gè)是函數(shù)參數(shù)籽腕,一個(gè)是執(zhí)行語(yǔ)句。

關(guān)于執(zhí)行語(yǔ)句部分纸俭,主要是函數(shù)的嵌套以及控制結(jié)構(gòu)的組合皇耗,這種內(nèi)容作為知識(shí)講解沒(méi)什么意思。大多數(shù)人都知道可以這么做揍很,但很多人做不好郎楼,是不是因?yàn)闆](méi)學(xué)好呢万伤?我覺(jué)得不是的,是練少了呜袁,積累項(xiàng)目經(jīng)驗(yàn)以后就會(huì)逐漸強(qiáng)化這方面的能力敌买。而裝飾器之前專門(mén)提前講過(guò),因此本文的重點(diǎn)會(huì)放在函數(shù)參數(shù)上阶界。之后也會(huì)在深入了解Python自定義函數(shù)參數(shù)設(shè)計(jì)的基礎(chǔ)上去認(rèn)識(shí)如何正確設(shè)置函數(shù)返回值虹钮。

自定義函數(shù)的參數(shù)

首先我要聲明一點(diǎn),我決定不講一般意義上的形參(形式參數(shù))和實(shí)參(實(shí)際參數(shù))的知識(shí)膘融。按道理來(lái)說(shuō)芙粱,即使Python不嚴(yán)格要求定義函數(shù)參數(shù),但這方面的知識(shí)有助于理解自定義函數(shù)中參數(shù)操作的情況氧映,還是應(yīng)該說(shuō)明一下的春畔。但是我仔細(xì)想了一下在Python編程中不知道這兩個(gè)概念真的完全沒(méi)有任何關(guān)系,我們可以簡(jiǎn)單地理解為在定義函數(shù)時(shí)括號(hào)中聲明的參數(shù)是我們?cè)诤瘮?shù)使用中會(huì)用到的參數(shù)岛都,在調(diào)用函數(shù)時(shí)括號(hào)中的變量就是參加函數(shù)運(yùn)算用到的變量律姨。是的,換個(gè)名字疗绣,參數(shù)(用于定義)和變量(用于調(diào)用)就足以理解了线召。

可能完全沒(méi)有基礎(chǔ)的同學(xué)看上面一段話還是有些不明白,這很正常多矮,我們還沒(méi)有講過(guò)函數(shù)的調(diào)用缓淹。沒(méi)關(guān)系再接下來(lái)的例子中我們會(huì)見(jiàn)到。不過(guò)這一節(jié)我們重點(diǎn)是看看函數(shù)定義時(shí)參數(shù)有哪些形式塔逃。

最普通的參數(shù)

最普通的自定義函數(shù)參數(shù)就是在括號(hào)中列出一系列我們要用的參數(shù)讯壶。

def print_times(_string, _time):
    for i in range(_time):
        print(_string)

print_times('Hello!', 3)

在這個(gè)例子中我們定義函數(shù)時(shí)定義了兩個(gè)變量,分別是_string_time湾盗。這個(gè)函數(shù)的作用不用我過(guò)多說(shuō)明伏蚊,函數(shù)體只有一個(gè)循環(huán)語(yǔ)句,目的是重復(fù)輸出一個(gè)字符串格粪。首先要注意的是為什么我要在"string"和“time”前加下劃線呢躏吊,這是為了防止和其他變量出現(xiàn)沖突。如“string”有可能和內(nèi)置關(guān)鍵字沖突(其實(shí)不會(huì)帐萎,Python字符串內(nèi)置關(guān)鍵字是str比伏,這里是為了保險(xiǎn)起見(jiàn)),“date”有可能在使用了標(biāo)準(zhǔn)庫(kù)datetime與其中的方法沖突疆导。為了減少歧義赁项,在定義函數(shù)的時(shí)候給變量前面加上下劃線是比較穩(wěn)妥的辦法。這個(gè)技巧是面向?qū)ο缶幊虝r(shí)類設(shè)計(jì)常用的手段,在自定義函數(shù)中一樣可以用悠菜。在后面的例子中舰攒,有時(shí)我會(huì)用比較長(zhǎng)的不太可能沖突的變量就可以不這么做了。

接下來(lái)就是函數(shù)的作用的問(wèn)題悔醋,我們需要重復(fù)輸出一個(gè)字符串摩窃,所以理所當(dāng)然的情況下我們只需要簡(jiǎn)單地涉及兩個(gè)操作對(duì)象,一個(gè)是要輸出的字符串篙顺,一個(gè)是這個(gè)字符串輸出的次數(shù)偶芍。這是比較好理解的。所以我們調(diào)用函數(shù)的時(shí)候德玫,我們給出的字符串是Hello匪蟀,次數(shù)是3,這個(gè)函數(shù)就會(huì)輸出Hello三次宰僧。

但是可能會(huì)有疑惑的是材彪,我們有必要自定義一個(gè)函數(shù)這么做嗎?我們直接用一個(gè)循環(huán)語(yǔ)句不是一樣可以完成這樣的工作嗎琴儿?這也就是我們?yōu)槭裁葱枰远x函數(shù)的問(wèn)題段化。

在文章開(kāi)頭我簡(jiǎn)單講了一下自定義函數(shù)可以把操作進(jìn)行整合,可以集中變量環(huán)境造成,這里我們仔細(xì)說(shuō)明一下這些話是什么意思显熏。

誠(chéng)然我們可以通過(guò)一個(gè)循環(huán)語(yǔ)句來(lái)完成重復(fù)輸出字符串的工作。但是晒屎,如果我們?cè)诔绦蛑行枰啻斡玫竭@個(gè)功能呢喘蟆,是不是我們每次都要再寫(xiě)一個(gè)循環(huán)語(yǔ)句呢?情況更糟的是鼓鲁,如果代碼寫(xiě)完了好幾天以后蕴轨,我突然想要在每次輸出這個(gè)字符串以后再輸出一個(gè)另一個(gè)字符串呢?如果我們使用了函數(shù)骇吭,這時(shí)候我們可以把函數(shù)改成這樣

def print_times(_string, _time, fix_string=None):
    if fix_string is None:
        for i in range(_time):
            print(_string)
    else:
        for i in range(_time):
            print(_string)
            print(fix_string)

或者這樣

def print_times(_string, _time, fix_string=None):
    def print_times_former(_string, _time):
        for i in range(_time):
            print(_string)

    if fix_string is not None:
        _string += '\n' + fix_string
    print_times_former(_string, _time)

或者我們可以寫(xiě)一個(gè)裝飾器(功能會(huì)更局限橙弱,在此不演示了),總之方法有很多燥狰。

注意到我給新參數(shù)一個(gè)默認(rèn)值并且使用了一個(gè)判斷語(yǔ)句棘脐,這樣原來(lái)調(diào)用print_times函數(shù)的地方不會(huì)報(bào)錯(cuò),會(huì)像原來(lái)一樣完成工作(有默認(rèn)值的參數(shù)會(huì)在下面介紹)龙致。我們可以去調(diào)用了print_times函數(shù)的地方加上我們需要使用的函數(shù)荆残,它們就可以完成新功能了。

可能你還可以反駁净当,就算我寫(xiě)了幾遍循環(huán),我就去用了循環(huán)的地方添上不就行了嗎。那好像啼,我的問(wèn)題是俘闯,如果一個(gè)文件代碼量很大,那么多for語(yǔ)句忽冻,你要找出來(lái)是重復(fù)輸出字符串的地方恐怕也挺費(fèi)勁吧真朗,不小心改到別的循環(huán)運(yùn)行有問(wèn)題是不是還得回來(lái)找?如果用了函數(shù)僧诚,在任何編輯器中ctrl+F查找print_times結(jié)果就一目了然了(在編輯器如VS Code中你只要選中這個(gè)字段就能清楚看到遮婶,甚至不需要搜索,而且可以復(fù)選進(jìn)行同步修改)湖笨。

而且試想一下旗扑,這只是一個(gè)簡(jiǎn)單的重復(fù)輸出字符串的功能而已,如果是更復(fù)雜的功能慈省,函數(shù)的優(yōu)勢(shì)就更明顯了臀防。這還是沒(méi)有返回值的函數(shù),涉及到返回值時(shí)边败,函數(shù)的優(yōu)勢(shì)非常大袱衷,下面我們會(huì)談到。函數(shù)還可以在別的文件中引用笑窜,而不是直接復(fù)制粘貼一大段代碼過(guò)來(lái)致燥。

言歸正傳,我們來(lái)看看最開(kāi)始的簡(jiǎn)單的print_times函數(shù)是怎么工作的排截。我們把_string_time作為參數(shù)嫌蚤,在函數(shù)體的執(zhí)行語(yǔ)句中定義了一些操作,但是如果我們不調(diào)用這個(gè)函數(shù)匾寝,那么什么都不會(huì)發(fā)生搬葬。其實(shí)自定義函數(shù)就像是一個(gè)模板,我們給出要操作的對(duì)象的典型(就是參數(shù))艳悔,在函數(shù)體中給出它的操作語(yǔ)句急凰。定義自定義函數(shù)的時(shí)候它是不會(huì)真的對(duì)這些參數(shù)進(jìn)行操作的,它只是用來(lái)規(guī)定我們操作參數(shù)的方法猜年。我們定義了一些對(duì)這些參數(shù)的操作抡锈,然后把它打包成一個(gè)函數(shù)。意思就是乔外,要是以后要對(duì)一些變量用這個(gè)函數(shù)床三,那么程序就請(qǐng)按這樣操作吧。

于是杨幼,當(dāng)我們print_times('Hello!', 3)這樣調(diào)用print_times函數(shù)的時(shí)候撇簿,程序就會(huì)完成我們規(guī)定好了的工作聂渊。要注意的是,僅僅是print_times的話一般代表這個(gè)函數(shù)本身四瘫,它有可能是函數(shù)變量汉嗽,也有可能是函數(shù)對(duì)象。而如果函數(shù)后面加上括號(hào)找蜜,在括號(hào)里面給出作為參數(shù)的變量饼暑,print_times('Hello!', 3)就是調(diào)用這個(gè)函數(shù)。這些知識(shí)還是參考Python精進(jìn)-裝飾器與函數(shù)對(duì)象洗做。

需要說(shuō)明的是弓叛,函數(shù)調(diào)用的時(shí)候,變量的順序是要和函數(shù)參數(shù)定義的時(shí)候聲明參數(shù)的數(shù)量相等且順序一致的诚纸。除非我們?cè)诮o定參數(shù)的時(shí)候指明參數(shù)名撰筷,如

print_times(_time=3, _string='Hello!',)

這樣即使順序和參數(shù)聲明的時(shí)候的順序不一致,解釋器也能完成正常完成功能咬清。但是這個(gè)方法非常不推薦大家使用闭专,原因在后面會(huì)再提。之所以要說(shuō)函數(shù)參數(shù)的順序問(wèn)題旧烧,因?yàn)檫@涉及到其他形式的函數(shù)參數(shù)影钉,包括有默認(rèn)值的參數(shù)和可選參數(shù)。

接下來(lái)我們先介紹有默認(rèn)值的函數(shù)參數(shù)掘剪。

參數(shù)的初始值

其實(shí)參數(shù)有默認(rèn)值的函數(shù)我們?cè)谏厦婢鸵?jiàn)過(guò)一個(gè)平委,但是在這里我們先不去管他。我們先來(lái)看看這個(gè)所謂的參數(shù)默認(rèn)值是什么樣的夺谁。

def func_defualt(a=3)
    print(a)

func()
func(2)

注意到形式其實(shí)很簡(jiǎn)單廉赔,就是在聲明函數(shù)參數(shù)的時(shí)候用賦值語(yǔ)句給參數(shù)一個(gè)初始值。在這樣的情況下匾鸥,我們本來(lái)調(diào)用函數(shù)是需要給出變量作為參數(shù)的蜡塌,但是如果我們的參數(shù)有默認(rèn)值,那么如果我們?cè)谡{(diào)用這個(gè)函數(shù)時(shí)不實(shí)例化這個(gè)參數(shù)勿负,那么程序就會(huì)用參數(shù)的默認(rèn)值進(jìn)行操作馏艾。上面的兩條調(diào)用語(yǔ)句,分別會(huì)輸出32奴愉。

接下來(lái)要說(shuō)的琅摩,就是剛才我們所說(shuō)過(guò)的參數(shù)順序的問(wèn)題。直接先說(shuō)結(jié)論锭硼,有默認(rèn)值的參數(shù)要放在所有沒(méi)有默認(rèn)值的參數(shù)后面房资。這個(gè)規(guī)定不像之前涉及過(guò)的編程習(xí)慣問(wèn)題,這是默認(rèn)Python解釋器規(guī)定的出錯(cuò)類型檀头。

>>> def func_default2(a=1,b):
...     print(a, b)
...
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
>>>

Python之所以要這樣規(guī)定轰异,是為了減少程序出錯(cuò)的可能性岖沛,是出于safety的考慮。在程序中safety和security是不一樣的概念溉浙,security一般指程序抵御外部攻擊的能力烫止,safety則一般指程序運(yùn)行的穩(wěn)定性。

試想一下戳稽,如果我們能夠用def func(a=1,b):這樣的形式定義函數(shù),那么調(diào)用這個(gè)函數(shù)的時(shí)候就可能會(huì)出現(xiàn)問(wèn)題期升。首先惊奇,如果你按照順序給出了所有參數(shù)的值,或者雖然打亂順序但是對(duì)應(yīng)好參數(shù)名用變量賦值了播赁,那么你有什么必要給這個(gè)參數(shù)一個(gè)默認(rèn)值呢颂郎?那到了想讓參數(shù)默認(rèn)值發(fā)揮作用的場(chǎng)景,你也只能把除了有默認(rèn)值的參數(shù)以外的其他參數(shù)都對(duì)應(yīng)好參數(shù)名用變量賦值容为,這不僅麻煩而且容易出現(xiàn)紕漏乓序,如果有某個(gè)參數(shù)沒(méi)有值,程序就會(huì)報(bào)錯(cuò)坎背。而且替劈,在實(shí)際編程中,函數(shù)參數(shù)有可能遠(yuǎn)遠(yuǎn)不止兩個(gè)得滤,如果其中一部分有默認(rèn)值一部分沒(méi)有陨献,但是順序又被打亂了,那么調(diào)用這個(gè)函數(shù)將會(huì)是非常糟糕的一件事情懂更。所以眨业,為了省去不必要的麻煩,Python解釋器將這個(gè)按道理來(lái)說(shuō)也是編程習(xí)慣的做法變成了強(qiáng)制的規(guī)定沮协。

當(dāng)然龄捡,以上一大段都不重要,只要記住一點(diǎn)慷暂,有默認(rèn)值的參數(shù)要放在所有沒(méi)有默認(rèn)值的參數(shù)后面聘殖。

另外值得一提的是,一般參數(shù)在函數(shù)調(diào)用時(shí)呜呐,如果不給出參數(shù)名就斤,不能置于有默認(rèn)值的參數(shù)之后

>>> def func_default2(a, b=1):
...     print(a, b)
...
>>> func_default2(b=2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>>

range函數(shù)的練習(xí)

知道了上面的概念以后蘑辑,我們來(lái)拿range函數(shù)當(dāng)作練習(xí)洋机。由于還沒(méi)有介紹過(guò)生成器,而且我們練習(xí)的重點(diǎn)是函數(shù)參數(shù)的設(shè)計(jì)洋魂,因此我們只需要返回range()對(duì)象就行绷旗。要求像Python內(nèi)置的range函數(shù)給定參數(shù)的規(guī)定一樣

  1. 當(dāng)只用一個(gè)變量調(diào)用這個(gè)函數(shù)時(shí)喜鼓,這個(gè)變量指的是輸出的等差數(shù)列的終點(diǎn),如range(5)
  2. 當(dāng)給定兩個(gè)變量時(shí)衔肢,分別指輸出的起始值和終點(diǎn),庄岖,如range(2, 5)
  3. 當(dāng)給定三個(gè)變量時(shí),在上一條的基礎(chǔ)上第三個(gè)變量指輸出時(shí)的步長(zhǎng)角骤,如range(2, 5, -1)

(假定我們調(diào)用這個(gè)函數(shù)時(shí)總是用整數(shù)或浮點(diǎn)數(shù))

分析一下如何實(shí)現(xiàn)這個(gè)函數(shù)隅忿,下面給出我的思路作為參考

  • 一共需要三個(gè)參數(shù)是顯而易見(jiàn)的;
  • 最直觀的感受是起始值是要有默認(rèn)值的邦尊,如果不規(guī)定從哪里開(kāi)始背桐,那就從0開(kāi)始;
  • 步長(zhǎng)也是要有默認(rèn)值的蝉揍,如果不規(guī)定链峭,那么步長(zhǎng)是1;
  • 根據(jù)有默認(rèn)值的參數(shù)要放在后面的原則又沾,那么最理所當(dāng)然的參數(shù)設(shè)計(jì)是range_custom(stop, start=0, step=1)
  • 這個(gè)方案看上去可行弊仪,但是不滿足剛才的后面兩個(gè)要求,如果我們這樣用兩個(gè)變量調(diào)用杖刷,起始值和終點(diǎn)是反的励饵;
  • 我們加個(gè)判斷就可以了,如果start用了初始值挺勿,那么說(shuō)明我們調(diào)用的時(shí)候只給了一個(gè)參數(shù)曲横,這個(gè)時(shí)候stop就是終點(diǎn),如果start被重新賦值了說(shuō)明給了至少兩個(gè)參數(shù)不瓶,那么這時(shí)候把stop和start的值調(diào)換一下就可以了禾嫉;
  • 現(xiàn)在這個(gè)函數(shù)似乎可以滿足大多數(shù)情況了,但是有一個(gè)bug蚊丐,如果給定參數(shù)的時(shí)候給的start值就是0怎么辦呢熙参?如range_custom(-5, 0)按目前的規(guī)則會(huì)被翻譯成range(0, -5),但是我們的目的卻是range(-5, 0)麦备;
  • 所以start的初始值不應(yīng)該是數(shù)字而是別的數(shù)據(jù)類型孽椰,為了方便起見(jiàn),我們把它的初始值賦為None凛篙,我們的程序雛形就出來(lái)了黍匾。
def range_custom(stop, start=None, step=1):
    if start is None:
        return range(stop)
    return range(stop, start, step)

現(xiàn)在這個(gè)程序已經(jīng)滿足我們的要求了,但是看上去不太舒服呛梆,可以改成

def range_custom(start, stop=None, step=1):
    if stop is None:
        return range(start)
    return range(start, stop, step)

現(xiàn)在這個(gè)函數(shù)的參數(shù)順序在邏輯上更好理解一些锐涯,可以說(shuō)基本上滿足我們的要求了。當(dāng)然填物,本例只是為了說(shuō)明參數(shù)的順序問(wèn)題纹腌,并不是為了實(shí)現(xiàn)range函數(shù)霎终。事實(shí)上Python的range函數(shù)還包括參數(shù)實(shí)例化,生成器等知識(shí)升薯,在后面我們應(yīng)該還有機(jī)會(huì)再接觸它莱褒。

可選參數(shù)

說(shuō)到可選參數(shù),可能有的人見(jiàn)過(guò)涎劈,卻也不明白到底是什么意思广凸,它一般是這樣出現(xiàn)的

def func_option(*args):
    return args

注意到我們聲明函數(shù)的時(shí)候在參數(shù)名前加了個(gè)*星號(hào),這是聲明可選參數(shù)的方法蛛枚。那么可選參數(shù)到底有什么用呢炮障?

可選參數(shù)的作用是用元組把所有多余的變量收集起來(lái),這個(gè)元組的名字就是這個(gè)可選參數(shù)名坤候。在上例func_option中我們可以用任意多個(gè)變量調(diào)用它,比如a = func_option(1, 2, 3)那么a就會(huì)是元組(1, 2, 3)企蹭。關(guān)于為什么是元組而不是列表白筹,我們?cè)谏弦黄?a href="http://www.reibang.com/p/e6c4683a511d" target="_blank">Python進(jìn)階-簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)中說(shuō)過(guò),元組在Python中往往是比列表更優(yōu)先考慮使用的數(shù)據(jù)結(jié)構(gòu)谅摄,具體原因在本文靠后深入自定義函數(shù)參數(shù)部分會(huì)討論徒河。

我們剛才說(shuō)可選參數(shù)會(huì)收集多余的變量。我這么說(shuō)是有原因的送漠。

>>> def func_option(a, *args, c=2):
...     return args
...
>>> func_option2(1)
()
>>> func_option2(1, 2)
(2,)
>>> func_option2(1, 2, 3)
(2, 3)

注意到我們的*args把除了給普通參數(shù)的第一個(gè)變量以外的值都放進(jìn)了元組中顽照。這樣做導(dǎo)致了一個(gè),問(wèn)題在于我們的有默認(rèn)值的參數(shù)如果不給定參數(shù)名地調(diào)用的話闽寡,就永遠(yuǎn)只能用默認(rèn)值了代兵。而且如果我們?cè)谡{(diào)用函數(shù)時(shí)不把有默認(rèn)值的參數(shù)放在最后面程序還會(huì)報(bào)錯(cuò)。

>>> func_option2(c=1, 2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

那么有沒(méi)有好的辦法能規(guī)避這個(gè)問(wèn)題呢爷狈?我們可以試試把可選參數(shù)放在有默認(rèn)值的參數(shù)后面植影。

>>> def func_option3(a, c=2, *args):
...     return args
...
>>> func_option3(1)
()
>>> func_option3(1, 2)
()
>>> func_option3(1, 2, 3)
(3,)
>>> func_option2(c=1, 2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

那么這種形式的函數(shù)能不能解決之前的問(wèn)題呢∠延溃看上去不行思币,不過(guò)我們知道了,調(diào)用函數(shù)的時(shí)候羡微,要盡量把有默認(rèn)值的參數(shù)放在靠后
的位置賦予變量谷饿。那么這兩種我們到底該用哪個(gè)方法呢?在實(shí)際操作中妈倔,我們傾向于將可選參數(shù)放在有默認(rèn)值的參數(shù)之后博投,而且如果參數(shù)較多,我們傾向于調(diào)用函數(shù)時(shí)都會(huì)所有變量都加上參數(shù)名启涯。而且實(shí)際操作中贬堵,其實(shí)可選參數(shù)用得不那么多恃轩,相對(duì)來(lái)說(shuō),另一種可選參數(shù)其實(shí)用得更多黎做。這種可選參數(shù)
的形式一般是這樣

def func_optionkw(**kwargs):
    return args

在這種情況下叉跛,關(guān)鍵字可選參數(shù)都是作為鍵值對(duì)保存在參數(shù)名的的字典中。也就是說(shuō)蒸殿,在調(diào)用函數(shù)時(shí)筷厘,在滿足一般參數(shù)以后,變量都應(yīng)該以賦值語(yǔ)句的形式給出宏所,等號(hào)左邊作為鍵右邊作為值酥艳。如果不這樣做,就會(huì)報(bào)錯(cuò)了爬骤。

>>> func_optionkw(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: t2() takes 0 positional arguments but 1 was given

需要說(shuō)明的是充石,一個(gè)自定義函數(shù)只能有一個(gè)可選參數(shù),同時(shí)也可以有至多一個(gè)關(guān)鍵字參數(shù)霞玄。其中關(guān)鍵字參數(shù)應(yīng)該放在普通可選參數(shù)之后骤铃。

現(xiàn)在我們來(lái)總結(jié)一下函數(shù)參數(shù)順序一般規(guī)律:

  • 一般參數(shù)放在最前面
  • 可選參數(shù)放在最后面
  • 關(guān)鍵字可選參數(shù)放在一般可選參數(shù)后面
  • 函數(shù)調(diào)用時(shí)盡量把有默認(rèn)值的參數(shù)對(duì)應(yīng)的變量放在靠后的位置
  • 如果參數(shù)比較多,調(diào)用函數(shù)時(shí)坷剧,最好所有變量都指明參數(shù)名

以上這些惰爬,有的是為了防止函數(shù)定義時(shí)出錯(cuò),有的是為了防止函數(shù)調(diào)用時(shí)出錯(cuò)惫企,總之撕瞧,應(yīng)該養(yǎng)成良好的編程習(xí)慣。

自定義函數(shù)的返回值

我們使用自定義函數(shù)集成對(duì)變量的操作狞尔,那么我們?nèi)绾潍@得變量操作的結(jié)果呢丛版?一般來(lái)說(shuō)有兩種,一種是對(duì)變量進(jìn)行操作使其本身變化沪么,這種行為是極不提倡的硼婿,這是不利于上面提到過(guò)的safety的,因?yàn)橥ㄟ^(guò)函數(shù)操作變量會(huì)帶來(lái)不確定性禽车,在下一部分我們會(huì)詳細(xì)介紹寇漫;還有一種就是用變量當(dāng)作運(yùn)算的初始值,最后返回運(yùn)算的結(jié)果殉摔。在上面的例子中州胳,我們一般都是后面這種方法定義函數(shù)。

需要說(shuō)明的是逸月,這個(gè)返回值說(shuō)是運(yùn)算的結(jié)果栓撞,其實(shí)類型非常寬容。它可以是經(jīng)過(guò)操數(shù)值運(yùn)算后的一個(gè)數(shù)據(jù),他也可以是列表元組等數(shù)據(jù)結(jié)構(gòu)瓤湘,它可以是個(gè)函數(shù)瓢颅,它還可以是調(diào)用某個(gè)函數(shù)后用其返回值當(dāng)作自己的返回值,總之返回值非常靈活弛说。

那么我們剛才說(shuō)的通過(guò)函數(shù)對(duì)變量本身進(jìn)行操作的方法需不需要返回值呢挽懦?一般來(lái)說(shuō)是不需要的,在C語(yǔ)言中信柿,我們習(xí)慣性會(huì)對(duì)這種函數(shù)設(shè)置一個(gè)return 0這是為了檢測(cè)是否函數(shù)正常運(yùn)行,在Python中我們當(dāng)然也可以這么做醒第。雖然我說(shuō)這種方法不安全愕宋,不常用用僧,但是幾乎每個(gè)C語(yǔ)言都會(huì)都會(huì)用到這個(gè)方法,這個(gè)方法一般用在main()函數(shù)中预吆。關(guān)于編程范式的知識(shí)在這里就不展開(kāi)講了誊涯,我就只順便簡(jiǎn)單講講Python中的main()函數(shù)一般長(zhǎng)什么樣子屡谐。

if __name__ = '__main__':
    pass

不管見(jiàn)過(guò)沒(méi)見(jiàn)過(guò)娄柳,這個(gè)結(jié)構(gòu)都是Python編程中非常普遍的方法争拐。這個(gè)結(jié)構(gòu)的功能是,如果該.py文件不是被其他文件import引用蝗岖,就執(zhí)行pass部分的語(yǔ)句。這就相當(dāng)于Python的main()函數(shù)榔至。如果我們直接執(zhí)行Python文件抵赢,那么執(zhí)行的就是這些語(yǔ)句。如果采用了這種結(jié)構(gòu)唧取,那么這個(gè)文件中的其他部分要么是靜態(tài)變量铅鲤,要么就是定義好了的函數(shù)。我們通過(guò)這個(gè)結(jié)構(gòu)來(lái)調(diào)用一系列集成過(guò)的自定義函數(shù)來(lái)完成某種復(fù)雜的功能枫弟。

深入自定義函數(shù)參數(shù)

在這個(gè)部分中邢享,我們會(huì)重點(diǎn)講一下關(guān)于Python可變對(duì)象和不可變對(duì)象在函數(shù)中需要注意的地方。這個(gè)知識(shí)點(diǎn)幾乎是面試必考內(nèi)容淡诗,因?yàn)樗w現(xiàn)了一個(gè)Python使用者對(duì)Python數(shù)據(jù)類型的理解以及函數(shù)設(shè)計(jì)方面的認(rèn)識(shí)

可變和不可變

首先我們要介紹一下到底什么是可變對(duì)象什么是不可變對(duì)象骇塘。在之前即使介紹數(shù)據(jù)結(jié)構(gòu)我也沒(méi)有展開(kāi)來(lái)講,為的就是現(xiàn)在和函數(shù)參數(shù)一起進(jìn)行說(shuō)明韩容。我們就拿列表和元組舉例款违,這是我們之前講過(guò)的典型的可變和不可變對(duì)象。

首先是列表:

>>> list_sample = [1, 2, 3]
>>> list_sample_mirror = list_sample
>>> id(list_sample)    # id函數(shù)用來(lái)查看變量在內(nèi)存中的地址
1372626593864
>>> id(ist_sample_mirror)
1372626593864
>>> list_sample[1] = 5
>>> id(list_sample)
1372626593864
>>> list_sample[1] += [4]
>>> id(list_sample)
1372626593864
>>> print(list_sample_mirror)
[1, 5, 3, 4]

注意到我們可以更改列表的值群凶,更改列表的值以后插爹,本來(lái)和它初值相等的另一個(gè)列表也被改變了。出現(xiàn)這種現(xiàn)象的原因在于,由于Python的引用語(yǔ)義赠尾,變量賦值往往會(huì)指向一個(gè)內(nèi)存中的最終對(duì)象力穗,如果存在就直接引用。那么對(duì)于可變對(duì)象來(lái)說(shuō)气嫁,改變它的值当窗,就是對(duì)內(nèi)存中的那個(gè)對(duì)象進(jìn)行修改,因此其他引用這個(gè)對(duì)象的變量也受到“牽連”了杉编。

那我們?cè)賮?lái)看元組又是什么情況呢:

>>> tuple_sample = (1, 2, 3)
>>> tuple_sample_mirror = tuple_sample
>>> id(tuple_sample)
2473662073160
>>> id(tuple_sample_mirror)
2473662073160
>>> tuple_sample[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> tuple_sample += (4, 5)
>>> tuple_sample
(1, 2, 3, 4, 5)
>>> id(tuple_sample)
2473625127928
>>> tuple_sample_mirror
(1, 2, 3)
>>> id(tuple_sample_mirror)
2473662073160

可以看到一樣是引用同一個(gè)內(nèi)存對(duì)象超全,但是元組不允許改變其中的元素。不過(guò)好在元組也支持連接操作邓馒,但是和列表有什么區(qū)別呢嘶朱,我們看到,連接后的元組其實(shí)已經(jīng)不是原來(lái)那個(gè)元組了光酣,其實(shí)Python按照要求的操作重新創(chuàng)建了一個(gè)元組將其賦值給這個(gè)變量疏遏。而另一個(gè)引用原來(lái)的元組的變量沒(méi)有受到任何影響。Python通過(guò)限制操作來(lái)控制元組的穩(wěn)定性救军。

這種情況下财异,通過(guò)賦值得來(lái)的tuple_sample_mirror就更加“safe”,它的值會(huì)保持在我們的意料之中唱遭。

需要說(shuō)明的是戳寸,在函數(shù)中,這些事情一樣會(huì)發(fā)生拷泽。

列表

def func_mutable(list_a):
    list_a += [1]
    print(list_a)

a = [0]

func_mutable(a)     # 輸出[0, 1]
print(a)            # 輸出[0, 1]
func_mutable(a)     # 輸出[0, 1, 1]
print(a)            # 輸出[0, 1, 1]

元組

def func_immutable(tuple_a):
    tuple_a += (1,)
    print(tuple_a)

a = (0,)

func_mutable(a)     # 輸出(0, 1)
print(a)            # 輸出(0,)
func_mutable(a)     # 輸出(0, 1)
print(a)            # 輸出(0,)

以上其實(shí)就是可變對(duì)象和不可變對(duì)象的區(qū)別疫鹊。需要注意的是,可變對(duì)象有些操作也是不改變這個(gè)對(duì)象的司致,如索引操作拆吆。而不可變對(duì)象只要不對(duì)變量重新賦值,那么原來(lái)的變量永遠(yuǎn)不會(huì)變脂矫。

Python中另外一些數(shù)據(jù)類型幾乎都是不可變的枣耀,如字符串和數(shù)字以及布爾值還有None。由于可變和不可變帶來(lái)的相關(guān)操作細(xì)節(jié)非常多庭再。比如說(shuō)為什么在判斷None的時(shí)候優(yōu)先使用is None而不去判斷==None捞奕,因?yàn)樗蠳one都是用的同一個(gè)對(duì)象,判斷時(shí)只需要查找內(nèi)存地址看是不是引用同一個(gè)地址拄轻,而不用去看地址里面的內(nèi)容是不是一致了缝彬。

可變對(duì)象作為函數(shù)參數(shù)

現(xiàn)在我們回到函數(shù)的問(wèn)題上來(lái),即可變對(duì)象作為函數(shù)參數(shù)的操作處理哺眯。我們先看一個(gè)例子:

def func_mutable(list_a=[]):
    list_a += [1]
    print(list_a)

func_mutable()
func_mutable()

注意到這個(gè)函數(shù)只有一個(gè)有默認(rèn)值的參數(shù)谷浅,這個(gè)參數(shù)的默認(rèn)值是一個(gè)空列表。那么實(shí)際操作中,會(huì)有什么樣的問(wèn)題出現(xiàn)呢一疯?問(wèn)題就在于撼玄,我們兩次調(diào)用這個(gè)函數(shù)的輸出是不一樣的。兩次分別是[1][1, 1]這是不合常理的墩邀。我們又沒(méi)有改變參數(shù)的默認(rèn)值掌猛,為什么函數(shù)執(zhí)行結(jié)果還能不一樣呢?原因就在于我們的參數(shù)默認(rèn)值是個(gè)可變對(duì)象眉睹。

我們?cè)?a href="http://www.reibang.com/p/016573e8f63e" target="_blank">Python精進(jìn)-裝飾器與函數(shù)對(duì)象中先把函數(shù)比作了列表荔茬,后來(lái)修正成為了元組。那學(xué)過(guò)簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)以后竹海,我今天要給出新的類比了慕蔚,自定義函數(shù)其實(shí)更像是不可變字典。字典和不可變這兩個(gè)概念都已經(jīng)介紹過(guò)斋配,那么合在一起理解起來(lái)應(yīng)該難度也不大孔飒。Python的自定義函數(shù)有許多內(nèi)置方法來(lái)保存運(yùn)行所需要的信息,就像是用鍵值對(duì)保存信息的字典艰争,不僅如此坏瞄,它的鍵和值分別都是不可變對(duì)象。Python自定義函數(shù)用來(lái)保存參數(shù)默認(rèn)值的內(nèi)置方法是__defaults__甩卓,我們可以直接調(diào)用它來(lái)查看函數(shù)參數(shù)的默認(rèn)值鸠匀。那么我們就來(lái)試一下。

def func_mutable(list_a=[], tuple_b=()):
    list_a += [1]
    tuple_b += (1,)
    print(list_a, tuple_b)

print(func_mutable.__defaults__)
func_mutable()
print(func_mutable.__defaults__)
func_mutable()
print(func_mutable.__defaults__)

執(zhí)行這個(gè)文件的輸出結(jié)果是這樣的:

([], ())
[1] (1, )
([1], ())
[1, 1] (1, )
([1, 1], ())

可以清楚地看到逾柿,Python是用元組來(lái)保存參數(shù)默認(rèn)值信息的狮崩。當(dāng)元組中的可變對(duì)象被操作以后,元組保留了操作結(jié)果鹿寻。同樣進(jìn)行了操作,tuple_b卻沒(méi)有改變默認(rèn)值诽凌,而且它的輸出結(jié)果和我們?cè)O(shè)想的一樣毡熏,兩次都是同樣的輸出結(jié)果。

通過(guò)以上的對(duì)比我們不難看出侣诵,列表是不適合作為函數(shù)參數(shù)使用的痢法,至少,不應(yīng)該有默認(rèn)值杜顺。如果一定要用有默認(rèn)值的列表當(dāng)作參數(shù)财搁,有沒(méi)有辦法同時(shí)又能保證參數(shù)默認(rèn)值一直是空列表不會(huì)變呢?方法是有的躬络。

def func_mutable(list_a=[]):
    list_exec = list_a
    list_exec += [1]
    print(list_a)

這樣做行不行呢尖奔?我們?cè)诤瘮?shù)體內(nèi)新聲明一個(gè)變量來(lái)復(fù)制列表的值,對(duì)這個(gè)新變量而不是列表本身進(jìn)行操作可不可以?通過(guò)前面的講解我們知道提茁,這樣做是自欺欺人的淹禾。

而且,我剛才還有一點(diǎn)故意沒(méi)說(shuō)茴扁。tuple_b += (1,)這個(gè)操作在我們之前的試驗(yàn)中铃岔,雖然元組自身不會(huì)變,但是變量會(huì)被重新賦值峭火,那么為什么__defaults__里面保存的不是這個(gè)新元組呢毁习?其實(shí),Python函數(shù)在調(diào)用是卖丸,相當(dāng)于自動(dòng)實(shí)例化了參數(shù)纺且,即使你不用list_exec = list_a,程序也是這樣做的坯苹,程序運(yùn)行的時(shí)候操作對(duì)象是list_exec而不是list_a隆檀。之所以看上去像是直接對(duì)參數(shù)進(jìn)行操作,那是為了方便學(xué)習(xí)者理解粹湃,但程序底層會(huì)使用更加安全的方式去執(zhí)行恐仑。這也是為什么不要用可變對(duì)象當(dāng)默認(rèn)值,因?yàn)檫@樣的話为鳄,程序執(zhí)行時(shí)裳仆,就真的相當(dāng)于對(duì)參數(shù)本身進(jìn)行操作了。

這也是為什么面試的時(shí)候老是考這樣的問(wèn)題孤钦,因?yàn)槿绻隳芾斫膺@里面的區(qū)別歧斟,那么說(shuō)明對(duì)Python的運(yùn)算特點(diǎn)算是有一定的了解了。我們言歸正傳偏形,除了剛才自欺欺人的辦法静袖,有沒(méi)有真正有效的方法呢?方法是有的俊扭。

def func_mutable(list_a=[]):
    list_exec = list_a.copy()
    list_exec += [1]
    print(list_a)

或者

def func_mutable(list_a=[]):
    list_exec = list(list_a)
    list_exec += [1]
    print(list_a)

這兩種辦法都能解決剛才的問(wèn)題队橙,都能保證正確的輸出結(jié)果。那么到底該選哪個(gè)方法萨惑,可以看個(gè)人取舍捐康,我傾向于推薦第一種方法。但是第二種方法也有好處庸蔼,它不僅可以用在列表上解总,用在元組上也是可以的,而且會(huì)使我們的操作非常靈活姐仅。

那么我們?cè)倩仡^看一下花枫,我們剛才說(shuō)Python會(huì)自動(dòng)進(jìn)行類似list_exec = list_a這樣的處理刻盐,那么它為什么不用list_exec = list_a.copy()呢?一方面乌昔,這種辦法浪費(fèi)內(nèi)存隙疚,而且運(yùn)行起來(lái)效率要比前者低,另一方面磕道,這樣其實(shí)也限制了很多的操作供屉。如果我們對(duì)自己有信心,那么利用元組保存列表的形式來(lái)構(gòu)建類似可變?cè)M的方法其實(shí)是非常有用的溺蕉。而且這樣做保留了用函數(shù)改變列表的可能性伶丐,簡(jiǎn)單程序如果面向過(guò)程開(kāi)發(fā)往往是最直接最高效的。

但是疯特,我還是要重申哗魂,一般來(lái)說(shuō)

  • 盡量不要用列表當(dāng)作變量傳入函數(shù)中,尤其不要依賴默認(rèn)值漓雅;
  • 如果一定要用列表變量當(dāng)函數(shù)參數(shù)录别,那么在函數(shù)中盡量不要涉及修改列表的操作
  • 如果一定要在函數(shù)內(nèi)部進(jìn)行修改列表的操作邻吞,那么最好用安全的辦法復(fù)制列表组题;
  • 如果是真的要用函數(shù)來(lái)改變列表,那么一定要有清晰的思路抱冷,程序非常簡(jiǎn)單而且是臨時(shí)代碼

(以上這些對(duì)字典一樣適用)

其中第二點(diǎn)是最關(guān)鍵的崔列。我們需要辨別對(duì)可變對(duì)象的哪些操作是不會(huì)改變列表的,哪些是只訪問(wèn)這個(gè)列表的而不進(jìn)行修改的旺遮。這些都是為了能夠提高代碼復(fù)用時(shí)的穩(wěn)定性赵讯。

裝飾器和函數(shù)對(duì)象

這個(gè)就不展開(kāi)來(lái)講了,跳轉(zhuǎn)本專題另一篇文章Python精進(jìn)-裝飾器與函數(shù)對(duì)象耿眉。


最后的廢話

本文和上一篇Python進(jìn)階-簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)一樣边翼,字?jǐn)?shù)真的很多。因?yàn)榧词刮抑恢v一些簡(jiǎn)單用法鸣剪,而且我的確在這么做组底,但是還是有非常多的內(nèi)容。不過(guò)已經(jīng)是Python進(jìn)階部分了西傀,多了解一些技術(shù)細(xì)節(jié)也是應(yīng)該的,但是我還要強(qiáng)調(diào)一次桶癣,編程重在練習(xí)拥褂。以上這些用過(guò)的簡(jiǎn)單例子,大可以用命令行嘗試一下看看輸出結(jié)果加深印象牙寞。
我一開(kāi)始的想法是爭(zhēng)取日更饺鹃,但像現(xiàn)在這樣的一篇1W字日更顯然是不現(xiàn)實(shí)的莫秆。我也只能晚上睡覺(jué)前有空就多少寫(xiě)一點(diǎn),爭(zhēng)取周更悔详。
下一篇計(jì)劃把之前最開(kāi)始的一篇環(huán)境配置修補(bǔ)一下镊屎,補(bǔ)充說(shuō)明一下Linux環(huán)境下Python的配置問(wèn)題以及遠(yuǎn)程連接的問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茄螃,一起剝皮案震驚了整個(gè)濱河市缝驳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌归苍,老刑警劉巖用狱,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拼弃,居然都是意外死亡夏伊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)吻氧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)溺忧,“玉大人,你說(shuō)我怎么就攤上這事盯孙÷成” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵镀梭,是天一觀的道長(zhǎng)刀森。 經(jīng)常有香客問(wèn)我,道長(zhǎng)报账,這世上最難降的妖魔是什么研底? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮透罢,結(jié)果婚禮上榜晦,老公的妹妹穿的比我還像新娘。我一直安慰自己羽圃,他們只是感情好乾胶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著朽寞,像睡著了一般识窿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脑融,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天喻频,我揣著相機(jī)與錄音,去河邊找鬼肘迎。 笑死甥温,一個(gè)胖子當(dāng)著我的面吹牛锻煌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姻蚓,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宋梧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了狰挡?” 一聲冷哼從身側(cè)響起捂龄,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎圆兵,沒(méi)想到半個(gè)月后跺讯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殉农,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年刀脏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片超凳。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愈污,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轮傍,到底是詐尸還是另有隱情暂雹,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布创夜,位于F島的核電站杭跪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驰吓。R本人自食惡果不足惜涧尿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望檬贰。 院中可真熱鬧姑廉,春花似錦、人聲如沸翁涤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)葵礼。三九已至号阿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸳粉,已是汗流浹背扔涧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赁严,地道東北人扰柠。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像疼约,于是被迫代替她去往敵國(guó)和親卤档。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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