【python】《Python進(jìn)階(Intermediate Python)》筆記

一個(gè)很全的在線手冊(cè)中心:
https://docs.pythontab.com/
《Intermediate Python》在線中文手冊(cè):https://docs.pythontab.com/interpy/

1.0 *args 和 **kwargs

用法

*args 和 **kwargs 主要用于函數(shù)定義。 你可以將不定數(shù)量的參數(shù)傳遞給一個(gè)函數(shù)廓握。
其中跨晴,**kwargs 允許你將不定長(zhǎng)度的鍵值對(duì), 作為參數(shù)傳遞給一個(gè)函數(shù)容客。 如果你想要在一個(gè)函數(shù)里處理帶名字的參數(shù), 你應(yīng)該使用**kwargs洁墙。
這里的不定的意思是:預(yù)先并不知道, 函數(shù)使用者會(huì)傳遞多少個(gè)參數(shù)給你, 所以在這個(gè)場(chǎng)景下使用這兩個(gè)關(guān)鍵字曙旭。 *args 是用來(lái)發(fā)送一個(gè)非鍵值對(duì)的可變數(shù)量的參數(shù)列表給一個(gè)函數(shù).

示例

def test_var_args(f_arg, *argv):
    print("first normal arg:", f_arg)
    for arg in argv:
        print("another arg through *argv:", arg)

test_var_args('yasoob', 'python', 'eggs', 'test')

def greet_me(**kwargs):
    for key, value in kwargs.items():
        print("{0} == {1}".format(key, value))

greet_me(var1="arg1", var2="arg2")

def test_args_kwargs(arg1, arg2, arg3):
    print("arg1:", arg1)
    print("arg2:", arg2)
    print("arg3:", arg3)

args = ("two", 3, 5)
test_args_kwargs(*args)

kargs = {"arg3": 3, "arg2": "two", "arg1": 5}
test_args_kwargs(**kargs)

標(biāo)準(zhǔn)參數(shù)與args荡碾、*kwargs在使用時(shí)的順序

那么如果你想在函數(shù)里同時(shí)使用所有這三種參數(shù)蕊程, 順序是這樣的:

some_func(fargs, *args, **kwargs)

什么時(shí)候用他們

最常見的用例是在寫函數(shù)裝飾器的時(shí)候
此外它也可以用來(lái)做猴子補(bǔ)丁(monkey patching)。猴子補(bǔ)丁的意思是在程序運(yùn)行時(shí)(runtime)修改某些代碼藕施。 打個(gè)比方寇损,你有一個(gè)類,里面有個(gè)叫g(shù)et_info的函數(shù)會(huì)調(diào)用一個(gè)API并返回相應(yīng)的數(shù)據(jù)裳食。如果我們想測(cè)試它矛市,可以把API調(diào)用替換成一些測(cè)試數(shù)據(jù)。例如:

import someclass

def get_info(self, *args):
    return "Test data"

someclass.get_info = get_info

猴子補(bǔ)痘寤觥:monkey patch指的是在運(yùn)行時(shí)動(dòng)態(tài)替換,一般是在startup的時(shí)候.
用過gevent就會(huì)知道,會(huì)在最開頭的地方gevent.monkey.patch_all();把標(biāo)準(zhǔn)庫(kù)中的thread/socket等給替換掉.這樣我們?cè)诤竺媸褂胹ocket的時(shí)候可以跟平常一樣使用,無(wú)需修改任何代碼,但是它變成非阻塞的了.
例如浊吏,想把json 替換為ujson(ujson效率更高),不需要把每個(gè)文件的import json 修改為import ujson as json
其實(shí)只需要在進(jìn)程startup的地方monkey patch就行了.是影響整個(gè)進(jìn)程空間的.
同一進(jìn)程空間中一個(gè)module只會(huì)被運(yùn)行一次.
下面是代碼main.py:

import json
import ujson
def monkey_patch_json():
   json.__name__ = 'ujson'
   json.dumps = ujson.dumps
   json.loads = ujson.loads

monkey_patch_json()
print 'main.py',json.__name__

2.0 調(diào)試

從命令行運(yùn)行

你可以在命令行使用Python debugger運(yùn)行一個(gè)腳本:


這會(huì)觸發(fā)debugger在腳本第一行指令處停止執(zhí)行救氯。這在腳本很短時(shí)會(huì)很有幫助找田。你可以通過(Pdb)模式接著查看變量信息,并且逐行調(diào)試着憨。

從腳本內(nèi)部運(yùn)行

同時(shí)墩衙,你也可以在腳本內(nèi)部設(shè)置斷點(diǎn),這樣就可以在某些特定點(diǎn)查看變量信息和各種執(zhí)行時(shí)信息了。這里將使用pdb.set_trace()方法來(lái)實(shí)現(xiàn)漆改。

import pdb

def make_bread():
    pdb.set_trace()
    return "I don't have time"

print(make_bread())

命令列表:
c: 繼續(xù)執(zhí)行
w: 顯示當(dāng)前正在執(zhí)行的代碼行的上下文信息
a: 打印當(dāng)前函數(shù)的參數(shù)列表
s: 執(zhí)行當(dāng)前代碼行心铃,并停在第一個(gè)能停的地方(相當(dāng)于單步進(jìn)入)
n: 繼續(xù)執(zhí)行到當(dāng)前函數(shù)的下一行,或者當(dāng)前行直接返回(單步跳過)

3.0 生成器(Generators)

迭代器:

迭代器是一個(gè)讓程序員可以遍歷一個(gè)容器(特別是列表)的對(duì)象挫剑。然而去扣,一個(gè)迭代器在遍歷并讀取一個(gè)容器的數(shù)據(jù)元素時(shí),并不會(huì)執(zhí)行一個(gè)迭代暮顺。

  • 可迭代對(duì)象(Iterable)
    Python中任意的對(duì)象厅篓,只要它定義了可以返回一個(gè)迭代器的iter方法,或者定義了可以支持下標(biāo)索引的getitem方法(這些雙下劃線方法會(huì)在其他章節(jié)中全面解釋)捶码,那么它就是一個(gè)可迭代對(duì)象羽氮。簡(jiǎn)單說(shuō),可迭代對(duì)象就是能提供迭代器的任意對(duì)象惫恼。
  • 迭代器(Iterator)
    任意對(duì)象档押,只要定義了next(Python2) 或者next方法,它就是一個(gè)迭代器
  • 迭代(Iteration)
    用簡(jiǎn)單的話講祈纯,它就是從某個(gè)地方(比如一個(gè)列表)取出一個(gè)元素的過程令宿。當(dāng)我們使用一個(gè)循環(huán)來(lái)遍歷某個(gè)東西時(shí),這個(gè)過程本身就叫迭代⊥罂現(xiàn)在既然我們有了這些術(shù)語(yǔ)的基本理解粒没,那我們開始理解生成器吧。

生成器(Generators):

生成器也是一種迭代器簇爆,但是你只能對(duì)其迭代一次癞松。這是因?yàn)樗鼈儾]有把所有的值存在內(nèi)存中,而是在運(yùn)行時(shí)生成值入蛆。你通過遍歷來(lái)使用它們响蓉,要么用一個(gè)“for”循環(huán),要么將它們傳遞給任意可以進(jìn)行迭代的函數(shù)和結(jié)構(gòu)哨毁。大多數(shù)時(shí)候生成器是以函數(shù)來(lái)實(shí)現(xiàn)的枫甲。然而,它們并不返回一個(gè)值扼褪,而是yield(暫且譯作“生出”)一個(gè)值想幻。

生成器函數(shù)的例子:

def generator_function():
    for i in range(10):
        yield i

for item in generator_function():
    print(item)

# Output: 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9

這個(gè)案例并不是非常實(shí)用(這樣做會(huì)消耗大量資源)。生成器最佳應(yīng)用場(chǎng)景是:你不想同一時(shí)間將所有計(jì)算出來(lái)的大量結(jié)果集分配到內(nèi)存當(dāng)中话浇,特別是結(jié)果集里還包含循環(huán)脏毯。
下面是一個(gè)計(jì)算斐波那契數(shù)列的生成器:

# generator version
def fibon(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b

函數(shù)使用方法如下:

for x in fibon(1000000):
    print(x)

用這種方式,我們可以不用擔(dān)心它會(huì)使用大量資源凳枝。然而,之前如果我們這樣來(lái)實(shí)現(xiàn)的話:

def fibon(n):
    a = b = 1
    result = []
    for i in range(n):
        result.append(a)
        a, b = b, a + b
    return result

這也許會(huì)在計(jì)算很大的輸入?yún)?shù)時(shí),用盡所有的資源岖瑰。我們已經(jīng)討論過生成器使用一次迭代叛买,但我們并沒有測(cè)試過。在測(cè)試前你需要再知道一個(gè)Python內(nèi)置函數(shù):next()蹋订。它允許我們獲取一個(gè)序列的下一個(gè)元素率挣。那我們來(lái)驗(yàn)證下我們的理解:

def generator_function():
    for i in range(3):
        yield i

gen = generator_function()
print(next(gen))
# Output: 0
print(next(gen))
# Output: 1
print(next(gen))
# Output: 2
print(next(gen))
# Output: Traceback (most recent call last):
#            File "<stdin>", line 1, in <module>
#         StopIteration

結(jié)果驗(yàn)證:


我們可以看到,在yield掉所有的值后露戒,next()觸發(fā)了一個(gè)StopIteration的異常椒功。基本上這個(gè)異常告訴我們智什,所有的值都已經(jīng)被yield完了动漾。你也許會(huì)奇怪,為什么我們?cè)谑褂胒or循環(huán)時(shí)沒有這個(gè)異常呢荠锭?啊哈旱眯,答案很簡(jiǎn)單。for循環(huán)會(huì)自動(dòng)捕捉到這個(gè)異常并停止調(diào)用next()证九。

Python中一些內(nèi)置數(shù)據(jù)類型也支持迭代:

my_string = "Yasoob"
next(my_string)
# Output: Traceback (most recent call last):
#      File "<stdin>", line 1, in <module>
#    TypeError: str object is not an iterator

好吧删豺,這不是我們預(yù)期的。這個(gè)異常說(shuō)那個(gè)str對(duì)象不是一個(gè)迭代器愧怜。對(duì)呀页,就是這樣!它是一個(gè)可迭代對(duì)象拥坛,而不是一個(gè)迭代器蓬蝶。這意味著它支持迭代,但我們不能直接對(duì)其進(jìn)行迭代操作渴逻。那我們?cè)鯓硬拍軐?duì)它實(shí)施迭代呢疾党?是時(shí)候?qū)W習(xí)下另一個(gè)內(nèi)置函數(shù),iter惨奕。它將根據(jù)一個(gè)可迭代對(duì)象返回一個(gè)迭代器對(duì)象雪位。這里是我們?nèi)绾问褂盟?/p>

my_string = "Yasoob"
my_iter = iter(my_string)
next(my_iter)
# Output: 'Y'

4.0 Map,Filter和Reduce

5.0 set(集合)數(shù)據(jù)結(jié)構(gòu)

set(集合)是一個(gè)非常有用的數(shù)據(jù)結(jié)構(gòu)。它與列表(list)的行為類似梨撞,區(qū)別在于set不能包含重復(fù)的值雹洗。

交集

可以對(duì)比兩個(gè)集合的交集(兩個(gè)集合中都有的數(shù)據(jù)),如下:

valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.intersection(valid))
### 輸出: set(['red'])

差集

你可以用差集(difference)找出無(wú)效的數(shù)據(jù)卧波,相當(dāng)于用一個(gè)集合減去另一個(gè)集合的數(shù)據(jù)时肿,例如:

valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.difference(valid))
### 輸出: set(['brown'])

6.0 三元運(yùn)算符

三元運(yùn)算符通常在Python里被稱為條件表達(dá)式,這些表達(dá)式基于真(true)/假(not)的條件判斷港粱,在Python 2.4以上才有了三元操作..
例子:

is_fat = True
state = "fat" if is_fat else "not fat"

或使用元組:
fat = True
fitness = ("skinny", "fat")[fat]
print("Ali is ", fitness)
#輸出: Ali is fa

7.0 裝飾器

裝飾器(Decorators)是Python的一個(gè)重要部分螃成。簡(jiǎn)單地說(shuō):他們是修改其他函數(shù)的功能的函數(shù)旦签。他們有助于讓我們的代碼更簡(jiǎn)短,也更Pythonic(Python范兒)寸宏。

一切皆對(duì)象

首先我們來(lái)理解下Python中的函數(shù)

def hi(name="yasoob"):
    return "hi " + name

print(hi())
# output: 'hi yasoob'

# 我們甚至可以將一個(gè)函數(shù)賦值給一個(gè)變量宁炫,比如
greet = hi
# 我們這里沒有在使用小括號(hào),因?yàn)槲覀儾⒉皇窃谡{(diào)用hi函數(shù)
# 而是在將它放在greet變量里頭氮凝。我們嘗試運(yùn)行下這個(gè)

print(greet())
# output: 'hi yasoob'

# 如果我們刪掉舊的hi函數(shù)羔巢,看看會(huì)發(fā)生什么!
del hi
print(hi())
#outputs: NameError

print(greet())
#outputs: 'hi yasoob'

在函數(shù)中定義函數(shù)

剛才那些就是函數(shù)的基本知識(shí)了罩阵。我們來(lái)讓你的知識(shí)更進(jìn)一步竿秆。在Python中我們可以在一個(gè)函數(shù)中定義另一個(gè)函數(shù):

def hi(name="yasoob"):
    print("now you are inside the hi() function")

    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    print(greet())
    print(welcome())
    print("now you are back in the hi() function")

hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function

# 上面展示了無(wú)論何時(shí)你調(diào)用hi(), greet()和welcome()將會(huì)同時(shí)被調(diào)用。
# 然后greet()和welcome()函數(shù)在hi()函數(shù)之外是不能訪問的稿壁,比如:

greet()
#outputs: NameError: name 'greet' is not defined

那現(xiàn)在我們知道了可以在函數(shù)中定義另外的函數(shù)幽钢。也就是說(shuō):我們可以創(chuàng)建嵌套的函數(shù)。現(xiàn)在你需要再多學(xué)一點(diǎn)常摧,就是函數(shù)也能返回函數(shù)搅吁。

從函數(shù)中返回函數(shù)

其實(shí)并不需要在一個(gè)函數(shù)里去執(zhí)行另一個(gè)函數(shù),我們也可以將其作為輸出返回出來(lái):

def hi(name="yasoob"):
    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    if name == "yasoob":
        return greet
    else:
        return welcome

a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500>

#上面清晰地展示了`a`現(xiàn)在指向到hi()函數(shù)中的greet()函數(shù)
#現(xiàn)在試試這個(gè)

print(a())
#outputs: now you are in the greet() function

再次看看這個(gè)代碼落午。在if/else語(yǔ)句中我們返回greet和welcome谎懦,而不是greet()和welcome()。為什么那樣溃斋?這是因?yàn)楫?dāng)你把一對(duì)小括號(hào)放在后面界拦,這個(gè)函數(shù)就會(huì)執(zhí)行;然而如果你不放括號(hào)在它后面梗劫,那它可以被到處傳遞享甸,并且可以賦值給別的變量而不去執(zhí)行它。

你明白了嗎梳侨?讓我再稍微多解釋點(diǎn)細(xì)節(jié)蛉威。

當(dāng)我們寫下a = hi(),hi()會(huì)被執(zhí)行走哺,而由于name參數(shù)默認(rèn)是yasoob蚯嫌,所以函數(shù)greet被返回了。如果我們把語(yǔ)句改為a = hi(name = "ali")丙躏,那么welcome函數(shù)將被返回择示。我們還可以打印出hi()(),這會(huì)輸出now you are in the greet() function晒旅。

將函數(shù)作為參數(shù)傳給另一個(gè)函數(shù)

def hi():
    return "hi yasoob!"

def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())

doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
#        hi yasoob!

現(xiàn)在你已經(jīng)具備所有必需知識(shí)栅盲,來(lái)進(jìn)一步學(xué)習(xí)裝飾器真正是什么了。裝飾器讓你在一個(gè)函數(shù)的前后去執(zhí)行代碼废恋。

你的第一個(gè)裝飾器

在上一個(gè)例子里谈秫,其實(shí)我們已經(jīng)創(chuàng)建了一個(gè)裝飾器扒寄!現(xiàn)在我們修改下上一個(gè)裝飾器,并編寫一個(gè)稍微更有用點(diǎn)的程序:

def a_new_decorator(a_func):

    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")

        a_func()

        print("I am doing some boring work after executing a_func()")

    return wrapTheFunction

def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()

a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

我們剛剛應(yīng)用了之前學(xué)習(xí)到的原理拟烫。這正是python中裝飾器做的事情旗们!它們封裝一個(gè)函數(shù),并且用這樣或者那樣的方式來(lái)修改它的行為。現(xiàn)在你也許疑惑垃你,我們?cè)诖a里并沒有使用@符號(hào)辜羊?那只是一個(gè)簡(jiǎn)短的方式來(lái)生成一個(gè)被裝飾的函數(shù)。這里是我們?nèi)绾问褂聾來(lái)運(yùn)行之前的代碼:

@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")

a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()

#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

希望你現(xiàn)在對(duì)Python裝飾器的工作原理有一個(gè)基本的理解掘鄙。如果我們運(yùn)行如下代碼會(huì)存在一個(gè)問題:

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

這并不是我們想要的!Ouput輸出應(yīng)該是“a_function_requiring_decoration”。這里的函數(shù)被warpTheFunction替代了半开。它重寫了我們函數(shù)的名字和注釋文檔(docstring)。幸運(yùn)的是Python提供給我們一個(gè)簡(jiǎn)單的函數(shù)來(lái)解決這個(gè)問題赃份,那就是functools.wraps寂拆。我們修改上一個(gè)例子來(lái)使用functools.wraps:

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
    """Hey yo! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")

print(a_function_requiring_decoration.__name__)
# Output: a_function_requiring_decoration

現(xiàn)在好多了。我們接下來(lái)學(xué)習(xí)裝飾器的一些常用場(chǎng)景抓韩。

藍(lán)本規(guī)范:

from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated

@decorator_name
def func():
    return("Function is running")

can_run = True
print(func())
# Output: Function is running

can_run = False
print(func())
# Output: Function will not run

注意:@wraps接受一個(gè)函數(shù)來(lái)進(jìn)行裝飾纠永,并加入了復(fù)制函數(shù)名稱、注釋文檔谒拴、參數(shù)列表等等的功能尝江。這可以讓我們?cè)谘b飾器里面訪問在裝飾之前的函數(shù)的屬性。

使用場(chǎng)景

  • 授權(quán)
    裝飾器能有助于檢查某個(gè)人是否被授權(quán)去使用一個(gè)web應(yīng)用的端點(diǎn)(endpoint)英上。它們被大量使用于Flask和Django web框架中炭序。這里是一個(gè)例子來(lái)使用基于裝飾器的授權(quán):
from functools import wraps

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated
  • 日志
    日志是裝飾器運(yùn)用的另一個(gè)亮點(diǎn)。這是個(gè)例子:
from functools import wraps

def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logit
def addition_func(x):
   """Do some math."""
   return x + x

result = addition_func(4)
# Output: addition_func was called

帶參數(shù)的裝飾器

來(lái)想想這個(gè)問題苍日,難道@wraps不也是個(gè)裝飾器嗎惭聂?但是,它接收一個(gè)參數(shù)相恃,就像任何普通的函數(shù)能做的那樣辜纲。那么,為什么我們不也那樣做呢豆茫?
這是因?yàn)榍惹福?dāng)你使用@my_decorator語(yǔ)法時(shí),你是在應(yīng)用一個(gè)以單個(gè)函數(shù)作為參數(shù)的一個(gè)包裹函數(shù)揩魂。記住幽邓,Python里每個(gè)東西都是一個(gè)對(duì)象,而且這包括函數(shù)火脉!記住了這些牵舵,我們可以編寫一下能返回一個(gè)包裹函數(shù)的函數(shù)柒啤。

在函數(shù)中嵌入裝飾器

我們回到日志的例子,并創(chuàng)建一個(gè)包裹函數(shù)畸颅,能讓我們指定一個(gè)用于輸出的日志文件担巩。

from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打開logfile,并寫入內(nèi)容
            with open(logfile, 'a') as opened_file:
                # 現(xiàn)在將日志打到指定的logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator

@logit()
def myfunc1():
    pass

myfunc1()
# Output: myfunc1 was called
# 現(xiàn)在一個(gè)叫做 out.log 的文件出現(xiàn)了没炒,里面的內(nèi)容就是上面的字符串

@logit(logfile='func2.log')
def myfunc2():
    pass

myfunc2()
# Output: myfunc2 was called
# 現(xiàn)在一個(gè)叫做 func2.log 的文件出現(xiàn)了涛癌,里面的內(nèi)容就是上面的字符串

裝飾器類

現(xiàn)在我們有了能用于正式環(huán)境的logit裝飾器,但當(dāng)我們的應(yīng)用的某些部分還比較脆弱時(shí)送火,異常也許是需要更緊急關(guān)注的事情拳话。比方說(shuō)有時(shí)你只想打日志到一個(gè)文件。而有時(shí)你想把引起你注意的問題發(fā)送到一個(gè)email种吸,同時(shí)也保留日志弃衍,留個(gè)記錄。這是一個(gè)使用繼承的場(chǎng)景坚俗,但目前為止我們只看到過用來(lái)構(gòu)建裝飾器的函數(shù)镜盯。

幸運(yùn)的是,類也可以用來(lái)構(gòu)建裝飾器猖败。那我們現(xiàn)在以一個(gè)類而不是一個(gè)函數(shù)的方式速缆,來(lái)重新構(gòu)建logit。

from functools import wraps

class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile

    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打開logfile并寫入
            with open(self.logfile, 'a') as opened_file:
                # 現(xiàn)在將日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 現(xiàn)在恩闻,發(fā)送一個(gè)通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function

    def notify(self):
        # logit只打日志激涤,不做別的
        pass

這個(gè)實(shí)現(xiàn)有一個(gè)附加優(yōu)勢(shì),在于比嵌套函數(shù)的方式更加整潔判呕,而且包裹一個(gè)函數(shù)還是使用跟以前一樣的語(yǔ)法:

@logit()
def myfunc1():
    pass

現(xiàn)在倦踢,我們給logit創(chuàng)建子類,來(lái)添加email的功能(雖然email這個(gè)話題不會(huì)在這里展開)侠草。

class email_logit(logit):
    '''
    一個(gè)logit的實(shí)現(xiàn)版本辱挥,可以在函數(shù)調(diào)用時(shí)發(fā)送email給管理員
    '''
    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(logit, self).__init__(*args, **kwargs)

    def notify(self):
        # 發(fā)送一封email到self.email
        # 這里就不做實(shí)現(xiàn)了
        pass

從現(xiàn)在起,@email_logit將會(huì)和@logit產(chǎn)生同樣的效果边涕,但是在打日志的基礎(chǔ)上晤碘,還會(huì)多發(fā)送一封郵件給管理員。

8.0 slots魔法

在Python中功蜓,每個(gè)類都有實(shí)例屬性园爷。默認(rèn)情況下Python用一個(gè)字典來(lái)保存一個(gè)對(duì)象的實(shí)例屬性。這非常有用式撼,因?yàn)樗试S我們?cè)谶\(yùn)行時(shí)去設(shè)置任意的新屬性童社。

然而,對(duì)于有著已知屬性的小類來(lái)說(shuō)著隆,它可能是個(gè)瓶頸扰楼。這個(gè)字典浪費(fèi)了很多內(nèi)存呀癣。Python不能在對(duì)象創(chuàng)建時(shí)直接分配一個(gè)固定量的內(nèi)存來(lái)保存所有的屬性。因此如果你創(chuàng)建許多對(duì)象(我指的是成千上萬(wàn)個(gè))弦赖,它會(huì)消耗掉很多內(nèi)存项栏。
不過還是有一個(gè)方法來(lái)規(guī)避這個(gè)問題。這個(gè)方法需要使用slots來(lái)告訴Python不要使用字典蹬竖,而且只給一個(gè)固定集合的屬性分配空間沼沈。

這里是一個(gè)使用與不使用slots的例子:

不使用 __slots__:
class MyClass(object):
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()
    # ...
使用 __slots__:
class MyClass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()
    # ...

第二段代碼會(huì)為你的內(nèi)存減輕負(fù)擔(dān)。通過這個(gè)技巧币厕,有些人已經(jīng)看到內(nèi)存占用率幾乎40%~50%的減少庆冕。

9.0 容器(Collections)

Python附帶一個(gè)模塊,它包含許多容器數(shù)據(jù)類型劈榨,名字叫作collections。我們將討論它的作用和用法晦嵌。
我們將討論的是:
defaultdict
counter
deque
namedtuple
enum.Enum (包含在Python 3.4以上)

defaultdict

與dict類型不同同辣,你不需要檢查key是否存在,所以我們能這樣做:

from collections import defaultdict

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favourite_colours = defaultdict(list)

for name, colour in colours:
    favourite_colours[name].append(colour)

print(favourite_colours)

輸出:
# defaultdict(<type 'list'>,
#    {'Arham': ['Green'],
#     'Yasoob': ['Yellow', 'Red'],
#     'Ahmed': ['Silver'],
#     'Ali': ['Blue', 'Black']
# })

另一種重要的是例子就是:當(dāng)你在一個(gè)字典中對(duì)一個(gè)鍵進(jìn)行嵌套賦值時(shí)惭载,如果這個(gè)鍵不存在旱函,會(huì)觸發(fā)keyError異常。 defaultdict允許我們用一個(gè)聰明的方式繞過這個(gè)問題描滔。 首先我分享一個(gè)使用dict觸發(fā)KeyError的例子棒妨,然后提供一個(gè)使用defaultdict的解決方案。

問題:

some_dict = {}
some_dict['colours']['favourite'] = "yellow"

異常輸出:KeyError: 'colours'

解決方案:

import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"

運(yùn)行正常

你可以用json.dumps打印出some_dict含长,例如:

import json
print(json.dumps(some_dict))

## 輸出: {"colours": {"favourite": "yellow"}}

counter

Counter是一個(gè)計(jì)數(shù)器券腔,它可以幫助我們針對(duì)某項(xiàng)數(shù)據(jù)進(jìn)行計(jì)數(shù)。比如它可以用來(lái)計(jì)算每個(gè)人喜歡多少種顏色:

from collections import Counter

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favs = Counter(name for name, colour in colours)
print(favs)

## 輸出:
## Counter({
##     'Yasoob': 2,
##     'Ali': 2,
##     'Arham': 1,
##     'Ahmed': 1
##  })

我們也可以在利用它統(tǒng)計(jì)一個(gè)文件拘泞,例如:

with open('filename', 'rb') as f:
    line_count = Counter(f)
print(line_count)

deque

deque提供了一個(gè)雙端隊(duì)列纷纫,你可以從頭/尾兩端添加或刪除元素。要想使用它陪腌,首先我們要從collections中導(dǎo)入deque模塊:
它的用法就像python的list辱魁,并且提供了類似的方法,例如:

from collections import deque

d = deque()
d.append('1')
d.append('2')
d.append('3')

print(len(d))

## 輸出: 3

print(d[0])

## 輸出: '1'

print(d[-1])

## 輸出: '3'

你可以從兩端取出(pop)數(shù)據(jù):

d = deque(range(5))
print(len(d))

## 輸出: 5

d.popleft()

## 輸出: 0

d.pop()

## 輸出: 4

print(d)

## 輸出: deque([1, 2, 3])

對(duì)象自省

自省(introspection)诗鸭,在計(jì)算機(jī)編程領(lǐng)域里染簇,是指在運(yùn)行時(shí)來(lái)判斷一個(gè)對(duì)象的類型的能力。它是Python的強(qiáng)項(xiàng)之一强岸。Python中所有一切都是一個(gè)對(duì)象锻弓,而且我們可以仔細(xì)勘察那些對(duì)象。Python還包含了許多內(nèi)置函數(shù)和模塊來(lái)幫助我們蝌箍。

  • dir
    返回一個(gè)列表弥咪,列出了一個(gè)對(duì)象所擁有的屬性和方法过蹂。這里是一個(gè)例子:
my_list = [1, 2, 3]
dir(my_list)
# Output: ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
# '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__',
# '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
# '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
# '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__',
# '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop',
# 'remove', 'reverse', 'sort']

上面的自省給了我們一個(gè)列表對(duì)象的所有方法的名字。當(dāng)你沒法回憶起一個(gè)方法的名字聚至,這會(huì)非常有幫助酷勺。如果我們運(yùn)行dir()而不傳入?yún)?shù),那么它會(huì)返回當(dāng)前作用域的所有名字扳躬。

  • type和id
print(type(''))
# Output: <type 'str'>

print(type([]))
# Output: <type 'list'>

print(type({}))
# Output: <type 'dict'>

print(type(dict))
# Output: <type 'type'>

print(type(3))
# Output: <type 'int'>

id()函數(shù)返回任意不同種類對(duì)象的唯一ID脆诉,舉個(gè)例子:

name = "Yasoob"
print(id(name))
# Output: 139972439030304
  • inspect模塊
    inspect模塊也提供了許多有用的函數(shù),來(lái)獲取活躍對(duì)象的信息贷币。比方說(shuō)击胜,你可以查看一個(gè)對(duì)象的成員,只需運(yùn)行:
import inspect
print(inspect.getmembers(str))
# Output: [('__add__', <slot wrapper '__add__' of ... ...

10.0 列表推導(dǎo)室

推導(dǎo)式(又稱解析式)是Python的一種獨(dú)有特性役纹,如果我被迫離開了它偶摔,我會(huì)非常想念。推導(dǎo)式是可以從一個(gè)數(shù)據(jù)序列構(gòu)建另一個(gè)新的數(shù)據(jù)序列的結(jié)構(gòu)體促脉。 共有三種推導(dǎo)辰斋,在Python2和3中都有支持:
列表(list)推導(dǎo)式
字典(dict)推導(dǎo)式
集合(set)推導(dǎo)式

  • 列表(list)推導(dǎo)式
    列表推導(dǎo)式(又稱列表解析式)提供了一種簡(jiǎn)明扼要的方法來(lái)創(chuàng)建列表。
    它的結(jié)構(gòu)是在一個(gè)中括號(hào)里包含一個(gè)表達(dá)式瘸味,然后是一個(gè)for語(yǔ)句宫仗,然后是0個(gè)或多個(gè)for或者if語(yǔ)句。那個(gè)表達(dá)式可以是任意的旁仿,意思是你可以在列表中放入任意類型的對(duì)象藕夫。返回結(jié)果將是一個(gè)新的列表,在這個(gè)以if和for語(yǔ)句為上下文的表達(dá)式運(yùn)行完成之后產(chǎn)生枯冈。
    規(guī)范
    variable = [out_exp for out_exp in input_list if out_exp == 2]
    
    這里是另外一個(gè)簡(jiǎn)明例子:
    multiples = [i for i in range(30) if i % 3 is 0]
    print(multiples)
    # Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
    
    這將對(duì)快速生成列表非常有用毅贮。
    有些人甚至更喜歡使用它而不是filter函數(shù)。
    列表推導(dǎo)式在有些情況下超贊尘奏,特別是當(dāng)你需要使用for循環(huán)來(lái)生成一個(gè)新列表嫩码。舉個(gè)例 子,你通常會(huì)這樣做:
    squared = []
    for x in range(10):
        squared.append(x**2)
    
    你可以使用列表推導(dǎo)式來(lái)簡(jiǎn)化它罪既,就像這樣:
    squared = [x**2 for x in range(10)]
    
  • 字典推導(dǎo)式(dict comprehensions)
    字典推導(dǎo)和列表推導(dǎo)的使用方法是類似的铸题。這里有個(gè)我最近發(fā)現(xiàn)的例子:
    mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
    mcase_frequency = {
        k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
        for k in mcase.keys()
    }
    # mcase_frequency == {'a': 17, 'z': 3, 'b': 34}
    
    在上面的例子中我們把同一個(gè)字母但不同大小寫的值合并起來(lái)了。
    就我個(gè)人來(lái)說(shuō)沒有大量使用字典推導(dǎo)式琢感。你還可以快速對(duì)換一個(gè)字典的鍵和值:
    {v: k for k, v in some_dict.items()}
    
    • 集合推導(dǎo)式(set comprehensions)
      它們跟列表推導(dǎo)式也是類似的丢间。 唯一的區(qū)別在于它們使用大括號(hào){}。 舉個(gè)例子:
    squared = {x**2 for x in [1, 1, 2]}
    print(squared)
    # Output: {1, 4}
    

11.0 異常

異常處理是一種藝術(shù)驹针,一旦你掌握烘挫,會(huì)授予你無(wú)窮的力量。我將要向你展示我們能處理 異常的一些方式。
最基本的術(shù)語(yǔ)里我們知道了try/except從句饮六∑渎ⅲ可能觸發(fā)異常產(chǎn)生的代碼會(huì)放到try語(yǔ)句塊里,而處理異常的代碼會(huì)在except語(yǔ)句塊里實(shí)現(xiàn)卤橄。這是一個(gè)簡(jiǎn)單的例子:

try:
    file = open('test.txt', 'rb')
except IOError as e:
    print('An IOError occurred. {}'.format(e.args[-1]))

上面的例子里绿满,我們僅僅在處理一個(gè)IOError的異常。大部分初學(xué)者還不知道的是窟扑,我們可以處理多個(gè)異常喇颁。

  • 處理多個(gè)異常
    我們可以使用三種方法來(lái)處理多個(gè)異常。
    第一種方法需要把所有可能發(fā)生的異常放到一個(gè)元組里嚎货。像這樣:
try:
    file = open('test.txt', 'rb')
except (IOError, EOFError) as e:
    print("An error occurred. {}".format(e.args[-1]))

另外一種方式是對(duì)每個(gè)單獨(dú)的異常在單獨(dú)的except語(yǔ)句塊中處理橘霎。我們想要多少個(gè)except語(yǔ)句塊都可以。這里是個(gè)例子:

try:
    file = open('test.txt', 'rb')
except EOFError as e:
    print("An EOF error occurred.")
    raise e
except IOError as e:
    print("An error occurred.")
    raise e

現(xiàn)在殖属,最后一種方式會(huì)捕獲所有異常:

try:
    file = open('test.txt', 'rb')
except Exception:
    # 打印一些異常日志姐叁,如果你想要的話
    raise
  • finally從句
    我們把我們的主程序代碼包裹進(jìn)了try從句。然后我們把一些代碼包裹進(jìn)一個(gè)except從句洗显,它會(huì)在try從句中的代碼觸發(fā)異常時(shí)執(zhí)行外潜。
    在下面的例子中,我們還會(huì)使用第三個(gè)從句墙懂,那就是finally從句。包裹到finally從句中的代碼不管異常是否觸發(fā)都將會(huì)被執(zhí)行扮念。這可以被用來(lái)在腳本執(zhí)行之后做清理工作损搬。這里是個(gè)簡(jiǎn)單的例子:
    try:
        file = open('test.txt', 'rb')
    except IOError as e:
        print('An IOError occurred. {}'.format(e.args[-1]))
    finally:
        print("This would be printed whether or not an exception occurred!")
    
    # Output: An IOError occurred. No such file or directory
    # This would be printed whether or not an exception occurred!
    
  • try/else從句
    try:
      print('I am sure no exception is going to occur!')
    except Exception:
      print('exception')
    else:
      # 這里的代碼只會(huì)在try語(yǔ)句里沒有觸發(fā)異常時(shí)運(yùn)行,
      # 但是這里的異常將 *不會(huì)* 被捕獲
      print('This would only run if no exception occurs. And an error here '
            'would NOT be caught.')
    finally:
      print('This would be printed in every case.')
    
    # Output: I am sure no exception is going to occur!
    # This would only run if no exception occurs.
    # This would be printed in every case.
    

12.0 lambda表達(dá)式

lambda表達(dá)式是一行函數(shù)。
它們?cè)谄渌Z(yǔ)言中也被稱為匿名函數(shù)柜与。如果你不想在程序中對(duì)一個(gè)函數(shù)使用兩次巧勤,你也許會(huì)想用lambda表達(dá)式,它們和普通的函數(shù)完全一樣弄匕。
原型:

    lambda 參數(shù):操作(參數(shù))

例子:

    add = lambda x, y: x + y

    print(add(3, 5))
    # Output: 8

這還有一些lambda表達(dá)式的應(yīng)用案例颅悉,可以在一些特殊情況下使用:
列表排序:

    a = [(1, 2), (4, 1), (9, 10), (13, -3)]
    a.sort(key=lambda x: x[1])

    print(a)
    # Output: [(13, -3), (4, 1), (1, 2), (9, 10)]

列表并行排序:

    data = zip(list1, list2)
    data.sort()
    list1, list2 = map(lambda t: list(t), zip(*data))

13.0 一行式

  1. 一行式

本章節(jié),我將向大家展示一些一行式的Python命令,這些程序?qū)?duì)你非常有幫助迁匠。

簡(jiǎn)易Web Server

你是否想過通過網(wǎng)絡(luò)快速共享文件剩瓶?好消息,Python為你提供了這樣的功能城丧。進(jìn)入到你要共享文件的目錄下并在命令行中運(yùn)行下面的代碼:

# Python 2
python -m SimpleHTTPServer

# Python 3
python -m http.server

漂亮的打印

你可以在Python REPL漂亮的打印出列表和字典延曙。這里是相關(guān)的代碼:

from pprint import pprint

my_dict = {'name': 'Yasoob', 'age': 'undefined', 'personality': 'awesome'}
pprint(my_dict)

這種方法在字典上更為有效。此外亡哄,如果你想快速漂亮的從文件打印出json數(shù)據(jù)枝缔,那么你可以這么做:

cat file.json | python -m json.tool

腳本性能分析 這可能在定位你的腳本中的性能瓶頸時(shí),會(huì)非常奏效:

python -m cProfile my_script.py

備注:cProfile是一個(gè)比profile更快的實(shí)現(xiàn)蚊惯,因?yàn)樗怯胏寫的

CSV轉(zhuǎn)換為json

在命令行執(zhí)行這條指令

python -c "import csv,json;print json.dumps(list(csv.reader(open('csv_file.csv'))))"

確保更換csv_file.csv為你想要轉(zhuǎn)換的csv文件

列表輾平

您可以通過使用itertools包中的itertools.chain.from_iterable輕松快速的輾平一個(gè)列表愿卸。下面是一個(gè)簡(jiǎn)單的例子:

a_list = [[1, 2], [3, 4], [5, 6]]
print(list(itertools.chain.from_iterable(a_list)))
# Output: [1, 2, 3, 4, 5, 6]

# or
print(list(itertools.chain(*a_list)))
# Output: [1, 2, 3, 4, 5, 6]

一行式的構(gòu)造器

避免類初始化時(shí)大量重復(fù)的賦值語(yǔ)句

class A(object):
    def __init__(self, a, b, c, d, e, f):
        self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})

14.0 For-Else

for循環(huán)還有一個(gè)else從句灵临,我們大多數(shù)人并不熟悉。這個(gè)else從句會(huì)在循環(huán)正常結(jié)束時(shí)執(zhí)行趴荸。這意味著儒溉,循環(huán)沒有遇到任何break. 一旦你掌握了何時(shí)何地使用它,它真的會(huì)非常有用赊舶。我自己對(duì)它真是相見恨晚睁搭。

有個(gè)常見的構(gòu)造是跑一個(gè)循環(huán),并查找一個(gè)元素笼平。如果這個(gè)元素被找到了园骆,我們使用break來(lái)中斷這個(gè)循環(huán)。有兩個(gè)場(chǎng)景會(huì)讓循環(huán)停下來(lái)寓调。 - 第一個(gè)是當(dāng)一個(gè)元素被找到锌唾,break被觸發(fā)。 - 第二個(gè)場(chǎng)景是循環(huán)結(jié)束夺英。

現(xiàn)在我們也許想知道其中哪一個(gè)晌涕,才是導(dǎo)致循環(huán)完成的原因。一個(gè)方法是先設(shè)置一個(gè)標(biāo)記痛悯,然后在循環(huán)結(jié)束時(shí)打上標(biāo)記余黎。另一個(gè)是使用else從句。

這就是for/else循環(huán)的基本結(jié)構(gòu):

for item in container:
    if search_something(item):
        # Found it!
        process(item)
        break
else:
    # Didn't find anything..
    not_found_in_container()

14.0 使用C擴(kuò)展

CPython還為開發(fā)者實(shí)現(xiàn)了一個(gè)有趣的特性载萌,使用Python可以輕松調(diào)用C代碼

開發(fā)者有三種方法可以在自己的Python代碼中來(lái)調(diào)用C編寫的函數(shù)-ctypes惧财,SWIG,Python/C API扭仁。每種方式也都有各自的利弊垮衷。

首先,我們要明確為什么要在Python中調(diào)用C乖坠?

常見原因如下: - 你要提升代碼的運(yùn)行速度搀突,而且你知道C要比Python快50倍以上 - C語(yǔ)言中有很多傳統(tǒng)類庫(kù),而且有些正是你想要的熊泵,但你又不想用Python去重寫它們 - 想對(duì)從內(nèi)存到文件接口這樣的底層資源進(jìn)行訪問 - 不需要理由仰迁,就是想這樣做

14.1 CTypes

Python中的ctypes模塊可能是Python調(diào)用C方法中最簡(jiǎn)單的一種。ctypes模塊提供了和C語(yǔ)言兼容的數(shù)據(jù)類型和函數(shù)來(lái)加載dll文件顽分,因此在調(diào)用時(shí)不需對(duì)源文件做任何的修改轩勘。也正是如此奠定了這種方法的簡(jiǎn)單性。
示例如下

實(shí)現(xiàn)兩數(shù)求和的C代碼怯邪,保存為add.c

//sample C file to add 2 numbers - int and floats

#include <stdio.h>

int add_int(int, int);
float add_float(float, float);

int add_int(int num1, int num2){
    return num1 + num2;

}

float add_float(float num1, float num2){
    return num1 + num2;

}

接下來(lái)將C文件編譯為.so文件(windows下為DLL)绊寻。下面操作會(huì)生成adder.so文件

#For Linux
$  gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c

#For Mac
$ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c

現(xiàn)在在你的Python代碼中來(lái)調(diào)用它

from ctypes import *

#load the shared object file
adder = CDLL('./adder.so')

#Find sum of integers
res_int = adder.add_int(4,5)
print "Sum of 4 and 5 = " + str(res_int)

#Find sum of floats
a = c_float(5.5)
b = c_float(4.1)

add_float = adder.add_float
add_float.restype = c_float
print "Sum of 5.5 and 4.1 = ", str(add_float(a, b))

輸出如下: 
Sum of 4 and 5 = 9
Sum of 5.5 and 4.1 =  9.60000038147

在這個(gè)例子中,C文件是自解釋的,它包含兩個(gè)函數(shù)澄步,分別實(shí)現(xiàn)了整形求和和浮點(diǎn)型求和冰蘑。

在Python文件中,一開始先導(dǎo)入ctypes模塊村缸,然后使用CDLL函數(shù)來(lái)加載我們創(chuàng)建的庫(kù)文件祠肥。這樣我們就可以通過變量adder來(lái)使用C類庫(kù)中的函數(shù)了。當(dāng)adder.add_int()被調(diào)用時(shí)梯皿,內(nèi)部將發(fā)起一個(gè)對(duì)C函數(shù)add_int的調(diào)用仇箱。ctypes接口允許我們?cè)谡{(diào)用C函數(shù)時(shí)使用原生Python中默認(rèn)的字符串型和整型。

而對(duì)于其他類似布爾型和浮點(diǎn)型這樣的類型东羹,必須要使用正確的ctype類型才可以剂桥。如向adder.add_float()函數(shù)傳參時(shí), 我們要先將Python中的十進(jìn)制值轉(zhuǎn)化為c_float類型,然后才能傳送給C函數(shù)属提。這種方法雖然簡(jiǎn)單权逗,清晰,但是卻很受限冤议。例如斟薇,并不能在C中對(duì)對(duì)象進(jìn)行操作。

14.2 SWIG

SWIG是Simplified Wrapper and Interface Generator的縮寫恕酸。是Python中調(diào)用C代碼的另一種方法堪滨。在這個(gè)方法中,開發(fā)人員必須編寫一個(gè)額外的接口文件來(lái)作為SWIG(終端工具)的入口蕊温。

Python開發(fā)者一般不會(huì)采用這種方法袱箱,因?yàn)榇蠖鄶?shù)情況它會(huì)帶來(lái)不必要的復(fù)雜。而當(dāng)你有一個(gè)C/C++代碼庫(kù)需要被多種語(yǔ)言調(diào)用時(shí)寿弱,這將是個(gè)非常不錯(cuò)的選擇犯眠。

示例如下(來(lái)自SWIG官網(wǎng)):
example.c文件中的C代碼包含了不同的變量和函數(shù):

#include <time.h>
double My_variable = 3.0;

int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);

}

int my_mod(int x, int y) {
    return (x%y);

}

char *get_time()
{
    time_t ltime;
    time(&ltime);
    return ctime(&ltime);

}

編譯它:

unix % swig -python example.i
unix % gcc -c example.c example_wrap.c \
    -I/usr/local/include/python2.1
unix % ld -shared example.o example_wrap.o -o _example.so

最后按灶,Python的輸出:

>>> import example
>>> example.fact(5)
120
>>> example.my_mod(7,3)
1
>>> example.get_time()
'Sun Feb 11 23:01:07 1996'
>>>

我們可以看到症革,使用SWIG確實(shí)達(dá)到了同樣的效果,雖然下了更多的工夫鸯旁,但如果你的目標(biāo)是多語(yǔ)言還是很值得的噪矛。

14.3 Python/C API

Python/C API可能是被最廣泛使用的方法。它不僅簡(jiǎn)單铺罢,而且可以在C代碼中操作你的Python對(duì)象艇挨。

這種方法需要以特定的方式來(lái)編寫C代碼以供Python去調(diào)用它。所有的Python對(duì)象都被表示為一種叫做PyObject的結(jié)構(gòu)體韭赘,并且Python.h頭文件中提供了各種操作它的函數(shù)缩滨。例如,如果PyObject表示為PyListType(列表類型)時(shí),那么我們便可以使用PyList_Size()函數(shù)來(lái)獲取該結(jié)構(gòu)的長(zhǎng)度脉漏,類似Python中的len(list)函數(shù)苞冯。大部分對(duì)Python原生對(duì)象的基礎(chǔ)函數(shù)和操作在Python.h頭文件中都能找到。
示例
編寫一個(gè)C擴(kuò)展侧巨,添加所有元素到一個(gè)Python列表(所有元素都是數(shù)字)
來(lái)看一下我們要實(shí)現(xiàn)的效果舅锄,這里演示了用Python調(diào)用C擴(kuò)展的代碼

#Though it looks like an ordinary python import, the addList module is implemented in C
import addList

l = [1,2,3,4,5]
print "Sum of List - " + str(l) + " = " +  str(addList.add(l))

上面的代碼和普通的Python文件并沒有什么分別,導(dǎo)入并使用了另一個(gè)叫做addList的Python模塊司忱。唯一差別就是這個(gè)模塊并不是用Python編寫的皇忿,而是C。

接下來(lái)我們看看如何用C編寫addList模塊坦仍,這可能看起來(lái)有點(diǎn)讓人難以接受鳍烁,但是一旦你了解了這之中的各種組成,你就可以一往無(wú)前了桨踪。

//Python.h has all the required function definitions to manipulate the Python objects
#include <Python.h>

//This is the function that is called from your python code
static PyObject* addList_add(PyObject* self, PyObject* args){

    PyObject * listObj;

    //The input arguments come as a tuple, we parse the args to get the various variables
    //In this case it's only one list variable, which will now be referenced by listObj
    if (! PyArg_ParseTuple( args, "O", &listObj ))
        return NULL;

    //length of the list
    long length = PyList_Size(listObj);

    //iterate over all the elements
    int i, sum =0;
    for (i = 0; i < length; i++) {
        //get an element out of the list - the element is also a python objects
        PyObject* temp = PyList_GetItem(listObj, i);
        //we know that object represents an integer - so convert it into C long
        long elem = PyInt_AsLong(temp);
        sum += elem;
    }

    //value returned back to python code - another python object
    //build value here converts the C long to a python integer
    return Py_BuildValue("i", sum);

}

//This is the docstring that corresponds to our 'add' function.
static char addList_docs[] =
"add(  ): add all elements of the list\n";

/* This table contains the relavent info mapping -
   <function-name in python module>, <actual-function>,
   <type-of-args the function expects>, <docstring associated with the function>
 */
static PyMethodDef addList_funcs[] = {
    {"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
    {NULL, NULL, 0, NULL}

};

/*
   addList is the module name, and this is the initialization block of the module.
   <desired module name>, <the-info-table>, <module's-docstring>
 */
PyMODINIT_FUNC initaddList(void){
    Py_InitModule3("addList", addList_funcs,
            "Add all ze lists");

}

逐步解釋 - Python.h頭文件中包含了所有需要的類型(Python對(duì)象類型的表示)和函數(shù)定義(對(duì)Python對(duì)象的操作) - 接下來(lái)我們編寫將要在Python調(diào)用的函數(shù), 函數(shù)傳統(tǒng)的命名方式由{模塊名}_{函數(shù)名}組成老翘,所以我們將其命名為addList_add

  • 然后填寫想在模塊內(nèi)實(shí)現(xiàn)函數(shù)的相關(guān)信息表,每行一個(gè)函數(shù)锻离,以空行作為結(jié)束 - 最后的模塊初始化塊簽名為PyMODINIT_FUNC init{模塊名}铺峭。

函數(shù)addList_add接受的參數(shù)類型為PyObject類型結(jié)構(gòu)(同時(shí)也表示為元組類型,因?yàn)镻ython中萬(wàn)物皆為對(duì)象汽纠,所以我們先用PyObject來(lái)定義)卫键。傳入的參數(shù)則通過PyArg_ParseTuple()來(lái)解析。第一個(gè)參數(shù)是被解析的參數(shù)變量虱朵。第二個(gè)參數(shù)是一個(gè)字符串莉炉,告訴我們?nèi)绾稳ソ馕鲈M中每一個(gè)元素。字符串的第n個(gè)字母正是代表著元組中第n個(gè)參數(shù)的類型碴犬。例如絮宁,"i"代表整形,"s"代表字符串類型, "O"則代表一個(gè)Python對(duì)象服协。接下來(lái)的參數(shù)都是你想要通過PyArg_ParseTuple()函數(shù)解析并保存的元素绍昂。這樣參數(shù)的數(shù)量和模塊中函數(shù)期待得到的參數(shù)數(shù)量就可以保持一致,并保證了位置的完整性偿荷。例如窘游,我們想傳入一個(gè)字符串,一個(gè)整數(shù)和一個(gè)Python列表跳纳,可以這樣去寫

int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &n, &s, &list);

在這種情況下忍饰,我們只需要提取一個(gè)列表對(duì)象,并將它存儲(chǔ)在listObj變量中寺庄。然后用列表對(duì)象中的PyList_Size()函數(shù)來(lái)獲取它的長(zhǎng)度艾蓝。就像Python中調(diào)用len(list)力崇。

現(xiàn)在我們通過循環(huán)列表,使用PyList_GetItem(list, index)函數(shù)來(lái)獲取每個(gè)元素赢织。這將返回一個(gè)PyObject*對(duì)象餐曹。既然Python對(duì)象也能表示PyIntType,我們只要使用PyInt_AsLong(PyObj *)函數(shù)便可獲得我們所需要的值敌厘。我們對(duì)每個(gè)元素都這樣處理台猴,最后再得到它們的總和。

總和將被轉(zhuǎn)化為一個(gè)Python對(duì)象并通過Py_BuildValue()返回給Python代碼俱两,這里的i表示我們要返回一個(gè)Python整形對(duì)象饱狂。

現(xiàn)在我們已經(jīng)編寫完C模塊了。將下列代碼保存為setup.py

#build the modules

from distutils.core import setup, Extension

setup(name='addList', version='1.0',  \
      ext_modules=[Extension('addList', ['adder.c'])])
并且運(yùn)行

python setup.py install

現(xiàn)在應(yīng)該已經(jīng)將我們的C文件編譯安裝到我們的Python模塊中了宪彩。

在一番辛苦后休讳,讓我們來(lái)驗(yàn)證下我們的模塊是否有效

#module that talks to the C code
import addList

l = [1,2,3,4,5]
print "Sum of List - " + str(l) + " = " +  str(addList.add(l))

輸出結(jié)果如下

Sum of List - [1, 2, 3, 4, 5] = 15

如你所見,我們已經(jīng)使用Python.h API成功開發(fā)出了我們第一個(gè)Python C擴(kuò)展尿孔。這種方法看似復(fù)雜俊柔,但你一旦習(xí)慣,它將變的非常有效活合。

Python調(diào)用C代碼的另一種方式便是使用Cython讓Python編譯的更快雏婶。但是Cython和傳統(tǒng)的Python比起來(lái)可以將它理解為另一種語(yǔ)言,所以我們就不在這里過多描述了白指。

15 open函數(shù)

open 函數(shù)可以打開一個(gè)文件留晚。超級(jí)簡(jiǎn)單吧?大多數(shù)時(shí)候告嘲,我們看到它這樣被使用:

f = open('photo.jpg', 'r+')
jpgdata = f.read()
f.close()

有三個(gè)錯(cuò)誤存在于上面的代碼中错维。
open的返回值是一個(gè)文件句柄,從操作系統(tǒng)托付給你的Python程序橄唬。一旦你處理完文件赋焕,你會(huì)想要?dú)w還這個(gè)文件句柄,只有這樣你的程序不會(huì)超出一次能打開的文件句柄的數(shù)量上限仰楚。

顯式地調(diào)用close關(guān)閉了這個(gè)文件句柄隆判,但前提是只有在read成功的情況下。如果有任意異常正好在f = open(...)之后產(chǎn)生缸血,f.close()將不會(huì)被調(diào)用(取決于Python解釋器的做法蜜氨,文件句柄可能還是會(huì)被歸還械筛,但那是另外的話題了)捎泻。為了確保不管異常是否觸發(fā),文件都能關(guān)閉埋哟,我們將其包裹成一個(gè)with語(yǔ)句:

with open('photo.jpg', 'r+') as f:
    jpgdata = f.read()

open的第一個(gè)參數(shù)是文件名笆豁。第二個(gè)(mode 打開模式)決定了這個(gè)文件如何被打開郎汪。

如果你想讀取文件,傳入r
如果你想讀取并寫入文件闯狱,傳入r+
如果你想覆蓋寫入文件煞赢,傳入w
如果你想在文件末尾附加內(nèi)容,傳入a

jpg圖像文件一般不是人寫的(而且其實(shí)不是人直接可讀的)哄孤,因此你應(yīng)該以二進(jìn)制模式來(lái)打開它們照筑,方法是在mode字符串后加一個(gè)b(你可以看看開頭的例子里,正確的方式應(yīng)該是rb)瘦陈。
如果你以文本模式打開一些東西(比如凝危,加一個(gè)t,或者就用r/r+/w/a),你還必須知道要使用哪種編碼晨逝。對(duì)于計(jì)算機(jī)來(lái)說(shuō)蛾默,所有的文件都是字節(jié),而不是字符捉貌。
那你怎么找出正在讀的文件是用哪種編碼寫的呢支鸡?好吧,不幸的是趁窃,并沒有一個(gè)十分簡(jiǎn)單的方式來(lái)檢測(cè)編碼牧挣。在不同的編碼中,同樣的字節(jié)可以表示不同醒陆,但同樣有效的字符浸踩。因此,你必須依賴一個(gè)元數(shù)據(jù)(比如统求,在HTTP頭信息里)來(lái)找出編碼检碗。越來(lái)越多的是,文件格式將編碼定義成UTF-8码邻。
有了這些基礎(chǔ)知識(shí)折剃,我們來(lái)寫一個(gè)程序,讀取一個(gè)文件像屋,檢測(cè)它是否是JPG(提示:這些文件頭部以字節(jié)FF D8開始)怕犁,把對(duì)輸入文件的描述寫入一個(gè)文本文件。

import io

with open('photo.jpg', 'rb') as inf:
    jpgdata = inf.read()

if jpgdata.startswith(b'\xff\xd8'):
    text = u'This is a JPEG file (%d bytes long)\n'
else:
    text = u'This is a random file (%d bytes long)\n'

with io.open('summary.txt', 'w', encoding='utf-8') as outf:
    outf.write(text % len(jpgdata))

我敢肯定己莺,現(xiàn)在你會(huì)正確地使用open啦奏甫!

16 協(xié)程

Python中的協(xié)程和生成器很相似但又稍有不同。主要區(qū)別在于: 生成器是數(shù)據(jù)的生產(chǎn)者 協(xié)程則是數(shù)據(jù)的消費(fèi)者
首先我們先來(lái)回顧下生成器的創(chuàng)建過程凌受。我們可以這樣去創(chuàng)建一個(gè)生成器:

def fib():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a+b

然后我們經(jīng)常在for循環(huán)中這樣使用它:

    for i in fib():
        print i

這樣做不僅快而且不會(huì)給內(nèi)存帶來(lái)壓力阵子,因?yàn)槲覀兯枰闹刀际莿?dòng)態(tài)生成的而不是將他們存儲(chǔ)在一個(gè)列表中。更概括的說(shuō)如果現(xiàn)在我們?cè)谏厦娴睦又惺褂?code>yield便可獲得了一個(gè)協(xié)程胜蛉。協(xié)程會(huì)消費(fèi)掉發(fā)送給它的值挠进。Python實(shí)現(xiàn)的grep就是個(gè)很好的例子:

    def grep(pattern):
        print("Searching for", pattern)
        while True:
            line = (yield)
            if pattern in line:
                print(line) 

等等色乾!yield返回了什么?啊哈领突,我們已經(jīng)把它變成了一個(gè)協(xié)程暖璧。它將不再包含任何初始值,相反要從外部傳值給它君旦。我們可以通過send()方法向它傳值澎办。這有個(gè)例子:

    search = grep('coroutine')
    next(search)
    #output: Searching for coroutine
    search.send("I love you")
    search.send("Don't you love me?")
    search.send("I love coroutine instead!")
    #output: I love coroutine instead!

發(fā)送的值會(huì)被yield接收。我們?yōu)槭裁匆\(yùn)行next()方法呢金砍?這樣做正是為了啟動(dòng)一個(gè)協(xié)程浮驳。就像協(xié)程中包含的生成器并不是立刻執(zhí)行,而是通過next()方法來(lái)響應(yīng)send()方法捞魁。因此至会,你必須通過next()方法來(lái)執(zhí)行yield表達(dá)式。

我們可以通過調(diào)用close()方法來(lái)關(guān)閉一個(gè)協(xié)程谱俭。像這樣:

    search = grep('coroutine')
    search.close()

更多協(xié)程相關(guān)知識(shí)的學(xué)習(xí)大家可以參考David Beazley的這份精彩演講奉件。

17 函數(shù)緩存 (Function caching)

函數(shù)緩存允許我們將一個(gè)函數(shù)對(duì)于給定參數(shù)的返回值緩存起來(lái)。
當(dāng)一個(gè)I/O密集的函數(shù)被頻繁使用相同的參數(shù)調(diào)用的時(shí)候昆著,函數(shù)緩存可以節(jié)約時(shí)間县貌。
在Python 3.2版本以前我們只有寫一個(gè)自定義的實(shí)現(xiàn)。在Python 3.2以后版本凑懂,有個(gè)lru_cache的裝飾器煤痕,允許我們將一個(gè)函數(shù)的返回值快速地緩存或取消緩存。
我們來(lái)實(shí)現(xiàn)一個(gè)斐波那契計(jì)算器接谨,并使用lru_cache摆碉。

from functools import lru_cache

@lru_cache(maxsize=32)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> print([fib(n) for n in range(10)])
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

那個(gè)maxsize參數(shù)是告訴lru_cache,最多緩存最近多少個(gè)返回值脓豪。
我們也可以輕松地對(duì)返回值清空緩存巷帝,通過這樣:

fib.cache_clear()

18 上下文管理器(Context managers)

上下文管理器允許你在有需要的時(shí)候,精確地分配和釋放資源扫夜。

使用上下文管理器最廣泛的案例就是with語(yǔ)句了楞泼。
想象下你有兩個(gè)需要結(jié)對(duì)執(zhí)行的相關(guān)操作,然后還要在它們中間放置一段代碼笤闯。
上下文管理器就是專門讓你做這種事情的堕阔。舉個(gè)例子:

with open('some_file', 'w') as opened_file:
    opened_file.write('Hola!')

上面這段代碼打開了一個(gè)文件,往里面寫入了一些數(shù)據(jù)颗味,然后關(guān)閉該文件超陆。如果在往文件寫數(shù)據(jù)時(shí)發(fā)生異常,它也會(huì)嘗試去關(guān)閉文件脱衙。上面那段代碼與這一段是等價(jià)的:

file = open('some_file', 'w')
try:
    file.write('Hola!')
finally:
    file.close()

當(dāng)與第一個(gè)例子對(duì)比時(shí)侥猬,我們可以看到,通過使用with捐韩,許多樣板代碼(boilerplate code)被消掉了退唠。 這就是with語(yǔ)句的主要優(yōu)勢(shì),它確保我們的文件會(huì)被關(guān)閉荤胁,而不用關(guān)注嵌套代碼如何退出瞧预。

上下文管理器的一個(gè)常見用例,是資源的加鎖和解鎖仅政,以及關(guān)閉已打開的文件(就像我已經(jīng)展示給你看的)垢油。

讓我們看看如何來(lái)實(shí)現(xiàn)我們自己的上下文管理器。這會(huì)讓我們更完全地理解在這些場(chǎng)景背后都發(fā)生著什么圆丹。

18.1 基于類的實(shí)現(xiàn)

一個(gè)上下文管理器的類滩愁,最起碼要定義_enter_和_exit_方法。
讓我們來(lái)構(gòu)造我們自己的開啟文件的上下文管理器辫封,并學(xué)習(xí)下基礎(chǔ)知識(shí)硝枉。

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        self.file_obj.close()

通過定義_enter_和_exit_方法显设,我們可以在with語(yǔ)句里使用它悄窃。我們來(lái)試試:

with File('demo.txt', 'w') as opened_file:
    opened_file.write('Hola!')

我們的_exit_函數(shù)接受三個(gè)參數(shù)。這些參數(shù)對(duì)于每個(gè)上下文管理器類中的_exit_方法都是必須的瓷式。我們來(lái)談?wù)勗诘讓佣及l(fā)生了什么欣福。

  • with語(yǔ)句先暫存了File類的_exit_方法
  • 然后它調(diào)用File類的_enter_方法
  • _enter_方法打開文件并返回給with語(yǔ)句
  • 打開的文件句柄被傳遞給opened_file參數(shù)
  • 我們使用.write()來(lái)寫文件
  • with語(yǔ)句調(diào)用之前暫存的_exit_方法
  • _exit_方法關(guān)閉了文件

18.2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末责球,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拓劝,更是在濱河造成了極大的恐慌雏逾,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郑临,死亡現(xiàn)場(chǎng)離奇詭異校套,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)牧抵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門笛匙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人犀变,你說(shuō)我怎么就攤上這事妹孙。” “怎么了获枝?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蠢正,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我省店,道長(zhǎng)嚣崭,這世上最難降的妖魔是什么笨触? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮雹舀,結(jié)果婚禮上芦劣,老公的妹妹穿的比我還像新娘。我一直安慰自己说榆,他們只是感情好虚吟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著签财,像睡著了一般串慰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唱蒸,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天邦鲫,我揣著相機(jī)與錄音,去河邊找鬼神汹。 笑死掂碱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慎冤。 我是一名探鬼主播疼燥,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚁堤!你這毒婦竟也來(lái)了醉者?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤披诗,失蹤者是張志新(化名)和其女友劉穎撬即,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呈队,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剥槐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宪摧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粒竖。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖几于,靈堂內(nèi)的尸體忽然破棺而出蕊苗,到底是詐尸還是另有隱情,我是刑警寧澤沿彭,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布朽砰,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瞧柔。R本人自食惡果不足惜漆弄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望造锅。 院中可真熱鬧撼唾,春花似錦、人聲如沸备绽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肺素。三九已至,卻和暖如春宇驾,著一層夾襖步出監(jiān)牢的瞬間倍靡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工课舍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塌西,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓筝尾,卻偏偏與公主長(zhǎng)得像捡需,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筹淫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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

  • 一站辉、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,744評(píng)論 0 10
  • 〇、前言 本文共108張圖损姜,流量黨請(qǐng)慎重饰剥! 歷時(shí)1個(gè)半月,我把自己學(xué)習(xí)Python基礎(chǔ)知識(shí)的框架詳細(xì)梳理了一遍摧阅。 ...
    Raxxie閱讀 18,952評(píng)論 17 410
  • 1汰蓉、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明先生_X自主閱讀 15,979評(píng)論 3 119
  • 包(lib)、模塊(module) 在Python中棒卷,存在包和模塊兩個(gè)常見概念顾孽。 模塊:編寫Python代碼的py...
    清清子衿木子水心閱讀 3,803評(píng)論 0 27
  • 要堅(jiān)持一件事情岩齿,談何容易?最重要的是要有時(shí)間苞俘?其實(shí)盹沈,不是的。我更樂意用偷時(shí)間這個(gè)詞,在無(wú)聲無(wú)息中完成自己的堅(jiān)持...
    PePing閱讀 240評(píng)論 0 0