Python基礎入門_4函數

原文連接:mp.weixin.qq.com/s/eszIPhEQD…javascript

Python 基礎入門前三篇:php

第四篇內容,此次介紹下函數的基本用法,包括函數的定義、參數的類型、匿名函數、變量做用域以及從模塊導入函數的方法,目錄以下所示:html


4. 函數

定義:函數是組織好的,可重複使用,用於實現單一或者相關聯功能的代碼段。java

在 Python 中既有內建函數,好比 print()sum() ,也能夠用戶自定義函數。python

4.1 定義函數

自定義一個函數須要遵照一些規則:c++

  • 函數代碼塊必須以 def 關鍵詞開頭,而後是函數標識符名稱(函數名)和圓括號 ()
  • 圓括號內部用於定義參數,而且傳入參數和自變量也是存放在圓括號內;
  • 函數的第一行語句能夠選擇性地使用文檔字符串—用於存放函數說明。
  • 函數內容以冒號起始,而且縮進。
  • return [表達式] 結束函數,選擇性地返回一個值給調用方。不帶表達式的 return 語句至關於返回 None

一個函數的通常格式以下:git

def 函數名(參數列表):
	函數體
複製代碼

默認狀況下,參數值和參數名稱是按照函數聲明中定義的順序匹配的github

簡單的定義和調用函數的例子以下所示:算法

def hello():
    print("Hello, world!")
# 計算面積的函數
def area(width, height):
    return width * height

hello()
width = 2
height = 3
print('width={}, height={}, area={}'.format(width, height, area(width, height)))
複製代碼

輸出結果:express

Hello, world!
width=2, height=3, area=6
複製代碼

上述例子定義了兩個函數,第一個是沒有參數的 hello(), 而第二個函數定義了兩個參數。

4.2 參數傳遞

在 python 中,類型屬於對象,變量是沒有類型的

a = [1, 2, 3]
a = "abc"
複製代碼

上述代碼中,[1,2,3] 是 List 類型,"abc" 是 String 類型,但變量 a 是沒有類型的,它僅僅是一個對象的引用(一個指針),能夠指向 List 類型,也能夠指向 String 類型。

可更改(mutable)與不可更改(immutable)對象

python 中,strings, tuples, numbers 是不可更改對象,而 list, dict 是可修改的對象。

  • 不可變類型:上述例子中 a 先賦值爲 5,而後賦值爲 10,其實是生成一個新對象,賦值爲 10,而後讓 a 指向它,而且拋棄了 5,並不是改變了 a 的數值;
  • 可變類型:對於 list 類型,變量 la=[1,2,3],而後令 la[2]=5 ,此時並無改變變量 la,僅僅改變了其內部的數值。

在以前的第二節介紹變量類型中,介紹瞭如何判斷數據類型是否可變,介紹了兩種方法:

  • id()
  • hash()

這裏用 id() 的方法來作一個簡單的例子,代碼以下:

# 判斷類型是否可變
a = 5
print('a id:{}, val={}'.format(id(a), a))
a = 3
print('a id:{}, val={}'.format(id(a), a))

la = [1, 2, 3]
print('la id:{}, val={}'.format(id(la), la))
la[2] = 5
print('la id:{}, val={}'.format(id(la), la))
複製代碼

輸出結果,能夠發現變量 aid 是發生了變化,而列表變量 laid 沒有變化,這證實了 a 的類型 int 是不可變的,而 list 是可變類型。

a id:1831338608, val=5
a id:1831338544, val=3

la id:1805167229448, val=[1, 2, 3]
la id:1805167229448, val=[1, 2, 5]
複製代碼

而後在 Python 中進行函數參數傳遞的時候,根據傳遞的變量是否可變,也須要分開討論:

  • 不可變類型:相似 c++值傳遞,如 整數、字符串、元組。如 fun(a),傳遞的只是 a 的值,沒有影響 a 對象自己。好比在 fun(a)內部修改 a 的值,只是修改另外一個複製的對象,不會影響 a 自己。
  • 可變類型:相似 c++ 的**引用傳遞,**如 列表,字典。如 fun(la),則是將 la 真正的傳過去,修改後 fun 外部的 la 也會受影響。

固然了,Python 中一切都是對象,這裏應該說是傳遞可變對象和不可變對象,而不是引用傳遞和值傳遞,但必須注意應該慎重選擇傳遞可變對象的參數,下面會分別給出傳遞兩種對象的例子。

首先是傳遞不可變對象的實例:

# 傳遞不可變對象的實例
def change_int(a):
    a = 10

b = 2
print('origin b=', b)
change_int(b)
print('after call function change_int(), b=', b)
複製代碼

輸出結果,傳遞的變量 b 並無發生改變。

origin b= 2
after call function change_int(), b= 2
複製代碼

接着,傳遞可變對象的例子:

# 傳遞可變對象的實例
def chang_list(la):
    """ 修改傳入的列表參數 :param la: :return: """
    la.append([2, 3])
    print('函數內部: ', la)
    return


la = [10, 30]
print('調用函數前, la=', la)
chang_list(la)
print('函數外取值, la=', la)
複製代碼

輸出結果,能夠看到在函數內部修改列表後,也會影響在函數外部的變量的數值。

調用函數前, la= [10, 30]
函數內部:  [10, 30, [2, 3]]
函數外取值, la= [10, 30, [2, 3]]
複製代碼

固然,這裏若是依然但願傳遞列表給函數,但又不但願修改列表原本的數值,能夠採用傳遞列表的副本給函數,這樣函數的修改只會影響副本而不會影響原件,最簡單實現就是切片 [:] ,例子以下:

# 不修改 lb 數值的辦法,傳遞副本
lb = [13, 21]
print('調用函數前, lb=', lb)
chang_list(lb[:])
print('傳遞 la 的副本給函數 change_list, lb=', lb)
複製代碼

輸出結果:

調用函數前, lb= [13, 21]
函數內部:  [13, 21, [2, 3]]
傳遞 lb 的副本給函數 change_list, lb= [13, 21]
複製代碼

4.3 參數

參數的類型主要分爲如下四種類型:

  • 位置參數
  • 默認參數
  • 可變參數
  • 關鍵字參數
  • 命名關鍵字參數
位置參數

**位置參數須以正確的順序傳入函數。調用時的數量必須和聲明時的同樣。**其定義以下,arg 就是位置參數,docstring 是函數的說明,通常說明函數做用,每一個參數的含義和類型,返回類型等;statement 表示函數內容。

def function_name(arg):
	"""docstring"""
	statement
複製代碼

下面是一個例子,包括一個正確調用例子,和兩個錯誤示例

# 位置參數
def print_str(str1, n):
    """ 打印輸入的字符串 n 次 :param str1: 打印的字符串內容 :param n: 打印的次數 :return: """
    for i in range(n):
        print(str1)


strs = 'python '
n = 3
# 正確調用
print_str(strs, n)
# 錯誤例子1
print_str()
# 錯誤例子2
print_str(n, strs)
複製代碼

對於正確例子,輸出:

python python python 
複製代碼

錯誤例子1--print_str(),也就是沒有傳入任何參數,返回錯誤:

TypeError: print_str() missing 2 required positional arguments: 'str1' and 'n'
複製代碼

錯誤例子1--print_str(n, strs),也就是傳遞參數順序錯誤,返回錯誤:

TypeError: 'str' object cannot be interpreted as an integer
複製代碼
默認參數

默認參數定義以下,其中 arg2 就是表示默認參數,它是在定義函數的時候事先賦予一個默認數值,調用函數的時候能夠不須要傳值給默認參數。

def function_name(arg1, arg2=v):
	"""docstring"""
	statement
複製代碼

代碼例子以下:

# 默認參數
def print_info(name, age=18):
    ''' 打印信息 :param name: :param age: :return: '''
    print('name: ', name)
    print('age: ', age)

print_info('jack')
print_info('robin', age=30)
複製代碼

輸出結果:

name:  jack
age:  18
name:  robin
age:  30
複製代碼

注意:默認參數必須放在位置參數的後面,不然程序會報錯。

可變參數

可變參數定義以下,其中 arg3 就是表示可變參數,顧名思義就是輸入參數的數量能夠是從 0 到任意多個,它們會自動組裝爲元組

def function_name(arg1, arg2=v, *arg3):
	"""docstring"""
	statement
複製代碼

這裏是一個使用可變參數的實例,代碼以下:

# 可變參數
def print_info2(name, age=18, height=178, *args):
    ''' 打印信息函數2 :param name: :param age: :param args: :return: '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)
    print(args)
    for language in args:
        print('language: ', language)

print_info2('robin', 20, 180, 'c', 'javascript')
languages = ('python', 'java', 'c++', 'go', 'php')
print_info2('jack', 30, 175, *languages)
複製代碼

輸出結果:

name:  robin
age:  20
height:  180
('c', 'javascript')
language:  c
language:  javascript

name:  jack
age:  30
height:  175
('python', 'java', 'c++', 'go', 'php')
language:  python
language:  java
language:  c++
language:  go
language:  php
複製代碼

這裏須要注意幾點:

  1. 首先若是要使用可變參數,那麼傳遞參數的時候,默認參數應該如上述例子傳遞,不能如print_info2('robin', age=20, height=180, 'c', 'javascript'),這種帶有參數名字的傳遞是會出錯的;
  2. 可變參數有兩種形式傳遞:
  • 直接傳入函數,如上述例子第一種形式,即 print_info2('robin', 20, 180, 'c', 'javascript');
  • 先組裝爲列表或者元組,再傳入,而且必須帶有 * ,即相似 func(*[1, 2,3]) 或者 func(*(1,2,3)),之因此必須帶 * ,是由於若是沒有帶這個,傳入的可變參數會多嵌套一層元組,即 (1,2,3) 變爲 ((1,2,3))
關鍵字參數

關鍵字參數定義以下,其中 arg4 就是表示關鍵字參數,關鍵字參數其實和可變參數相似,也是能夠傳入 0 個到任意多個,不一樣的是會自動組裝爲一個字典,而且是參數前 ** 符號。

def function_name(arg1, arg2=v, *arg3, **arg4):
	"""docstring"""
	statement
複製代碼

一個實例以下:

def print_info3(name, age=18, height=178, *args, **kwargs):
    ''' 打印信息函數3,帶有關鍵字參數 :param name: :param age: :param height: :param args: :param kwargs: :return: '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)

    for language in args:
        print('language: ', language)
    print('keyword: ', kwargs)


# 不傳入關鍵字參數的狀況
print_info3('robin', 20, 180, 'c', 'javascript')
複製代碼

輸出結果以下:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {}
複製代碼

傳入任意數量關鍵字參數的狀況:

# 傳入任意關鍵字參數
print_info3('robin', 20, 180, 'c', 'javascript', birth='2000/02/02')
print_info3('robin', 20, 180, 'c', 'javascript', birth='2000/02/02', weight=125)
複製代碼

結果以下:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02'}

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02', 'weight': 125}
複製代碼

第二種傳遞關鍵字參數方法--字典:

# 用字典傳入關鍵字參數
keys = {'birth': '2000/02/02', 'weight': 125, 'province': 'Beijing'}
print_info3('robin', 20, 180, 'c', 'javascript', **keys)
複製代碼

輸出結果:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02', 'province': 'Beijing', 'weight': 125}
複製代碼

因此,一樣和可變參數類似,也是兩種傳遞方式:

  • 直接傳入,例如 func(birth='2012')
  • 先將參數組裝爲一個字典,再傳入函數中,如 func(**{'birth': '2000/02/02', 'weight': 125, 'province': 'Beijing'})
命名關鍵字參數

命名關鍵字參數定義以下,其中 *, nkw 表示的就是命名關鍵字參數,它是用戶想要輸入的關鍵字參數名稱,定義方式就是在 nkw 前面添加 *, ,這個參數的做用主要是限制調用者能夠傳遞的參數名

def function_name(arg1, arg2=v, *arg3, *,nkw, **arg4):
	"""docstring"""
	statement
複製代碼

一個實例以下:

# 命名關鍵字參數
def print_info4(name, age=18, height=178, *, weight, **kwargs):
    ''' 打印信息函數4,加入命名關鍵字參數 :param name: :param age: :param height: :param weight: :param kwargs: :return: '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)

    print('keyword: ', kwargs)
    print('weight: ', weight)

print_info4('robin', 20, 180, birth='2000/02/02', weight=125)
複製代碼

輸出結果以下:

name:  robin
age:  20
height:  180
keyword:  {'birth': '2000/02/02'}
weight:  125
複製代碼

這裏須要注意:

  • 加入命名關鍵字參數後,就不能加入可變參數了
  • 對於命名關鍵字參數,傳遞時候必須指明該關鍵字參數名字,不然可能就被當作其餘的參數。
參數組合

經過上述的介紹,Python 的函數參數分爲 5 種,位置參數、默認參數、可變參數、關鍵字參數以及命名關鍵字參數,而介紹命名關鍵字參數的時候,能夠知道它和可變參數是互斥的,是不能同時出現的,所以這些參數能夠支持如下兩種組合及其子集組合:

  • 位置參數、默認參數、可變參數和關鍵字參數
  • 位置參數、默認參數、關鍵字參數以及命名關鍵字參數

通常狀況下,其實只須要位置參數和默認參數便可,一般並不須要過多的組合參數,不然函數會很難懂。

4.4 匿名函數

上述介紹的函數都屬於同一種函數,即用 def 關鍵字開頭的正規函數,Python 還有另外一種類型的函數,用 lambda 關鍵字開頭的匿名函數

它的定義以下,首先是關鍵字 lambda ,接着是函數參數 argument_list,其參數類型和正規函數可用的同樣,位置參數、默認參數、關鍵字參數等,而後是冒號 :,最後是函數表達式 expression ,也就是函數實現的功能部分。

lambda argument_list: expression
複製代碼

一個實例以下:

# 匿名函數
sum = lambda x, y: x + y

print('sum(1,3)=', sum(1, 3))
複製代碼

輸出結果:

sum(1,3)= 4
複製代碼

4.5 變量做用域

Python 中變量是有做用域的,它決定了哪部分程序能夠訪問哪一個特定的變量,做用域也至關因而變量的訪問權限,一共有四種做用域,分別是:

  • L(Local):局部做用域
  • E(Enclosing):閉包函數外的函數中
  • G(Global):全局做用域
  • B(Built-in):內置做用域(內置函數所在模塊的範圍)

尋找的規則是 L->E->G->B ,也就是優先在局部尋找,而後是局部外的局部(好比閉包),接着再去全局,最後纔是內置中尋找。

下面是簡單介紹這幾個做用域的例子,除內置做用域:

g_count = 0  # 全局做用域
def outer():
    o_count = 1  # 閉包函數外的函數中
    # 閉包函數 inner()
    def inner():
        i_count = 2  # 局部做用域
複製代碼

內置做用域是經過一個名爲 builtin 的標準模塊來實現的,但這個變量名自己沒有放入內置做用域,須要導入這個文件纔可使用它,使用代碼以下,能夠查看預約義了哪些變量:

import builtins
print(dir(builtins))
複製代碼

輸出的預約義變量以下:

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
複製代碼

注意:只有模塊(module),類(class)以及函數(def, lambda)纔會引入新的做用域,其餘代碼塊(好比 if/elif/else、try/except、for/while)是不會引入新的做用域,在這些代碼塊內定義的變量,外部也可使用。

下面是兩個例子,一個在函數中新定義變量,另外一個在 if 語句定義的變量,在外部分別調用的結果:

g_count = 0  # 全局做用域
def outer():
    o_count = 1  # 閉包函數外的函數中
    # 閉包函數 inner()
    def inner():
        i_count = 2  # 局部做用域
        
if 1:
    sa = 2
else:
    sa = 3
print('sa=', sa)
print('o_count=', o_count)
複製代碼

輸出結果,對於在 if 語句定義的變量 sa 是能夠正常訪問的,可是函數中定義的變量 o_count 會報命名錯誤 NameError ,提示該變量沒有定義。

sa= 2
NameError: name 'o_count' is not defined
複製代碼
全局變量和局部變量

全局變量和局部變量的區別主要在於定義的位置是在函數內部仍是外部,也就是在函數內部定義的是局部變量,在函數外部定義的是全局變量。

局部變量只能在其被聲明的函數內部訪問,而全局變量能夠在整個程序範圍內訪問。調用函數時,全部在函數內聲明的變量名稱都將被加入到做用域中。以下實例:

# 局部變量和全局變量
total = 3  # 全局變量

def sum_nums(arg1, arg2):
    total = arg1 + arg2  # total在這裏是局部變量.
    print("函數內是局部變量 : ", total)
    return total


# 調用 sum_nums 函數
sum_nums(10, 20)
print("函數外是全局變量 : ", total)
複製代碼

輸出結果:

函數內是局部變量 :  30
函數外是全局變量 :  3
複製代碼
global 和 nonlocal 關鍵字

若是在內部做用域想修改外部做用域的變量,好比函數內部修改一個全局變量,那就須要用到關鍵字 globalnonlocal

這是一個修改全局變量的例子:

# 函數內部修改全局變量
a = 1

def print_a():
    global a
    print('全局變量 a=', a)
    a = 3
    print('修改全局變量 a=', a)


print_a()
print('調用函數 print_a() 後, a=', a)
複製代碼

輸出結果:

全局變量 a= 1
修改全局變量 a= 3
調用函數 print_a() 後, a= 3
複製代碼

而若是須要修改嵌套做用域,也就是閉包做用域,外部並不是全局做用域,則須要用關鍵字 nonlocal ,例子以下:

# 修改閉包做用域中的變量
def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal關鍵字聲明
        num = 100
        print('閉包函數中 num=', num)

    inner()
    print('調用函數 inner() 後, num=',num)


outer()
複製代碼

輸出結果:

閉包函數中 num= 100
調用函數 inner() 後, num= 100
複製代碼

4.6 從模塊中導入函數

通常咱們會須要導入一些標準庫的函數,好比 ossys ,也有時候是本身寫好的一個代碼文件,須要在另外一個代碼文件中導入使用,導入的方式有如下幾種形式:

# 導入整個模塊
import module_name
# 而後調用特定函數
module_name.func1()

# 導入特定函數
from module_name import func1, func2

# 採用 as 給函數或者模塊指定別名
import module_name as mn
from module_name import func1 as f1

# * 表示導入模塊中全部函數
from module_name import *
複製代碼

上述幾種形式都是按照實際需求來使用,但最後一種方式並不推薦,緣由主要是 Python 中可能存在不少相同名稱的變量和函數,這種方式可能會覆蓋相同名稱的變量和函數。最好的導入方式仍是導入特定的函數,或者就是導入整個模塊,而後用句點表示法調用函數,即 module_name.func1()


參考


小結

本文主要是簡單介紹了 Python 函數的基礎內容,正規函數和匿名函數的定義和用法,參數的5種類型和使用,固然函數還有更多內容,好比高階函數 map, filter, reduce,還有本文中間提過的閉包函數等,這部分計劃放到後續進階中介紹。

此外,本文的代碼都上傳到個人 github 上了:

github.com/ccc013/Pyth…

歡迎關注個人微信公衆號--算法猿的成長,或者掃描下方的二維碼,你們一塊兒交流,學習和進步!

往期精彩推薦

機器學習系列
Github項目 & 資源教程推薦
相關文章
相關標籤/搜索