Python函數(shù)式編程

在 Python 中使用函數(shù)式編程的最佳實(shí)踐帘瞭!

簡(jiǎn) 介

Python 是一種功能豐富的高級(jí)編程語(yǔ)言。它有通用的標(biāo)準(zhǔn)庫(kù)呈昔,支持多種編程語(yǔ)言范式挥等,還有許多內(nèi)部的透明度。如果你愿意韩肝,還可以查看 Python 的底層并修改触菜,甚至能在程序運(yùn)行的時(shí)候直接修改運(yùn)行時(shí)。

我最近注意到一個(gè)有經(jīng)驗(yàn)的 Python 程序員使用 Python 的新方法哀峻。就像許多 Python 新手一樣涡相,我在第一次看到 Python 時(shí)喜歡它的簡(jiǎn)單易懂的基本循環(huán)、函數(shù)和類定義的語(yǔ)法剩蟀。在掌握了基礎(chǔ)語(yǔ)法之后催蝗,我開(kāi)始對(duì)高級(jí)功能感興趣,如繼承育特、生成器丙号、元編程等。但是缰冤,我不太清楚它們的使用方法犬缨,經(jīng)常會(huì)在不恰當(dāng)?shù)牡胤绞褂谩S幸欢螘r(shí)間里我寫的代碼復(fù)雜又難理解棉浸。后來(lái)我反復(fù)修改怀薛,特別是需要長(zhǎng)期在同一段代碼上工作時(shí),最終會(huì)將大部分代碼慢慢改回使用基本的函數(shù)迷郑、循環(huán)枝恋、單例類创倔。

盡管如此,那些高級(jí)功能一定有其存在的理由焚碌,它們也一定是非常重要的工具畦攘。很明顯,“怎樣編寫優(yōu)秀的代碼”是個(gè)非常廣泛的話題十电,甚至沒(méi)有唯一的正確答案知押!相反,這篇文章的目標(biāo)是一個(gè)特定的話題:Python 中函數(shù)式編程的應(yīng)用鹃骂。我將討論函數(shù)式是什么朗徊,怎樣在 Python 中使用,并根據(jù)我的經(jīng)驗(yàn)介紹最佳使用方法偎漫。

號(hào):923414804
    群里有志同道合的小伙伴,互幫互助有缆,
    群里有不錯(cuò)的視頻學(xué)習(xí)教程和PDF象踊!

什么是函數(shù)式編程?

函數(shù)式編程(簡(jiǎn)稱 FP)是一種編程范式棚壁,其中最基本的元素是不可修改的值杯矩,以及不與其他函數(shù)共享狀態(tài)的“純函數(shù)”。純函數(shù)對(duì)于給定的輸入永遠(yuǎn)返回同樣的輸出袖外,而且不會(huì)修改任何數(shù)據(jù)史隆,也不會(huì)造成副作用。因此曼验,純函數(shù)經(jīng)常與數(shù)學(xué)運(yùn)算比較泌射。例如,3+4 永遠(yuǎn)等于 7鬓照,不管同時(shí)進(jìn)行了其他任何數(shù)學(xué)運(yùn)算熔酷,也不管之前進(jìn)行了多少次加法運(yùn)算。

有了純函數(shù)和不可修改的值豺裆,程序員就可以創(chuàng)建邏輯結(jié)構(gòu)了拒秘。迭代可以用遞歸代替,因?yàn)檫f歸才是讓同一個(gè)動(dòng)作多次執(zhí)行的“函數(shù)式”做法臭猜。函數(shù)使用新的輸入調(diào)用自己躺酒,直到參數(shù)滿足某個(gè)終止條件。此外蔑歌,還有高階函數(shù)羹应,它的輸入是其他函數(shù),返回另一個(gè)函數(shù)丐膝。我稍后會(huì)介紹這個(gè)概念量愧。

盡管函數(shù)式編程從上世紀(jì)五十年代就出現(xiàn)了钾菊,而且許多語(yǔ)言也都實(shí)現(xiàn)了它,但它并沒(méi)有完全地描述一門語(yǔ)言偎肃。Clojure煞烫、Common Lisp、Haskell 和 OCaml 都是以函數(shù)式為主的語(yǔ)言累颂,也都融合了其他不同的編程語(yǔ)言概念滞详,如類型系統(tǒng)、嚴(yán)格或懶惰求值等紊馏。大多數(shù)語(yǔ)言還用某種方法支持副作用料饥,如寫入文件、讀取文件等朱监,通常這些副作用都被仔細(xì)地標(biāo)記為“不純凈”岸啡。

人們通常都認(rèn)為函數(shù)式很深?yuàn)W,而且與可實(shí)踐性相比赫编,它更看重優(yōu)雅和簡(jiǎn)潔巡蘸。大公司很少會(huì)在大規(guī)模項(xiàng)目上依賴于函數(shù)式為主的語(yǔ)言,即使要用也是在較小的范圍內(nèi)擂送,遠(yuǎn)遠(yuǎn)不如其他 C++悦荒、Java、Python 等語(yǔ)言流行嘹吨。但是搬味,F(xiàn)P 實(shí)際上只是一種框架,一種考慮邏輯流的方式蟀拷,它本身也有優(yōu)點(diǎn)和缺點(diǎn)碰纬,而且也能與其他編程范式配合使用。

Python 支持什么匹厘?

盡管Python并不是以函數(shù)式為主的語(yǔ)言嘀趟,但對(duì)它來(lái)說(shuō)支持函數(shù)式編程也相對(duì)比較容易,因?yàn)镻ython中的一切都是對(duì)象愈诚。這意味著函數(shù)定義也可以賦給變量并傳遞她按。

def add(a, b):
    return a + b

plus = add

plus(3, 4)  # returns 7

Lambda

通過(guò) Lambda 表達(dá)式的語(yǔ)法,可以用聲明式的方式創(chuàng)建函數(shù)炕柔。關(guān)鍵字 lambda 來(lái)自希臘字母酌泰,經(jīng)常在正式的數(shù)學(xué)邏輯中用來(lái)描述函數(shù)和變量的虛擬綁定,即“l(fā)ambda 演算”匕累,它的歷史比函數(shù)式編程還要久遠(yuǎn)陵刹。這一概念的另一個(gè)術(shù)語(yǔ)叫做“匿名函數(shù)”,因?yàn)?lambda 函數(shù)可以直接嵌入到行內(nèi)使用,不需要事先指定名稱帮碰。將匿名函數(shù)賦值給變量后拷沸,它的行為與正常函數(shù)完全一樣咖刃。

(lambda a, b: a + b)(3, 4)  # returns 7

addition = lambda a, b: a + b
addition(3, 4)  # returns 7

lambda 函數(shù)最常見(jiàn)的用法就是提供給那些接受可調(diào)用對(duì)象作為參數(shù)的函數(shù)∷傅牵“可調(diào)用對(duì)象”是任何能夠通過(guò)括號(hào)調(diào)用的東西粮宛,具體來(lái)說(shuō)有類烹玉、函數(shù)和方法狗热。其中最常見(jiàn)的用法就是在對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行排序時(shí)钞馁,通過(guò)參數(shù)的鍵指定排序的相對(duì)順序。

authors = ['Octavia Butler', 'Isaac Asimov', 'Neal Stephenson', 'Margaret Atwood', 'Usula K Le Guin', 'Ray Bradbury']
sorted(authors, key=len)  # Returns list ordered by length of author name
sorted(authors, key=lambda name: name.split()[-1])  # Returns list ordered alphabetically by last name.

行內(nèi)嵌入式 lambda 函數(shù)的缺點(diǎn)在于它不會(huì)在棧跟蹤中顯示名稱匿刮,可能會(huì)給調(diào)試帶來(lái)麻煩僧凰。

Functools

高階函數(shù)是函數(shù)式編程的精華,部分由 Python 直接提供熟丸,部分通過(guò) functools 函數(shù)庫(kù)提供训措。你可能在大規(guī)模分布式數(shù)據(jù)分析方面聽(tīng)說(shuō)過(guò) map 和 reduce,但實(shí)際上它們也是最重要的兩個(gè)高階函數(shù)光羞。map 在給定序列的每個(gè)元素上執(zhí)行函數(shù)隙弛,然后返回結(jié)果的序列;reduce 使用一個(gè)函數(shù)收集序列中的每個(gè)元素狞山,然后返回單個(gè)值。

val = [1, 2, 3, 4, 5, 6]

# Multiply every item by two
list(map(lambda x: x * 2, val)) # [2, 4, 6, 8, 10, 12]
# Take the factorial by multiplying the value so far to the next item
reduce(lambda: x, y: x * y, val, 1) # 1 * 1 * 2 * 3 * 4 * 5 * 6

還有許多高階函數(shù)能用其他方式操作函數(shù)叉寂,其中最值得一提的就是 partial萍启,它能鎖定函數(shù)的一部分參數(shù)。這種方式也叫做“currying”屏鳍,這個(gè)術(shù)語(yǔ)來(lái)自函數(shù)式編程的先驅(qū)者 Haskell Curry:

def power(base, exp):
     return base ** exp
cube = partial(power, exp=3)
cube(5)  # returns 125

關(guān)于 Python 中的 FP 概念的具體介紹勘纯,以及怎樣優(yōu)先使用函數(shù)式進(jìn)行編程,我推薦 Mary Rose Cook 的這篇文章(https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming)钓瞭。

這些函數(shù)可以將許多行的循環(huán)轉(zhuǎn)變成極其精簡(jiǎn)的一行代碼驳遵。但是,一般的程序員也很難理解這些代碼山涡,特別是 Python 原本與英語(yǔ)十分類似的語(yǔ)法流堤结。個(gè)人經(jīng)驗(yàn),我永遠(yuǎn)都記不住參數(shù)的順序鸭丛,以及每個(gè)函數(shù)的功能竞穷,盡管我查了這么多次手冊(cè)。但我強(qiáng)烈建議嘗試一下這些函數(shù)鳞溉,以了解一些 FP 的概念瘾带,而且有時(shí)候我認(rèn)為它們才是正確的選擇,如下一節(jié)的例子所示熟菲。

修飾器

高階函數(shù)也以修飾器的形式融入了日常的 Python 編程中看政。定義修飾器的方法就反映了這一點(diǎn)朴恳,而@符號(hào)實(shí)際上只是個(gè)語(yǔ)法糖,將被修飾的函數(shù)傳遞給修飾器作為參數(shù)允蚣。下面就定義了一個(gè)簡(jiǎn)單的修飾器于颖,它會(huì)將給定的代碼重試三次,返回第一個(gè)成功的值厉萝,或者在三次嘗試都失敗之后放棄并拋出最后的異常恍飘。

def retry(func):
    def retried_function(*args, **kwargs):
        exc = None
        for _ in range(3):
            try:
               return func(*args, **kwargs)
            except Exception as exc:
               print("Exception raised while calling %s with args:%s, kwargs: %s. Retrying" % (func, args, kwargs).

        raise exc
     return retried_function

@retry
def do_something_risky():
    ...

retried_function = retry(do_something_risky)  # No need to use `@`

這個(gè)修飾器的輸入和輸出的類型和值完全一樣,但這并不是必須的谴垫。修飾器可以添加或減少參數(shù)章母,也可以改變參數(shù)的類型。它們也可以通過(guò)本身的參數(shù)進(jìn)行配置翩剪。我想指出的是乳怎,修飾器本身不一定是“純函數(shù)”,它們可以(而且經(jīng)常會(huì))有副作用前弯,只不過(guò)是恰巧使用了高階函數(shù)而已蚪缀。

就像許多中級(jí)或高級(jí) Python 技巧一樣,這個(gè)功能非常強(qiáng)大恕出,但也很容易造成混亂询枚。你必須使用 functools.wrap 修飾器進(jìn)行修飾,否則調(diào)用的函數(shù)和棧跟蹤中看到的函數(shù)名字會(huì)不一樣浙巫。我見(jiàn)過(guò)一些修飾器會(huì)做一些非常復(fù)雜或非常重要的事情金蜀,如解析 json blob 中的值,或者處理認(rèn)證的畴。我還見(jiàn)過(guò)同一個(gè)函數(shù)或方法定義上的多層修飾器渊抄,必須掌握修飾器的應(yīng)用次序才能正確理解。我認(rèn)為通過(guò)利用內(nèi)置的修飾器如“staticmethod”可以幫助理解丧裁,或者編寫最簡(jiǎn)單的修飾器來(lái)避免大量樣板代碼护桦,但如果你想讓你的代碼符合類型檢查的話,那么盡量不要去修改輸入或輸出的類型煎娇。

我的建議

函數(shù)式編程很有趣二庵,而且學(xué)習(xí)舒適區(qū)之外的編程范式能夠?yàn)槟銕?lái)靈活性,而且也可以讓你從另一個(gè)角度考慮問(wèn)題缓呛。但是眨猎,我不推薦使用 Python 時(shí)以函數(shù)式為主,特別是在舊的代碼庫(kù)中不要這么做强经。除了上面我提到的那些坑之外睡陪,還有下面的理由:

開(kāi)始使用 Python 不需要理解 FP。這樣做很可能會(huì)迷惑其他閱讀者,或者迷惑未來(lái)的自己兰迫。

你無(wú)法保證任何你依賴的代碼(通過(guò) pip 安裝的模塊信殊,或其他同事的代碼)是函數(shù)式的,是純凈的汁果。你也不知道你自己的代碼是否像你想象的那么純凈涡拘。與函數(shù)式為主的語(yǔ)言不同,Python 的語(yǔ)法或編譯器不會(huì)幫你強(qiáng)制純凈据德,也不會(huì)幫你消滅某些 Bug鳄乏。將副作用和高階函數(shù)混合在一起回導(dǎo)致巨大的混亂,因?yàn)槟阈枰撟C兩種不同的復(fù)雜性棘利,其難度是兩者的乘積橱野。

使用帶有類型注釋的高階函數(shù)是高級(jí)技巧。類型簽名通常是又長(zhǎng)又笨拙的“Callable”的嵌套善玫。例如水援,一個(gè)簡(jiǎn)單的返回輸入函數(shù)的高階修飾器,其定義是“F = TypeVar[‘F’, bound=Callable[..., Any]]”茅郎,然后標(biāo)注是“def transparent(func: F) -> F: return func”蜗元。也許你懶得研究正確的簽名的寫法,而直接使用“Any”代替了系冗。

那么奕扣,我們應(yīng)該使用函數(shù)式編程的哪部分呢?

純函數(shù)

只要可能并且合理掌敬,就應(yīng)該盡量保持函數(shù)“純凈”成畦,并仔細(xì)考慮應(yīng)當(dāng)在何處保持改變了的狀態(tài),并仔細(xì)地標(biāo)記好涝开。這樣能讓單元測(cè)試變得更容易,你不需要做太多 set-up 和 tear-down框仔,也不需要太多 mocking舀武,而且測(cè)試用例不論執(zhí)行順序如何,都會(huì)產(chǎn)生預(yù)期中的結(jié)果离斩。

下面是個(gè)非函數(shù)式的例子银舱。

dictionary = ['fox', 'boss', 'orange', 'toes', 'fairy', 'cup']
def puralize(words):
   for i in range(len(words)):
       word = words[i]
       if word.endswith('s') or word.endswith('x'):
           word += 'es'
       if word.endswith('y'):
           word = word[:-1] + 'ies'
       else:
           word += 's'
       words[i] = word

def test_pluralize():
    pluralize(dictionary)
    assert dictionary == ['foxes', 'bosses', 'oranges', 'toeses', 'fairies', 'cups']

第一次運(yùn)行 test_pluraize 時(shí)該測(cè)試能夠通過(guò),但以后每次運(yùn)行都會(huì)失敗跛梗,因?yàn)樗鼤?huì)反復(fù)添加“s”和“es”寻馏。為了讓它變成純函數(shù), 可以這樣寫:

dictionary = ['fox', 'boss', 'orange', 'toes', 'fairy', 'cup']
def puralize(words):
   result = []
   for word in words:
       word = words[i]
       if word.endswith('s') or word.endswith('x'):
           plural = word + 'es')
       if word.endswith('y'):
           plural = word[:-1] + 'ies'
       else:
           plural = +  's'
       result.append(plural)
    return result

def test_pluralize():
    result = pluralize(dictionary)
    assert result == ['foxes', 'bosses', 'oranges', 'toeses', 'fairies', 'cups']

注意這里并沒(méi)有使用任何 FP 特有的概念核偿,只是創(chuàng)建并返回了一個(gè)新的對(duì)象诚欠,而不是重用并修改已有的舊對(duì)象。這樣輸入的內(nèi)容也會(huì)保持不變。

雖然這個(gè)例子像個(gè)玩具轰绵,但想象一下粉寞,如果你傳遞并改變了某個(gè)復(fù)雜的對(duì)象,或者通過(guò)數(shù)據(jù)庫(kù)連接進(jìn)行了某些操作左腔。當(dāng)編寫很多很多測(cè)試用例時(shí)就會(huì)發(fā)現(xiàn)唧垦,你必須非常小心地處理測(cè)試用例的順序,或者花大量代價(jià)在每個(gè)測(cè)試用例之后清除并重新創(chuàng)建狀態(tài)液样。這些工作應(yīng)該是在 e2e 集成測(cè)試階段的活兒振亮,不應(yīng)該在比較小的單元測(cè)試階段進(jìn)行。

理解(并避免)可修改性

為什么這一點(diǎn)很重要鞭莽?有些時(shí)候列表和元組可以互換使用坊秸,因此人們經(jīng)常會(huì)在代碼中隨機(jī)使用兩者之一。于是當(dāng)你試圖修改一個(gè)元組(比如給其中一個(gè)元素賦值)時(shí)就會(huì)出錯(cuò)撮抓「窘铮或者試圖用列表作為字典的鍵,也會(huì)導(dǎo)致 TypeError丹拯,因?yàn)榱斜硎强尚薷牡恼境TM和字符串可以作為字典的鍵使用,因?yàn)樗鼈儾豢尚薷墓猿辏梢缘玫酱_定的哈希值死相,而其他數(shù)據(jù)結(jié)構(gòu)都不行,因?yàn)樗鼈兊膶?duì)象標(biāo)識(shí)即使保持不變咬像,值也會(huì)改變算撮。

最重要的是,在傳遞字典县昂、列表或集合時(shí)肮柜,它們可能會(huì)在其他上下文中被意料之外地改變。這種問(wèn)題非常難以調(diào)試倒彰∩蠖矗可修改的默認(rèn)參數(shù)就是個(gè)經(jīng)典的例子:

def add_bar(items=[]):
    items.append('bar')
    return items

l = add_bar()  # l is ['bar']
l.append('foo')
add_bar() # returns ['bar', 'foo', 'bar']

字典、集合和列表很強(qiáng)大待讳、效率很高芒澜、非常 Python,而且非常有用创淡。寫代碼時(shí)完全不使用它們是不明智的痴晦。但即使如此,我永遠(yuǎn)會(huì)在默認(rèn)參數(shù)的位置使用元組或 None(代替空字典或空列表)琳彩,并且在缺乏足夠的防御代碼的情況下誊酌,避免將可修改的數(shù)據(jù)結(jié)構(gòu)在不同的上下文中傳遞部凑。

減少類的使用

類(及其實(shí)例)的可修改性是把雙刃劍。隨著寫的 Python 代碼越來(lái)越多术辐,我開(kāi)始傾向于僅在絕對(duì)必要時(shí)才使用類砚尽,而且我?guī)缀鯊牟皇褂每尚薷牡念悓傩浴?duì)于那些高度面向?qū)ο蟮恼Z(yǔ)言(如 Java)的程序員來(lái)說(shuō)這一點(diǎn)可能很難做到辉词,但許多其他語(yǔ)言中在類層面完成的東西必孤,在 Python 可以在模塊層面完成。例如瑞躺,如果需要將函數(shù)或常量或命名空間分組敷搪,那么可以把它們一起放到另一個(gè) .py 文件中。

我經(jīng)炒鄙冢看到一些類的目的是保存幾個(gè)命名變量的值赡勘,這種情況下 namedtuple(其類型是 typing.NamedTuple)就足夠,而且還是不可改變的捞镰。

from collections import namedtuple
VerbTenses = namedtuple('VerbTenses', ['past', 'present', 'future'])
# versus
class VerbTenses(object):
    def __init__(self, past, present, future):
        self.past = past,
        self.present = present
        self.future = future

如果確實(shí)需要狀態(tài)的來(lái)源闸与,而且多個(gè)視圖都需要改變?cè)摖顟B(tài),那么類是絕佳的選擇岸售。此外践樱,與靜態(tài)方法相比,我更傾向于單例純函數(shù)凸丸,這樣它們能在其他上下文中組合使用拷邢。

可修改的類屬性非常危險(xiǎn),因?yàn)樗鼈儗儆陬惗x而不是類實(shí)例屎慢,因此可能會(huì)不小心修改到同一個(gè)類的多個(gè)實(shí)例中的狀態(tài)瞭稼!

class Bus(object):
     passengers = set()
     def add_passenger(self, person):
        self.passengers.add(person)

bus1 = Bus()
bus2 = Bus()
bus1.add_passenger('abe')
bus2.add_passenger('bertha')
bus1.passengers  # returns ['abe', 'bertha']
bus2.passengers  # also ['abe', 'bertha']

冪等性

任何實(shí)際的大規(guī)模復(fù)雜系統(tǒng)都可能會(huì)失敗,而失敗就要重試腻惠。矩陣代數(shù)中的“冪等性”的概念也存在于 API 設(shè)計(jì)中环肘,但對(duì)于函數(shù)式編程來(lái)說(shuō),傳遞之前的輸出給冪等函數(shù)集灌,永遠(yuǎn)會(huì)返回相同的值悔雹。因此,重做某件事情會(huì)收斂到相同的值绝页。因此,上述 pluralize 函數(shù)更理想的寫法為:寂恬,首先檢查輸入是否已是復(fù)數(shù)续誉,再考慮怎樣計(jì)算出復(fù)數(shù)形式。

lambda 和高階函數(shù)使用上的注意點(diǎn)

我發(fā)現(xiàn)初肉,在進(jìn)行短小的操作(如獲取排序的鍵供 sort 使用)時(shí)使用 lambda 非常方便酷鸦。但如果 lambda 超過(guò)一行,那么使用普通的函數(shù)定義可能更好。通常傳遞函數(shù)可以避免重復(fù)臼隔,但我在使用時(shí)經(jīng)常提醒自己嘹裂,額外的結(jié)構(gòu)是否會(huì)讓代碼清晰度下降。通常摔握,將其分解成更小的輔助函數(shù)會(huì)更清晰寄狼。

在需要時(shí)使用生成器和高階函數(shù)

有時(shí)候你會(huì)遇到抽象的生成器和迭代器,它們可能會(huì)返回巨大或者無(wú)限的序列氨淌。一個(gè)例子就是 range泊愧。在 Python 3 中,range 默認(rèn)是生成器(相當(dāng)于Python 2 中的 xrange)盛正,避免在迭代大數(shù)字時(shí)出現(xiàn)內(nèi)存不足的錯(cuò)誤删咱,如range(10 ** 10)。如果要在一個(gè)可能很大的生成器的每個(gè)元素上執(zhí)行某個(gè)操作豪筝,那么使用 map痰滋、filter 之類的工具可能是最好的選擇。

與此相似续崖,如果不知道你新寫的迭代器可能會(huì)返回多少結(jié)果敲街,但可能會(huì)很大,那就應(yīng)該定義一個(gè)生成器袜刷。但是聪富,并不是每個(gè)人都愿意去使用生成器,他們可能更希望使用列表解析式(list comprehension)著蟹,從而導(dǎo)致你一開(kāi)始想要避免的內(nèi)存不足錯(cuò)誤墩蔓。生成器是 Python 對(duì)于流式編程的實(shí)現(xiàn),它也不一定是函數(shù)式的萧豆,所以它也有其他 Python 編程方式擁有的安全性缺陷奸披。

結(jié) 論

通過(guò)瀏覽功能、庫(kù)和內(nèi)部代碼來(lái)理解自己選擇的編程語(yǔ)言涮雷,毫無(wú)疑問(wèn)能幫你在調(diào)試和閱讀代碼方面提高速度阵面。理解其他語(yǔ)言或編程語(yǔ)言理論方面的思想也很有意思,而且能讓你成為更強(qiáng)大洪鸭、無(wú)所不通的程序員样刷。

但是,成為Python的高級(jí)程序員意味著你不僅要知道能做什么览爵,更要理解哪種才是最有效的方式置鼻。在Python中應(yīng)用函數(shù)式編程可能很容易。

為了保持優(yōu)雅蜓竹,特別是在共享的代碼中保持優(yōu)雅箕母,我認(rèn)為最好是使用純粹的函數(shù)式思想储藐,讓代碼更容易預(yù)測(cè),從而更容易維護(hù)嘶是,并且具有冪等性钙勃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市聂喇,隨后出現(xiàn)的幾起案子辖源,更是在濱河造成了極大的恐慌,老刑警劉巖授帕,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件同木,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跛十,警方通過(guò)查閱死者的電腦和手機(jī)彤路,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芥映,“玉大人洲尊,你說(shuō)我怎么就攤上這事∧纹” “怎么了坞嘀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惊来。 經(jīng)常有香客問(wèn)我丽涩,道長(zhǎng),這世上最難降的妖魔是什么裁蚁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任矢渊,我火速辦了婚禮,結(jié)果婚禮上枉证,老公的妹妹穿的比我還像新娘矮男。我一直安慰自己,他們只是感情好室谚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布毡鉴。 她就那樣靜靜地躺著,像睡著了一般秒赤。 火紅的嫁衣襯著肌膚如雪猪瞬。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天入篮,我揣著相機(jī)與錄音陈瘦,去河邊找鬼。 笑死崎弃,一個(gè)胖子當(dāng)著我的面吹牛甘晤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饲做,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼线婚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了盆均?” 一聲冷哼從身側(cè)響起塞弊,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泪姨,沒(méi)想到半個(gè)月后游沿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肮砾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年诀黍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仗处。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡眯勾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出婆誓,到底是詐尸還是另有隱情吃环,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布洋幻,位于F島的核電站郁轻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏文留。R本人自食惡果不足惜好唯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厂庇。 院中可真熱鬧渠啊,春花似錦、人聲如沸权旷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拄氯。三九已至躲查,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間译柏,已是汗流浹背镣煮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鄙麦,地道東北人典唇。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓镊折,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親介衔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恨胚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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