天天看點

第五章 Python函數你知多少

函數作用:把一些複雜的代碼封裝起來,函數一般都是一個功能,用的時候才調用,提高重複使用率和簡化程式結構。

<b>5.1 文法</b>

def functionname(parms1, parms2, ...):

     code block

     return expression

函數以def關鍵字開頭,空格後跟函數名,括号裡面是參數,用于傳參,函數代碼段裡面引用。

<b>5.2 函數定義與調用</b>

# 定義函數

&gt;&gt;&gt; def func():

...   print "hello world!"

...   return "hello world!" 

...

# 調用函數

&gt;&gt;&gt; func()

hello world!

'hello world!'

當我們定義好函數,是不執行的,沒有任何輸出。當輸入函數名後跟雙小括号才會執行函數裡寫的代碼。

<b>順便說下print和return差別:</b>

有沒有點奇怪!為什麼print和return輸出一樣呢,return就加個單引号,貌似也沒啥明顯差別啊!其實在解釋器下所有的結果都會輸出的。

先了解下return作用:結束函數,并傳回一個值。如果不跟表達式,會傳回一個none。

好,那麼我們深入了解下他們差別,舉個例子,寫個py程式:

#!/usr/bin/env python

def func():

    print "1: hello world!"

    return "2: hello world!"

func()

# python test.py

1: hello world!

明白點了嘛?print是列印對象的值,而return是傳回對象的值。也就是說你return預設是将對象值存儲起來,要想知道裡面的值,可以用print可以列印。

print func()

2: hello world!

為什麼函數裡面不用print就在這裡,往往我們定義一個函數是不需要列印的,而是交給其他代碼去處理這個函數傳回值。當然,print在調試函數代碼時會起到很好的幫助。

<b>5.3 函數參數</b>

   <b>5.3.1 接受參數</b>

&gt;&gt;&gt; def func(a, b):

...   print a + b

&gt;&gt;&gt; func(1, 2)

3

&gt;&gt;&gt; func(1, 2, 3)

traceback (most recent call last):

  file "&lt;stdin&gt;", line 1, in &lt;module&gt;

typeerror: func() takes exactly 2 arguments (3 given)

   a和b可以了解為是個變量,可由裡面代碼塊引用。調用函數時,小括号裡面的表達式數量要對應函數參數數量,并且按傳參按位置賦予函數參數位置。如果數量不對應,會抛出typeerror錯誤。

   當然,函數參數也可以是數組:

&gt;&gt;&gt; def func(a):

...   print a

&gt;&gt;&gt; func([1,2,3])

[1, 2, 3]

&gt;&gt;&gt; func({'a':1,'b':2})

{'a': 1, 'b': 2}

   如果不想一一對應傳參,可以指定參數值:

&gt;&gt;&gt; def func(a,b):

&gt;&gt;&gt; func(b=2,a=1)

   <b>5.3.2 函數參數預設值</b>

   參數預設值是預先定義好,如果調用函數時傳入了這個值,那麼将以傳入的為實際值,否則是預設值。

&gt;&gt;&gt; def func(a, b=2):

&gt;&gt;&gt; func(1)

&gt;&gt;&gt; func(1, 3)

4

   <b>5.3.3 接受任意數量參數</b>

   上面方式固定了參數多個,當不知道多少參數時候可以用以下方式。

   單個星号使用:

&gt;&gt;&gt; def func(*a):     

&gt;&gt;&gt; func(1,2,3)

(1, 2, 3)

   單個星号存儲為一個元組。

   兩個星号使用:

&gt;&gt;&gt; def func(**a):

&gt;&gt;&gt; func(a=1, b=2, c=3)

{'a': 1, 'c': 3, 'b': 2}

   兩個星号存儲為一個字典。可見它們都是以數組的形式傳入。

   你也許在查資料的時候,會看到這樣寫的函數參數(*args, **kwargs),與上面隻是名字不一樣罷了 :

&gt;&gt;&gt; def func(*args, **kwargs):

...   print args

...   print kwargs

&gt;&gt;&gt; func(1,2,3,a=1,b=2,c=3)

   與普通參數一起使用:

&gt;&gt;&gt; def func(a, b, *c):

...   print c

&gt;&gt;&gt; func(1,2,3,5,6)

(3, 5, 6)

&gt;&gt;&gt; def func(a, b, **c):

&gt;&gt;&gt; func(1,2,a=1,b=2,c=3)

typeerror: func() got multiple values for keyword argument 'a'

&gt;&gt;&gt; func(1,2,c=3,d=4,e=5)

{'c': 3, 'e': 5, 'd': 4}

   抛出異常,是因為傳入的第一個參數1,和第三個參數a=1,都認為是傳入函數參數a了。請注意下這點。

<b>5.4 作用域</b>

作用域聽着挺新鮮,其實很簡單,就是限制一個變量或一段代碼可用範圍,不在這個範圍就不可用。提高了程式邏輯的局部性,減少名字沖突。

作用域範圍一般是:全局(global)-&gt;局部(local)-&gt;内置(build-in)

先看看全局和局部變量:

&gt;&gt;&gt; a = 2

...   b = 3

&gt;&gt;&gt; a

2

&gt;&gt;&gt; b

nameerror: name 'b' is not defined

a變量的作用域是整個代碼中有效,稱為全局變量,也就是說一段代碼最開始定義的變量。

b變量的作用域在函數内部,也就是局部變量,在函數外是不可引用的。

這麼一來,全局變量與局部變量即使名字一樣也不沖突。

如果函數内部的變量也能在全局引用,需要使用global聲明:

...   global b

抛出異常,說明一個問題,當函數沒引用使用,裡面的代碼塊是沒有解釋的。

使用global聲明變量後外部是可以調用函數内部的變量的。

<b>5.5 嵌套函數</b>

# 不帶參數

...   x = 2

...   def func2():

...     return x

...   return func2  # 傳回func2函數

&gt;&gt;&gt; func()()

&gt;&gt;&gt; func2()

nameerror: name 'func2' is not defined

&gt;&gt;&gt; def func():   

...   x = 2         

...   global func2

...     return x 

...   return func2

内層函數可以通路外層函數的作用域。内嵌函數隻能被外層函數調用,但也可以使用global聲明全局作用域。

調用内部函數的另一種用法:

# 帶參數

...   def func2(b):

...     return a * b

&gt;&gt;&gt; f = func(2)   # 變量指向函數。是的,變量可以指向函數。

&gt;&gt;&gt; f(5)

10

&gt;&gt;&gt; func(2)(5)

内層函數可以通路外層函數的作用域 。但變量不能重新指派,舉例說明:

...      x = 3

...   func2()

...   return x

...     x += 1

  file "&lt;stdin&gt;", line 5, in func

  file "&lt;stdin&gt;", line 4, in func2

unboundlocalerror: local variable 'x' referenced before assignment

<b>5.6 閉包</b>

“官方”的解釋是:所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。

其實,上面嵌套函數就是閉包一種方式:

func是一個函數,裡面又嵌套了一個函數func2,外部函數傳過來的a參數,這個變量會綁定到函數func2。func函數以内層函數func2作為傳回值,然後把func函數存儲到f變量中。當外層函數調用内層函數時,内層函數才會執行(func()()),就建立了一個閉包。

<b>5.7 高階函數</b>

高階函數是至少滿足這兩個任意中的一個條件:

1) 能接受一個或多個函數作為輸入。

2)輸出一個函數。

abs、map、reduce都是高階函數,後面會講解。

其實,上面所講的嵌套函數也是高階函數。

舉例說明下高階函數:

&gt;&gt;&gt; def f(x):

...   return x * x

&gt;&gt;&gt; def f2(func, y):

...   return func(y)

&gt;&gt;&gt; f2(f, 2)

這裡的f2就是一個高階函數,因為它的第一個參數是一個函數,滿足了第一個條件。

<b>部落格位址:http://lizhenliang.blog.51cto.com and https://yq.aliyun.com/u/lizhenliang</b>

qq群:323779636(shell/python運維開發群)

<b>5.8 函數裝飾器</b>

裝飾器(decorator)本身是一個函數,包裝另一個函數或類,它可以讓其他函數在不需要改動代碼情況下動态增加功能,裝飾器傳回的也是一個函數對象。

先舉一個例子,說明下裝飾器的效果,定義兩個函數,分别傳參計算乘積:

#!/usr/bin/python

# -*- coding: utf-8 -*-

def f1(a, b):

    print "f1 result: " + str(a * b)

def f2(a, b):

    print "f2 result: " + str(a * b)

f1(1, 2)

f2(2, 2)

# python test.py

f1 result: 2

f2 result: 4

跟預期的那樣,列印出了乘積。

如果我想給這兩個函數加一個列印傳入的參數,怎麼辦,應該這樣:

    print "f1 parameter: %d %d" %(a, b)

    print "f2 parameter: %d %d" %(a, b)

f1 parameter: 1 2

f2 parameter: 2 2

按照所想的列印了傳入的參數,有沒有方法能更簡潔點呢,來看看裝飾器後的效果。

def deco(func):

    def f(a, b):

        print "%s parameter: %d %d" %(func.__name__, a, b)

        return func(a, b)

    return f

@deco

可見用裝飾器也實作了上面方法,給要裝飾的函數添加了裝飾器定義的功能,這種方式顯得是不是更簡潔呢!

好,那麼我們繼續深入學習裝飾器用法。

   5.8.1 無參數裝飾器

方式1:函裝飾器函數裝飾函數

    return func

def f1():

    print "hello world!"

myfunc = deco(f1)

myfunc()  

方式2:使用文法糖"@"來裝飾函數

f1()

   方式1是将一個函數作為參數傳給裝飾器函數。

   方式2使用了文法糖,也實作同樣效果。

   其實兩種方式結果一樣,方式1需要每次使用裝飾器時要先變量指派下,而方式2使用裝飾器時直接用文法糖"@"引用,會顯得更友善些,實際代碼中一般也都是用文法糖。

   5.8.2 帶參數裝飾器

        print "function name: %s" % func.__name__   # __name__屬性是擷取函數名,為了說明執行了這個函數

        return func(a, b)   # 用接受過來的func函數來處理傳過來的參數

    print a + b

f1(2, 2)

function name: f1

   3)不固定參數

def log(func):

    def deco(*args, **kwargs):

        print "function name: %s" % func.__name__

        return func(*args, **kwargs)

    return deco

@log

    print "f1() run."

f1(1,2)

f1() run.

   4)裝飾器加參數

# 三層函數,調用log函數傳回deco函數,再調用傳回的函數deco,則傳回值是_deco函數

def log(arg):

    def deco(func):

        def _deco(*args, **kwargs):

            print "%s - function name: %s" % (arg, func.__name__)  

            return func(*args, **kwargs)

        return _deco

@log("info")

info - function name: f1

   再舉一個例子,給函數輸出字元串帶顔色:

def fontcolor(color):

    begin = "\033["

    end = "\033[0m"

    d = {

        'red':'31m',

        'green':'32m',

        'yellow':'33m',

        'blue':'34m'

    }

        print begin + d[color] + func() + end

@fontcolor("red")

def f():

    return "hello world!"

@fontcolor("green")

def f2():

   可以看出裝飾器處理方式滿足了高階函數的條件,是以裝飾器也是一種高階函數。

   裝飾器優點:靈活給裝飾器增加功能,而不修改函數,提高代碼可重複利用性,增加可讀性。

<b>5.9 匿名函數</b>

匿名函數:定義函數的一種形式,無需定義函數名和語句塊,是以代碼邏輯會受到局限,同時也減少代碼量,增加可讀性。

在python中匿名函數是lambda。

舉例子說明def關鍵字與lambda函數定義函數差別:

# 普通函數

...   return "hello world!"

...   return a * b

&gt;&gt;&gt; func(2, 2)

# 匿名函數

&gt;&gt;&gt; f = lambda:"hello world!"

&gt;&gt;&gt; f()

&gt;&gt;&gt; f = lambda a, b: a * b   # 冒号左邊是函數參數,右邊是傳回值

&gt;&gt;&gt; f(2, 2)

lambda函數一行就寫成一個函數功能,省去定義函數過程,讓代碼更加精簡。

<b>5.10 内置高階函數</b>

  <b> 5.10.1 map()</b>

   文法:map(function, sequence[, sequence, ...]) -&gt; list

   将序列中的元素通過函數處理傳回一個新清單。

   例如:

&gt;&gt;&gt; lst = [1,2,3,4,5]

&gt;&gt;&gt; map(lambda x:str(x)+".txt", lst)

['1.txt', '2.txt', '3.txt', '4.txt', '5.txt']

<b>   5.10.2 filter()</b>

   文法:filter(function or none, sequence) -&gt; list, tuple, or string

   将序列中的元素通過函數處理傳回一個新清單、元組或字元串。

   例如:過濾清單中的奇數

&gt;&gt;&gt; filter(lambda x:x%2==0, lst)

[2, 4]

  <b> 5.10.3 reduce()</b>

   文法:reduce(function, sequence[, initial]) -&gt; value

   reduce()是一個二進制運算函數,是以隻接受二進制操作函數。

   例如:計算清單總和

&gt;&gt;&gt; reduce(lambda x,y:x+y, lst)

15

   先将前兩個元素相加等于3,再把結果與第三個元素相加等于6,以此類推。這就是reduce()函數功能。