溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

[Python] 函數(shù)與函數(shù)編程

發(fā)布時間:2020-07-13 08:55:55 來源:網(wǎng)絡(luò) 閱讀:826 作者:爾冬 欄目:編程語言

1. 函數(shù)

使用def語句可定義函數(shù):

def add(x, y):
    return x + y

函數(shù)體就是在調(diào)用函數(shù)時所執(zhí)行的一系列語句。調(diào)用函數(shù)的方法是在函數(shù)名稱后面加上參數(shù)。參數(shù)的順序必須與函數(shù)定義匹配,否則會引發(fā)TypeError異常??梢詾楹瘮?shù)的參數(shù)設(shè)置默認值,例如:

def split(line, delimiter=','):
    statements

如果給最后一個參數(shù)名加上星號"*",函數(shù)就可以接受任意數(shù)量的參數(shù):

def fprintf(file, fmt, *args):
    file.write(fmt % args)
fprintf(out, "%d %s %f", 42, "hello world", 3.45)

在這個例子中,所有余下的參數(shù)都作為一個元組放入args變量。要把元組args當作參數(shù)傳遞給函數(shù),可以在函數(shù)調(diào)用中使用*args語法。例如:

def printf(fmt, *args):
    fprintf(sys.stdout, fmt, *args)

提供函數(shù)參數(shù)還有一種方式,即顯示地命名每個參數(shù)并為其指定一個值,這稱為關(guān)鍵字參數(shù),例如:

def foo(w, x, y, z):
    statements
foo(x=3, y=22, w='hello', z=[1, 2])

使用關(guān)鍵字參數(shù)時,參數(shù)的順序無關(guān)緊要。但除非提供了默認值,否則必須顯式地命名所有必需的函數(shù)參數(shù)。位置參數(shù)和關(guān)鍵字參數(shù)可以同時使用,前提是所有位置參數(shù)必須先出現(xiàn),給所有非可選參數(shù)提供值,例如:

foo('hello', 3, z=[1, 2], y=22)

如果函數(shù)定義的最后一個參數(shù)以"**"開頭,可以把所有額外的關(guān)鍵字參數(shù)都放入一個字典中,并把這個字典傳遞給參數(shù)。例如:

def make_table(data, **params):
    fgcolor = params.pop("fgcolor", "black")
    bgcolor = params.pop("bgcolor", "white")
    width = params.pop("width", None)
    if params:
        raise TypeError("Unsupported configuration options %s" % list(params))
make_table(items, fgcolor="black", bgcolor="white", border=1, border, cellpoadding=10, width=400)

關(guān)鍵字參數(shù)和可變長度參數(shù)列表可以一起使用,只要"**"參數(shù)出現(xiàn)在最后即可,例如:

def spam(*args, **kwargs):
    statements

?

2. 參數(shù)傳遞與返回值

調(diào)用函數(shù)時,函數(shù)參數(shù)僅僅是引用傳入對象的名稱。參數(shù)傳遞的基本語義和其他編程語言中已知的方式不完全相同,如“按值傳遞”和“按引用傳遞”。比如傳遞不可變的值,參數(shù)看起來實際是按值傳遞的,如果傳遞的是可變對象(如列表或字典)給函數(shù),然后再修改此可變對象,這些改動將反映在原始對象中。例如:

a = [1, 2, 3, 4, 5]
def square(items):
    for i, x in enumerate(items):
        items[i] = x * x
square(a) # a = [1, 4, 9, 16, 25]

return語句從函數(shù)返回一個值。如果沒有指定任何值或者省略return語句,就會返回None對象。如果返回值有多個,可以把它們放在一個元組中,例如:

def factor(a):
    d = 2
    while (d <= (a / 2)):
        if ((a / d) * d == a):
            return ((a / d), d)
        d = d + 1
    return (a, 1)

?

3. 作用域規(guī)則

每次執(zhí)行一個函數(shù)時,就會創(chuàng)建新的局部命名空間。該命名空間代表一個局部環(huán)境,其中包含函數(shù)參數(shù)的名稱和在函數(shù)體內(nèi)賦值的變量名稱。解析這些名稱時,解釋器將首先搜索局部命名空間。如果沒有找到匹配的名稱,它就會搜索全局命名空間。如果在全局命名空間中也找不到匹配值,最終會檢查內(nèi)置命名空間。如果仍然找不到,就會引發(fā)NameError異常。
命名空間的特性之一是在函數(shù)中對全局變量的操作,例如:

a = 42
def foo():
    a = 13
foo() # a仍然是42

執(zhí)行這段代碼時,盡量在函數(shù)foo中修改了變量a的值,但最終a仍然是42.在函數(shù)中對變量進行賦值時,這些變量始終綁定到該函數(shù)的局部命名空間中,因此函數(shù)體中的變量a引用的是一個包含值13的全新對象,而不是外部的變量。使用global語句可以改變這種行為,例如:

a = 42
def foo():
    global a
    a  = 13
foo() # a的值已變13

Python支持嵌套的函數(shù)定義,例如:

def countdown(start):
    n = start
    def display():
        print('T-minus %d' % n)
    while n > 0:
        display()
        n -= 1

使用靜態(tài)作用域綁定嵌套函數(shù)中的變量,即解析名稱時首先檢查局部作用域,而后由內(nèi)向外一層層檢查外部嵌套函數(shù)定義的作用域。如果找不到匹配,最后將搜索全局命名空間和內(nèi)置命名空間??梢允褂胣onlocal語句綁定外部變量,例如:

def countdown(start):
    n = start
    def display():
        print('T-minus %d' % n)
    def decrement():
        nonlocal n
        n -= 1
    while n > 0:
        display()
        decrement()

nonlocal聲明不會把名稱綁定到任意函數(shù)中定義的局部變量,而是搜索當前調(diào)用棧中的下一層函數(shù)定義,即動態(tài)作用域。例如:

i = 0
def foo():
    i = i + 1 # UnboundLocalError異常

盡管有一個全局變量i,但它不會給局部變量i提供值。函數(shù)定義時就確定了變量是局部的還是全局的,而且在函數(shù)中不能突然改變它們的作用域。

?

4. 函數(shù)對象與閉包

函數(shù)在Python中是第一類對象。即可以把它們當作參數(shù)傳遞給其他函數(shù),放在數(shù)據(jù)結(jié)構(gòu)中,以及作為函數(shù)的返回結(jié)果。例如:

def callf(func):
    return func()

把函數(shù)當作數(shù)據(jù)處理時,它將顯式地攜帶與定義該函數(shù)的周圍環(huán)境相關(guān)的信息。這將影響到函數(shù)中自由變量的綁定方式。例如:

# foo.py
x = 42
def callf(func):
    return func()

# main.py
import foo
x = 37
def helloworld():
    reutrn "x is %d" % x
foo.callf(helloworld) # x is 37

在上例中,即使foo.py中也定義了一個變量x,變際調(diào)用的是與helloworld()函數(shù)相同的環(huán)境中定義的值。將組成函數(shù)的語句和這些語句的執(zhí)行環(huán)境打包在一起時,得到的對象稱為閉包。事實上所有函數(shù)都擁有一個指向了定義該函數(shù)的全局命名空間的__globals__屬性。例如:

def page(url):
    def get():
        return urlopen(url).read()
    return get
python = page("http://www.python.org")
jython = page("http://www.jython.org")
pydata = python() # 獲取http://www.python.org
jydata = jython() # 獲取http://www.jython.org

?

5. 裝飾器

裝飾器是一個函數(shù),其主要用途是包裝另一個函數(shù)或類。這種包裝的首要目的是透明地修改或增強被包裝對象的行為。表示裝飾器的語法是特殊符號"@",例如:

@trace
def square(x):
    return x * x

上面的代碼可以簡化為:

def square(x):
    return x * x
square = trace(square)

現(xiàn)在考慮trace的實現(xiàn):

enable_tracing =  True
if enable_tracing:
    debug_log = open("debug.log", "w")

def trace(func):
    if enable_tracing:
        def callf(*args, **kwargs):
            debug_log.write("Calling %s: %s, %s\n" % (func.__name__, args, kwargs))
            r = func(*args, **kwargs)
            debug_log.write("%s returned %s\n" % (func.__name__, r))
            return r
        return callf
    else:
        return func

這段代碼中,trace()創(chuàng)建了寫有一些調(diào)試輸出的包裝器函數(shù),然后調(diào)用了原始函數(shù)對象。因此如果調(diào)用square()函數(shù),看到的將是包裝器中write()方法的輸出。
使用裝飾器時,它們必須出現(xiàn)在函數(shù)或類定義之前的單獨行上。可以同時使用多個裝飾器,例如:

@foo
@bar
@spam
def grok(x):
    pass\
grok = foo(bar(spam(grok)))

裝飾器也可以接受參數(shù),例如:

@eventhandler('BUTTON')
def handle_button(msg):
    ...
@eventhandler('RESET')
def handle_reset(msg):
    ...

如果提供參數(shù),裝飾器的語義如下所示:

def handle_button(msg):
    ...
temp = eventhandler('BUTTON')
handle_button = temp(handle_button)

對于類裝飾器,應(yīng)該讓裝飾器函數(shù)始終返回類對象作為結(jié)果。需要使用原始類定義的代碼可能要直接引用類成員。
?

6. 生成器與yield

函數(shù)使用yield關(guān)鍵字可以定義生成器對象。生成器是一個函數(shù),它生成一個值的序列,以便在迭代中使用,例如:

def countdown(n):
    while n > 0:
        yield n
        n -=1
    return

如果調(diào)用該函數(shù),其中的代碼不會開始執(zhí)行,它會返回一個生成器對象,該對象在__next__()被調(diào)用,例如:

c = countdown(10)
c.__next__()

調(diào)用__next__()時,生成器函數(shù)將不斷執(zhí)行語句,直到遇到y(tǒng)ield語句為止。通常不會在生成器上直接調(diào)用__next__()方法,而是在for語句、sum()或一些使用序列的其他操作中使用,例如:

for n in countdown(10):
    statements
a = sum(countdown(10))

生成器函數(shù)完成的標志是返回或引發(fā)StopIteration異常,這標志著迭代的結(jié)束。如果生成器沒有全部完成,并且不再使用,可以調(diào)用close()方法,雖然通常情況下可以不必調(diào)用,例如:

c = countdown(10)
c.__next__()
c.close()
c.__next__() # 拋出異常

在生成器函數(shù)內(nèi)部,在yield語句上出現(xiàn)GeneratorExit異常時就會調(diào)用close()方法??梢赃x擇獲取這個異常,例如:

def countdown(n):
    try:
        while n > 0:
            yield n
            n -= 1
    except GeneratorExit:
        print("Only made it to %d" % n)

?

7. 協(xié)程與yield表達式

在函數(shù)內(nèi),yield語句還可以用作出現(xiàn)在賦值運算符右邊的表達式,例如:

def receiver():
    while True:
        n = (yield)
        print("Got %s" % n)

以這種方式使用yield語句的函數(shù)稱為協(xié)程,它的執(zhí)行是為了響應(yīng)發(fā)送給它的值。它的行為也類似于生成器,例如:

r = receiver()
r.__next__()
r.send(1)
r.send(2)

在協(xié)程中需要首先調(diào)用__next__()這件事很容易被忘記,可以用一個自動完成該步驟的裝飾器來包裝協(xié)程,例如:

def coroutine(func):
    def start(*args, **kwargs):
        g = func(*args, **kwargs)
        g.next()
        return g
    return start

@coroutine
def receiver():
    while True:
        n = (yield)
        print("Got %s" % n)

r = receiver()
r.send("Hello World")

協(xié)程的運行一般是無限期的,除非它被顯式關(guān)閉或者自己退出。使用close()可以關(guān)閉輸入值的流,例如:

r.close()
r.send() # 拋出異常

關(guān)閉后如果繼續(xù)給協(xié)程發(fā)送值,就會引發(fā)StopIteration異常,close()操作將在協(xié)程內(nèi)部引發(fā)GeneratorExit異常。
?

8. 列表包含

函數(shù)的常用操作是將函數(shù)應(yīng)用給一個列表的所有項,并使用結(jié)果創(chuàng)建一個新列表。這種操作很常見,因此出現(xiàn)了叫做列表推導(dǎo)的運算符,例如:

nums = [1, 2, 3, 4, 5]
squares = [n * n for n in nums]

列表推導(dǎo)的一般語法如下:

[expression for item1 in iterable1 if condition1
                                        for item2 in iterable2 if condition2
                                        ...
                                        for itemN in iterableN if conditionN]

下面給出一些例子:

a = [-3, 5, 2, -10, 7, 8]
b = 'abc'
c = [2 * s for s in a] # c = [-6, 10, 4, -20, 14, 16]
d = [s for s in a if s >= 0] # d = [5, 2, 7, 8]
e= [(x, y) for x in a
                                for y in b
                                if x > 0] 
# e = [(5, 'a'), (5, 'b'), (5, 'c'),
                    (2, 'a'), (2, 'b'), (2, 'c'),
                    (7, 'a'), (7, 'b'), (7, 'c'),
                    (8, 'a'), (8, 'b'), (8, 'c')]
f = [(1, 2), (3, 4), (5, 6)]
g  = [math.sqrt(x * x + y * y) for x, y in f] # g = [2.23606797749979, 5.0, 7.810249675906654]

?

9. 生成器表達式

生成器表達式是一個對象,它執(zhí)行的計算與列表包含相同,但會迭代地生成結(jié)果,語法與列表包含相同,除了用圓括號代替方括號,如下:

(expression for item1 in iterable1 if condition1
                                        for item2 in iterable2 if condition2
                                        ...
                                        for itemN in iterableN if conditionN)

生成器表達式實際上不創(chuàng)建列表或者立即對圓括號內(nèi)的表達式求值,它創(chuàng)建一個通過迭代并按照需要生成值的生成器對象,例如:

a  = [1, 2, 3, 4]
b = (10 * i for i in a)
print(b.__next__())
print(b.__next__())

使用列表推導(dǎo)時,Python實際上創(chuàng)建了包含結(jié)果數(shù)據(jù)的列表。而使用生成器表達式時,Python創(chuàng)建的是只知道如何按照需要生成數(shù)據(jù)的生成器。在某些應(yīng)用中,可能影響性能和內(nèi)存使用,例如:

f = open("data.txt")
lines = (t.strip() for t in f)
comments = (t for t in lines if t[0] == '#')
for c in comments:
    print(c)

生成器表達式不會創(chuàng)建序列形式的對象,不能對它進行索引。但是,使用內(nèi)置的list()函數(shù)可以將生成器表達式轉(zhuǎn)換為列表,例如:

clist = list(comments)

?

10. lambda運算符

使用lambda語句可以創(chuàng)建表達式形式的匿名函數(shù):

lambda args: expression

args是以逗號分隔的參數(shù)列表,而expression是用到這些參數(shù)的表達式,例如:

a = lambda x, y: x + y
r = a(2, 3)

使用lambda語句定義的代碼必須是合法的表達式。lambda語句中不能出現(xiàn)多條語句和其他非表達式語句,比如for或while。
?

11. 文檔字符串

通常,函數(shù)的第一條語句會使用文檔字符串,用于描述函數(shù)的用途,例如:

def factorial(n):
    """Computes n factorial. For examples:
            >>> factorial(6)
            120
    """
    if n <= 1: return 1
    else: return n* factorial(n-1)

文檔字符串保存在函數(shù)的__doc__屬性中,IDE通常使用該函數(shù)提供交互式幫助。如果需要使用裝飾器,可能會破壞與文檔字符串相關(guān)的幫助功能,例如:

def wrap(func):
    call(*args, **kwargs):
        return func(*args, **kwargs)
    return call
@wrap
def factorial(n):
    """Computes n factorial."""

如果查目的地以上函數(shù)的幫助,可能會看到一個相當奇怪的內(nèi)容,解決方法是編寫可以傳遞函數(shù)名稱和文檔字符串的裝飾器函數(shù),例如:

def wrap(func):
    call(*args, **kwargs):
        return func(*args, **kwargs)
    call.__doc__ =  func.__doc__
    call.__name__ = func.__name__
    return call

因為這是一個常見問題,所以functools模塊提供了函數(shù)wraps,用于自動復(fù)制這些屬性,例如:

from functools import wraps
def wrap(func):
    @wrap(func)
    call(*args, **kwargs):
        return func(*args, **kwargs)
    return call

?

12. 函數(shù)屬性

可以給函數(shù)添加任意屬性,例如:

def foo():
    statements
foo.secure = 1
foo.private = 1

函數(shù)屬性保存在函數(shù)的__dict__屬性中,__dic__屬性是一個字典。和文檔字符串一樣,也要注意混合使用函數(shù)屬性和裝飾器的問題。如果使用裝飾器包裝函數(shù),實際上是由裝飾器函數(shù)而非原始函數(shù)來訪問屬性。
?

13. eval()、exec()和compile()函數(shù)

eval(str [, globals [, locals]])函數(shù)執(zhí)行一個表達式字符串并返回結(jié)果,例如:

a = eval('3 * math.sin(3.5 + x) + 7.2')

相似地,exec(str [, globals [, locals]])函數(shù)執(zhí)行一個包含任意Python代碼的字符串。例如:

a = [3, 5, 10, 13]
exec("for i in a: print(i)")

這兩個函數(shù)都會在調(diào)用者的命名空間中執(zhí)行。eval()和exec()函數(shù)可以接受一個或兩個可選的映射對象,分別用作代碼執(zhí)行的全局和局部命名空間,例如:

globals = {'x': 7, 'y': 10, 'birds': ['Parrot', 'Swallow', 'Albatross']}
locals = {}
a = eval("3 * x + 4 * y", globals, locals)
exec("fro b in birds: print(b)", globals, locals)

compile(str, filename, kind)函數(shù)將字符串編譯為字節(jié)碼,其中str是包含要編譯代碼的字符串,而filename是定義該字符串的文件,kind參數(shù)指定了要編譯代碼的類型。single表示一條語句,exec代表一組語句,而eval代表一個表達式。例如:

s = "for i inrange(0, 10): print(i)"
c = compile(s, '', 'exec')
exec(c)
s2 = "3 * x + 4 * y"
c2 = compile(s2, '', 'eval')
result = eval(c2)
向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI