第二十七章 一個強(qiáng)大的命令行解析器 Plumbum

一、基本介紹

本文主要介紹如何使用 Plumbum CLI 工具包來開發(fā) Python 命令行應(yīng)用程序,這是一個非常 Pythonic率拒、容易使用倍谜、功能強(qiáng)大的工具包迈螟,非常值得廣大 Python 程序員掌握并使用。

輕松執(zhí)行程序的另一方面是輕松編寫 CLI 程序尔崔。Python 腳本一般使用 optparse 或者最新的 argparse 及其衍生品來開發(fā)命令行工具答毫,但是所有這些表現(xiàn)力有限,而且非常不直觀(甚至不夠 Pythonic)季春。Plumbum 的 CLI 工具包提供了一個程序化的方法來構(gòu)建命令行應(yīng)用程序洗搂,不需要創(chuàng)建一個解析器對象,然后填充一系列“選項(xiàng)”,該 CLI 工具包使用內(nèi)省機(jī)制將這些原語轉(zhuǎn)義成 Pythonic 結(jié)構(gòu)耘拇。

總體來看撵颊,Plumbum CLI 應(yīng)用程序是一個繼承自 plumbum.cli.Application 的類。

這些類定義了一個 main() 方法惫叛,并且可選地公開出方法和屬性來作為命令行的選項(xiàng)倡勇。這些選項(xiàng)可能需要參數(shù),而任何剩余的位置參數(shù)會根據(jù) main 函數(shù)的聲明來將其賦予 main 方法嘉涌。

二妻熊、安裝

pip3 install plumbum

三、基本使用

一個簡單的 CLI 應(yīng)用程序看起來像如下這樣:

# plumexample.py  文件內(nèi)容如下
from plumbum import cli
class MyApp():
    pass
if __name__ == "__main__":
    MyApp.run()

你可以運(yùn)行該程序:

$ python3 plum命令行參數(shù)解析.py -h
Usage:
    plum命令行參數(shù)解析.py [SWITCHES] args...

Meta-switches:
    -h, --help         Prints this help message and quits
    --help-all         Prints help messages of all sub-commands and
                       quits
    -v, --version      Prints the program's version and quits

到現(xiàn)在為止洛心,你只看到了非彻淘牛基本的使用。我們現(xiàn)在開始探索該庫词身。

新版本 1.6.1: 你可以直接運(yùn)行應(yīng)用程序 MyApp()厅目,不需要參數(shù),也不需要調(diào)用 .main()法严。

四损敷、高級用法

1 應(yīng)用程序

Application 類是你的應(yīng)用程序的“容器”,該“容器”由一個你需要實(shí)現(xiàn)的main()方法和任何數(shù)量公開選項(xiàng)函數(shù)和屬性深啤。你的應(yīng)用程序的入口是類方法 run拗馒,該方法實(shí)例化你的類、解析參數(shù)溯街、調(diào)用所有的選項(xiàng)函數(shù)诱桂,然后使用給的位置參數(shù)來調(diào)用main()函數(shù)。為了從命令行運(yùn)行你的應(yīng)用程序呈昔,你所要做的是:

if __name__ == "__main__":
 MyApp.run()

除了 run()main()挥等,Application 類還公開了兩個內(nèi)置的選項(xiàng)函數(shù):help()version(),分別用于顯示幫助和程序的版本堤尾。默認(rèn)情況下肝劲,--hep-h 會調(diào)用 help()--version-v 會調(diào)用 version()郭宝,這些函數(shù)被調(diào)用后會顯示相應(yīng)的信息然后退出(沒有處理任何其他選項(xiàng))辞槐。

你可以通過定義類屬性來自定義 help()version() 顯示的信息,比如 PROGNAME粘室、 VERSIONDESCRIPTION榄檬。舉例:

class MyApp(cli.Application):
 PROGNAME = "Foo"
 VERSION = "7.3"

2 顏色

新版本 1.6

該庫也支持終端字符顏色控制。你可以直接將 PROGNAME, VERSIONDESCRIPTION 變?yōu)閹ь伾淖址瓮场H绻憬o PROGNAME 設(shè)置了顏色鹿榜,你會得到自定義的程序名字和顏色先朦。使用方法字符串的顏色可以通過設(shè)置 COLOR_USAGE 來生效,不同選項(xiàng)組的顏色可以通過設(shè)置 COLOR_GROUPS 字典來生效犬缨。

舉例如下:

class MyApp(cli.Application):
    PROGNAME = colors.green
    VERSION = colors.blue | "1.0.2"
    COLOR_GROUPS = {"Meta-switches" : colors.bold & colors.yellow}
    opts =  cli.Flag("--ops", help=colors.magenta | "This is help")

執(zhí)行結(jié)果

SimpleColorCLI.py 1.0.2
Usage:
 SimpleColorCLI.py [SWITCHES]
Meta-switches
 -h, --help         Prints this help message and quits
 --help-all         Print help messages of all subcommands and quit
 -v, --version      Prints the program's version and quits
Switches
 --ops              This is help

3 選項(xiàng)函數(shù)

switch 裝飾器是該 CLI 開發(fā)工具包的“靈魂”,它會公開你的 CLI 應(yīng)用程序的方法來作為 CLI 命令行選項(xiàng)棉浸,這些方法運(yùn)行通過命令行來調(diào)用怀薛。我們測試下如下應(yīng)用:

class MyApp(cli.Application):
    _allow_root = False       # provide a default
    @cli.switch("--log-to-file", str)
    def log_to_file(self, filename):
        """Sets the file into which logs will be emitted"""
        logger.addHandler(FileHandle(filename))
    @cli.switch(["-r", "--root"])
    def allow_as_root(self):
        """If given, allow running as root"""
        self._allow_root = True
    def main(self):
        if os.geteuid() == 0 and not self._allow_root:
            raise ValueError("cannot run as root")

當(dāng)程序運(yùn)行時,選項(xiàng)函數(shù)通過相應(yīng)的參數(shù)被調(diào)用迷郑。比如枝恋,$ ./myapp.py --log-to-file=/tmp/log 將被轉(zhuǎn)化成調(diào)用 app.log_to_file("/tmp/log")。在選項(xiàng)函數(shù)被執(zhí)行后嗡害,程序的控制權(quán)會被傳遞到 main 方法焚碌。

注意

方法的文檔字符串和參數(shù)名字會被用來渲染幫助信息,盡量保持你的代碼 DRY霸妹。

autoswitch 可以從函數(shù)名字中推斷出選項(xiàng)的名稱十电,舉例如下:

@cli.autoswitch(str)
def log_to_file(self, filename):
pass

這會將選項(xiàng)函數(shù)和 --log-to-file 綁定

4 選項(xiàng)參數(shù)

如上面例子所示,選項(xiàng)函數(shù)可能沒有參數(shù)(不包括 self)或者有一個參數(shù)叹螟。如果選項(xiàng)函數(shù)接受一個參數(shù)鹃骂,必須指明該參數(shù)的類型。如果你不需要特殊的驗(yàn)證罢绽,只需傳遞 str,否則良价,您可能會傳遞任何類型(或?qū)嶋H上可調(diào)用的任何類型)寝殴,該類型將接收一個字符串并將其轉(zhuǎn)換為有意義的對象。如果轉(zhuǎn)換是不可行的明垢,那么會拋出 TypeError 或者 ValueError 異常蚣常。

舉例:

class MyApp(cli.Application):
    _port = 8080
    @cli.switch(["-p"], int)
    def server_port(self, port):
        self._port = port
    def main(self):
        print(self._port)
$ ./example.py -p 17
17
$ ./example.py -p foo
Argument of -p expected to be <type 'int'>, not 'foo':
    ValueError("invalid literal for int() with base 10: 'foo'",)

工具包包含兩個額外的“類型”(或者是是驗(yàn)證器):RangeSetRange 指定一個最小值和最大值袖外,限定一個整數(shù)在該范圍內(nèi)(閉區(qū)間)史隆。

Set 指定一組允許的值,并且期望參數(shù)匹配這些值中的一個曼验。示例如下:

class MyApp(cli.Application):
    _port = 8080
    _mode = "TCP"
    @cli.switch("-p", cli.Range(1024,65535))
    def server_port(self, port):
        self._port = port
    @cli.switch("-m", cli.Set("TCP", "UDP", 
                              # 下面是不轉(zhuǎn)換大小寫
                              case_sensitive = False
                             ))
    def server_mode(self, mode):
        self._mode = mode
    def main(self):
        print(self._port, self._mode)
$ ./example.py -p 17
Argument of -p expected to be [1024..65535], not '17':
    ValueError('Not in range [1024..65535]',)
$ ./example.py -m foo
Argument of -m expected to be Set('udp', 'tcp'), not 'foo':
    ValueError("Expected one of ['UDP', 'TCP']",)

注意 工具包中還有其他有用的驗(yàn)證器:
ExistingFile(確保給定的參數(shù)是一個存在的文件)
ExistingDirectory(確保給定的參數(shù)是一個存在的目錄)
NonexistentPath(確保給定的參數(shù)是一個不存在的路徑)泌射。
所有這些將參數(shù)轉(zhuǎn)換為本地路徑

5 可重復(fù)的選項(xiàng)

默認(rèn)情況下鬓照,在一條命令行中熔酷,選項(xiàng)只能指定一次,除非你給 switch 裝飾器傳遞 list = True 參數(shù)豺裆。

class MyApp(cli.Application):
    _dirs = []
    @cli.switch("-I", str, list = True)
    def include_dirs(self, dirs):
        self._dirs = dirs
    def main(self):
        print(self._dirs)
$ ./example.py -I/foo/bar -I/usr/include
['/foo/bar', '/usr/include']

注意 選項(xiàng)函數(shù)只被調(diào)用一次拒秘,它的參數(shù)將會變成一個列表号显。

6 強(qiáng)制的選項(xiàng)

如果某個選項(xiàng)是必須的,你可以給 switch 裝飾器傳遞 mandatory = True 來實(shí)現(xiàn)躺酒。這樣的話押蚤,如果用戶不指定該選項(xiàng),那么程序?qū)o法運(yùn)行羹应。

class MyApp(cli.Application):
    __port = 80
    @cli.switch('-p',int, mandatory=True)
    def server_port(self, port):
        self.__port = port

    def main(self):
        print(self.__port)

7 選項(xiàng)依賴

很多時候揽碘,一個選項(xiàng)的出現(xiàn)依賴另一個選項(xiàng),比如园匹,如果不給定 -y 選項(xiàng)雳刺,那么 -x 選項(xiàng)是無法給定的。這種限制可以通過給 switch 裝飾器傳遞 requires 參數(shù)來實(shí)現(xiàn)裸违,該參數(shù)是一個當(dāng)前選項(xiàng)所依賴的選項(xiàng)名稱列表掖桦。如果不指定某個選項(xiàng)所依賴的其他選項(xiàng),那么用戶是無法運(yùn)行程序的供汛。

class MyApp(cli.Application):
    @cli.switch("--log-to-file", str)
    def log_to_file(self, filename):
        logger.addHandler(logging.FileHandler(filename))
    @cli.switch("--verbose", requires = ["--log-to-file"])
    def verbose(self):
        logger.setLevel(logging.DEBUG)
$ ./example --verbose
Given --verbose, the following are missing ['log-to-file']

警告 選項(xiàng)函數(shù)的調(diào)用順序和命令行指定的選項(xiàng)的順序是一致的枪汪。目前不支持在程序運(yùn)行時計(jì)算選項(xiàng)函數(shù)調(diào)用的拓?fù)漤樞颍菍頃倪M(jìn)怔昨。

8 選項(xiàng)互斥

有些選項(xiàng)依賴其他選項(xiàng)料饥,但是有些選項(xiàng)是和其他選項(xiàng)互斥的。比如朱监,--verbose--terse 同時存在是不合理的岸啡。為此,你可以給 switch 裝飾器指定 excludes 列表來實(shí)現(xiàn)赫编。

class MyApp(cli.Application):
    @cli.switch("--log-to-file", str)
    def log_to_file(self, filename):
        logger.addHandler(logging.FileHandler(filename))
    @cli.switch("--verbose", requires = ["--log-to-file"], excludes = ["--terse"])
    def verbose(self):
        logger.setLevel(logging.DEBUG)
    @cli.switch("--terse", requires = ["--log-to-file"], excludes = ["--verbose"])
    def terse(self):
        logger.setLevel(logging.WARNING)
$ ./example --log-to-file=log.txt --verbose --terse
Given --verbose, the following are invalid ['--terse']

9 選項(xiàng)分組

如果你希望在幫助信息中將某些選項(xiàng)組合在一起巡蘸,你可以給 switch 裝飾器指定 group = "Group Name", Group Name 可以是任意字符串。當(dāng)顯示幫助信息的時候擂送,所有屬于同一個組的選項(xiàng)會被聚合在一起悦荒。注意,分組不影響選項(xiàng)的處理嘹吨,但是可以增強(qiáng)幫助信息的可讀性搬味。

10 選項(xiàng)屬性

很多時候只需要將選項(xiàng)的參數(shù)存儲到類的數(shù)據(jù)屬性中,或者當(dāng)某個數(shù)據(jù)屬性給定后設(shè)置一個標(biāo)志蟀拷。為此碰纬,工具包提供了 SwitchAttr,這是一個數(shù)據(jù)描述符问芬,用來存儲參數(shù)悦析。 還提供了兩個額外的

  • cli.Flag() 如果使用其中的選項(xiàng),會給其賦予默認(rèn)值此衅, 用于實(shí)現(xiàn)布爾值的參數(shù)
  • CountOf 某個選項(xiàng)出現(xiàn)的次數(shù)强戴。
class MyApp(cli.Application):
    log_file = cli.SwitchAttr("--log-file", str, default = None)
    enable_logging = cli.Flag("--no-log", default = True)
    verbosity_level = cli.CountOf("-v")
    def main(self):
        print(self.log_file, self.verbosity_level)
        if self.enable_logging:
            print(self.enable_logging)
$ ./example.py -v --log-file=log.txt -v --no-log -vvv
log.txt False 5

11 Main

一旦當(dāng)所有命令行參數(shù)被處理后 亭螟,main() 方法會獲取程序的控制,并且可以有任意數(shù)量的位置參數(shù)骑歹,比如预烙,在 cp -r /foo /bar 中, /foo/bar 是位置參數(shù)。程序接受位置參數(shù)的數(shù)量依賴于 main() 函數(shù)的聲明:如果 main 方法有 5 個參數(shù)道媚,2 個是有默認(rèn)值的默伍,那么用戶最少需要提供 3 個位置參數(shù)并且總數(shù)量不能多于 5 個。如果 main 方法的聲明中使用的是可變參數(shù)(*args)衰琐,那么位置參數(shù)的個數(shù)是沒有限制的。

class MyApp(cli.Application):
    def main(self, src, dst, mode = "normal"):
        print(src, dst, mode)
$ ./example.py /foo /bar
/foo /bar normal
$ ./example.py /foo /bar spam
/foo /bar spam
$ ./example.py /foo
Expected at least 2 positional arguments, got ['/foo']
$ ./example.py /foo /bar spam bacon
Expected at most 3 positional arguments, got ['/foo', '/bar', 'spam', 'bacon']

注意 該方法的聲明也用于生成幫助信息炼蹦,例如:

Usage:  [SWITCHES] src dst [mode='normal']

使用可變參數(shù):

class MyApp(cli.Application):
    def main(self, src, dst, *eggs):
        print(src, dst, eggs)
$ ./example.py a b c d
a b ('c', 'd')
$ ./example.py --help
Usage:  [SWITCHES] src dst eggs...
Meta-switches:
    -h, --help                 Prints this help message and quits
    -v, --version              Prints the program's version and quits

11 位置驗(yàn)證

新版本 1.6

你可以使用 cli.positional 裝飾器提供的驗(yàn)證器來驗(yàn)證位置參數(shù)羡宙。

只需在裝飾器中傳遞與 main 函數(shù)中的位置參數(shù)相匹配的驗(yàn)證器即可。

例如:

class MyApp(cli.Application):
    @cli.positional(cli.ExistingFile,
                    cli.NonexistentPath)
    def main(self, infile, *outfiles):
        "infile is a path, outfiles are a list of paths, proper errors are given"

如果你的程序只在 Python 3 中運(yùn)行掐隐,你可以使用下面的方式來指定驗(yàn)證器狗热,例如:

class MyApp(cli.Application):
    def main(self, 
             infile : cli.ExistingFile,
             *outfiles : cli.NonexistentPath):
    "Identical to above MyApp"

如果 positional 裝飾器存在,那么注解會被忽略虑省。

五匿刮、擴(kuò)展部分

1 子命令

新版本 1.1

隨著 CLI 應(yīng)用程序的擴(kuò)展,功能變的越來越多探颈,一個通常的做法是將其邏輯分成多個子應(yīng)用(或者子命令)熟丸。一個典型的例子是版本控制系統(tǒng),比如 git伪节,git 是根命令光羞,在這之下的子命令比如 commit 或者 push 是嵌套的。git 甚至支持命令別名怀大,這允許用戶自己創(chuàng)建一些子命令纱兑。Plumbum 寫類似這樣的程序是很輕松的。

在我們開始了解代碼之前化借,先強(qiáng)調(diào)兩件事情:

  • 在 Plumbum 中潜慎,每個子命令都是一個完整的 cli.Application 應(yīng)用,你可以單獨(dú)執(zhí)行它蓖康,或者從所謂的根命令中分離出來铐炫。當(dāng)應(yīng)用程序單獨(dú)執(zhí)行時,它的父屬性是 None蒜焊,當(dāng)以子命令運(yùn)行時驳遵,它的父屬性指向父應(yīng)用程序。同樣山涡,當(dāng)父應(yīng)用使用子命令執(zhí)行時堤结,它的內(nèi)嵌命令被設(shè)置成內(nèi)嵌應(yīng)用唆迁。

  • 每個子命令只負(fù)責(zé)它自己的選項(xiàng)參數(shù)(直到下一個子命令)。這允許應(yīng)用在內(nèi)嵌應(yīng)用調(diào)用之前來處理它自己的選項(xiàng)和位置參數(shù)竞穷。例如 git --foo=bar spam push origin --tags:根應(yīng)用 git 負(fù)責(zé)選項(xiàng) --foo 和位置選項(xiàng) spam 唐责,內(nèi)嵌應(yīng)用 push 負(fù)責(zé)在它之后的參數(shù)。從理論上講瘾带,你可以將多個子應(yīng)用程序嵌套到另一個應(yīng)用程序中鼠哥,但在實(shí)踐中,通常嵌套層級只有一層看政。

這是一個模仿版本控制系統(tǒng)的例子 geet朴恳。我們有一個根應(yīng)用 Geet ,它有兩個子命令 GeetCommitGeetPush:這兩個子命令通過 subcommand 裝飾器來將其附加到根應(yīng)用允蚣。

class Geet(cli.Application):
    """The l33t version control"""
    VERSION = "1.7.2"
    def main(self, *args):
        if args:
            print("Unknown command {0!r}".format(args[0]))
            return 1   # error exit code
        if not self.nested_command:    # will be ``None`` if no sub-command follows
            print("No command given")
            return 1   # error exit code
@Geet.subcommand("commit")     # attach 'geet commit'
class GeetCommit(cli.Application):
    """creates a new commit in the current branch"""
    auto_add = cli.Flag("-a", help = "automatically add changed files")
    message = cli.SwitchAttr("-m", str, mandatory = True, help = "sets the commit message")
    def main(self):
        print("doing the commit...")
        
@Geet.subcommand("push")        # attach 'geet push'
class GeetPush(cli.Application):
    """pushes the current local branch to the remote one"""
    def main(self, remote, branch = None):
        print("doing the push...")
        
if __name__ == "__main__":
    Geet.run()

注意

  • 由于 GeetCommit 也是一個 cli.Application于颖,因此你可以直接調(diào)用 GeetCommit.run() (這在應(yīng)用的上下文是合理的)
  • 你也可以不用裝飾器而使用 subcommand 方法來附加子命令:Geet.subcommand("push", GeetPush)
  • 在定義的應(yīng)用類中,定義的任何參數(shù)不可以使用 mandatory=True,否則會導(dǎo)致子命令失效

以下是運(yùn)行該應(yīng)用程序的示例:

$ python geet.py --help
geet v1.7.2
The l33t version control
Usage: geet.py [SWITCHES] [SUBCOMMAND [SWITCHES]] args...
Meta-switches:
    -h, --help                 Prints this help message and quits
    -v, --version              Prints the program's version and quits
Subcommands:
    commit                     creates a new commit in the current branch; see
                               'geet commit --help' for more info
    push                       pushes the current local branch to the remote
                               one; see 'geet push --help' for more info
$ python geet.py commit --help
geet commit v1.7.2
creates a new commit in the current branch
Usage: geet commit [SWITCHES]
Meta-switches:
    -h, --help                 Prints this help message and quits
    -v, --version              Prints the program's version and quits
Switches:
    -a                         automatically add changed files
    -m VALUE:str               sets the commit message; required
$ python geet.py commit -m "foo"
doing the commit...

六嚷兔、配置解析器

應(yīng)用程序的另一個常見的功能是配置文件解析器森渐,解析后臺 INI 配置文件:Config (或者 ConfigINI)。

如果配置文件不存在冒晰,那么將會以當(dāng)前的 key 和默認(rèn)的 value 來創(chuàng)建一個配置文件同衣,在調(diào)用 .get 方法時會得到默認(rèn)值,當(dāng)上下文管理器存在時壶运,文件會被創(chuàng)建耐齐。如果配置文件存在,那么該文件將會被讀取并且沒有任何改變蒋情。

ini 解析器默認(rèn)使用 [DEFAULT] 段蚪缀,就像 Python 的 ConfigParser。如果你想使用一個不同的段恕出,只需要在 key 中通過 . 將段和標(biāo)題分隔開询枚。比如 conf['section.item'] 會將 item 放置在 [section] 下。所有存儲在 ConfigINI 中的條目會被轉(zhuǎn)化成 str浙巫,get() 方法返回的也是str 金蜀。

使用示例:

from plumbum import cli
with cli.Config('./myapp_rc') as conf:
    one = conf.get('one', '1')
    two = conf.get('two', '2')
    conf.set("three", 3)
    conf["four"]=[33,34]
    three = conf.get("three")   # 獲取到設(shè)置的值
    conf.write()  # 假如有新設(shè)置的鍵值對,保存到當(dāng)前文件中
print(one,two, three)    # 獲取到的都是字符串類型
   

保存后的文件內(nèi)容

[DEFAULT]
one = 10
two = 2
three = 3
four = [33, 34]

[dbserver]
user = admin
port = 3306

要獲取幫助列表和更多的信息請參見 api docs的畴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渊抄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丧裁,更是在濱河造成了極大的恐慌护桦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煎娇,死亡現(xiàn)場離奇詭異二庵,居然都是意外死亡贪染,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門催享,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杭隙,“玉大人,你說我怎么就攤上這事因妙√翟鳎” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵攀涵,是天一觀的道長铣耘。 經(jīng)常有香客問我,道長以故,這世上最難降的妖魔是什么蜗细? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮据德,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘跷车。我一直安慰自己棘利,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布朽缴。 她就那樣靜靜地躺著善玫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪密强。 梳的紋絲不亂的頭發(fā)上茅郎,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機(jī)與錄音或渤,去河邊找鬼系冗。 笑死,一個胖子當(dāng)著我的面吹牛薪鹦,可吹牛的內(nèi)容都是我干的掌敬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼池磁,長吁一口氣:“原來是場噩夢啊……” “哼奔害!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起地熄,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤华临,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后端考,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雅潭,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揭厚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寻馏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棋弥。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诚欠,靈堂內(nèi)的尸體忽然破棺而出顽染,到底是詐尸還是另有隱情,我是刑警寧澤轰绵,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布粉寞,位于F島的核電站,受9級特大地震影響左腔,放射性物質(zhì)發(fā)生泄漏唧垦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一液样、第九天 我趴在偏房一處隱蔽的房頂上張望振亮。 院中可真熱鬧,春花似錦鞭莽、人聲如沸坊秸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褒搔。三九已至,卻和暖如春喷面,著一層夾襖步出監(jiān)牢的瞬間星瘾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工惧辈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琳状,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓盒齿,卻偏偏與公主長得像算撮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子县昂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

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

  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,383評論 0 5
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,101評論 1 32
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    草里有只羊閱讀 18,324評論 0 85
  • 一肮柜、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運(yùn)行的地址不確定 關(guān)于...
    SeanCST閱讀 7,813評論 0 27
  • 今天在店里上班。中午后倒彰,門外停了一輛黑色小車审洞,下來一位氣質(zhì)美女。看到她的第一眼芒澜,我就起來了仰剿,哦,原來她是我們之前的...
    莫迷閱讀 263評論 0 0