Python3的基本使用(2)
1、函數
1.1、定義函數
在Python中,定義一個函數要使用def語句,依次寫出函數名、括號、括號中的參數和冒號:,然后,在縮進塊中編寫函數體,函數的返回值用return語句返回。
def my_abs(x): if x >= 0: return x else: return -x
在某個文件中定義了函數后,可以在另一個文件中用 " from 文件名 import 方法名 " 的方式來導入某個方法。注意,導入時文件名不含文件后綴 .py。
#在FuncTest.py文件中定義方法: def my_abs(x): if x >= 0: return x else: return -x #在test.py文件中引用該方法: from FuncTest import my_abs print(my_abs(100))
調用函數時,如果參數個數不對,Python解釋器會自動檢查出來,并拋出TypeError。但是如果參數類型不對,Python解釋器就無法幫我們檢查。
在Python中,函數可以定義返回多個值,并且最終結果也會返回多個值,但實際上只是返回了一個 tuple。
但是,在語法上,返回一個tuple可以省略括號,而多個變量可以同時接收一個tuple,并且會按位置賦給對應的值,所以,也可以視為返回了多個值。
#導入math包 import math def myMove(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny #可以看做是返回了多個值 x, y = myMove(100, 100, 60, math.pi / 6) print(x, y) #151.96152422706632 70.0 #但實際上是返回了一個 tuple r = move(100, 100, 60, math.pi / 6) print(r) #(151.96152422706632, 70.0)
1.1.1、參數傳遞
在 python 中,字符串strings、元組 tuples 和 數字類型 numbers 是不可更改的對象,而 列表list、字典dict 等則是可以修改的對象。
-
不可變類型:變量賦值 a=5 后再賦值 a=10,這里實際是新生成一個 int 值對象 10,再讓 a 指向它,而 5 被丟棄,不是改變 a 的值,相當于新生成了 a。
-
可變類型:變量賦值 la=[1,2,3,4] 后再賦值 la[2]=5 則是將 list la 的第三個元素值更改,本身la沒有動,只是其內部的一部分值被修改了。
python 函數的參數傳遞:不可更改對象作為參數是值傳遞(整數、字符串、元組)。可更改對象作為參數是引用傳遞(列表,字典)。
如 a=10,la=[1,2,3],fun(a),傳遞的只是 a 的值,不會影響外部的a的值,如果在 fun(a))內部修改 a 的值,則是新生成來一個 a。而 fun(la),則是將 la 真正的傳過去,修改后 fun 外部的 la 也會受影響。
1.2、空函數
如果想定義一個什么事也不做的空函數,可以用pass語句:
def nop(): pass
pass語句什么都不做,那有什么用?實際上pass可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以先放一個pass,讓代碼能運行起來。
pass還可以用在其他語句里,比如下面,缺少了pass,代碼運行就會有語法錯誤。
if age >= 18: pass
1.3、函數的默認參數
我們可以給函數定義默認參數,這樣使用者可以不傳那個默認參數。注意,必須得保證必選參數在前,默認參數在后,否則Python的解釋器會報錯。
def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s
可以定義多個默認參數。調用有多個默認參數的函數時,可以按順序提供默認參數,比如調用enroll('Bob', 'M', 7)。也可以不按順序提供部分默認參數,此時需要把參數名寫上。比如調用enroll('Adam', 'M', city='Tianjin'),
def enroll(name, gender, age=6, city='Beijing'): print('name:', name) print('gender:', gender) print('age:', age) print('city:', city)
定義默認參數要牢記一點:默認參數必須指向不變對象!
否則將可能會出現類似以下默認參數隨著使用過程當中會發生變化的問題:
#先定義一個函數,傳入一個list,添加一個END再返回: def add_end(L=[]): L.append('END') return L #當你正常調用時,結果沒問題: add_end([1, 2, 3]) #[1, 2, 3, 'END'] add_end(['x', 'y', 'z']) #['x', 'y', 'z', 'END'] #當你使用默認參數調用時,一開始結果也是對的: >>> add_end() ['END'] #但是,再次調用add_end()時,結果就不對了: >>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END']
因為 Python 函數在定義的時候,默認參數L的值就被計算出來了,即[],因為默認參數L也是一個變量,它指向對象[],每次調用該函數,如果改變了L的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]了。
要避免這個問題,我們可以用None這個不變對象來實現:
#這樣定義,無論怎么調用、調用多少次,都不會有問題 def add_end(L=None): if L is None: L = [] L.append('END') return L
1.4、不定長參數(*參數名)
不定長參數的形式是*args,名稱前面有一個星號*,用以接收不確定數量的參數。我們常用的內置函數print就是一個可變參數函數。調用該函數時,可以傳入任意個參數,包括0個參數:
def foo(*args): print(type(args)) for item in args: print(item)
在函數內部,參數 args 接收到的實際上就是一個元組 tuple。
foo('a', 'b', 'c') #將輸出 <class 'tuple'> a b c
如果需要將 list 或者 tuple 作為參數去調用具有可變參數的函數時,我們可以直接將其作為參數進行傳遞。Python同時允許你在 list 或 tuple 前面加一個*號,把 list 或 tuple 里面的元素變成一個個參數傳進去:
nums = [1, 2, 3] foo(nums) #將輸出 <class 'tuple'> 1 2 3
1.5、關鍵字參數(name=val、**參數名)
調用函數時,我們可以使用“關鍵字參數”,它的形式是:kwarg=value。代碼示例:
def say_hi(name, greeting='Hi', more=''): print(greeting, name, more) say_hi(name='Tom') #Hi Tom say_hi(name='Tom', greeting='Hello') #Hello Tom say_hi(name='Tom', more='how are you') #Hi Tom how are you say_hi(more='good day', name='Tom', greeting='Hi') #Hi Tom good day
關鍵字參數是通過關鍵字來確認參數的,所以可以不用按照函數定義時的順序傳遞參數。但是關鍵字參數后面必須都是關鍵字參數,否則將會報錯。比如下面的調用都是無效的:
def say_hi(name, greeting='Hi', more=''): print(greeting, name, more) say_hi() # 缺少必須的參數name say_hi(name='Tom', 'Hello') # 關鍵字參數后面出現了非關鍵字參數 say_hi('Tom', name='Tim') # 同樣的參數傳了兩個值 say_hi(age=10) # 函數定義中不存在的關鍵字參數
如果函數定義的最后一個參數是兩個星號加名稱,比如**name,那么往這個函數傳入的所有關鍵字參數將會在函數內部自動組裝成一個字典dictionary,并且該字典指向為參數名 name。這個字典不包括name前面聲明的普通參數。
如下,定義了關鍵字參數 kw:
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)
函數person除了必選參數name和age外,還接受關鍵字參數kw。在調用該函數時,可以只傳入必選參數,也可以傳入任意個數的關鍵字參數:
#不傳關鍵字參數 person('Michael', 30) #name: Michael age: 30 other: {} #可以傳入任意個數的關鍵字參數: person('Bob', 35, city='Beijing') #name: Bob age: 35 other: {'city': 'Beijing'} person('Adam', 45, gender='M', job='Engineer') #name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
把 dict 作為參數傳遞:
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra) #name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra表示把extra這個dict的所有key-value用關鍵字參數傳入到函數的**kw參數,kw將獲得一個dict。注意kw獲得的dict是extra的一份深拷貝,對kw的改動不會影響到函數外的extra。
1.6、命名關鍵字參數(*, 參數名1, 參數名2)
如果要限制關鍵字參數的名字,我們就可以用命名關鍵字參數。和關鍵字參數比如:**kw不同,命名關鍵字參數需要一個特殊分隔符*,*后面的參數被視為命名關鍵字參數。
例如,只接收city和job作為關鍵字參數的函數定義如下:
def person(name, age, *, city, job): print(name, age, city, job) person('wen', 11, city='aa', job='bb') #輸出 wen 11 aa bb
在調用定義了命名關鍵字參數的函數時,如果要使用命名關鍵字參數則必須傳入參數名,如果沒有傳入參數名,調用將可能報錯。因為 Python 會將其認為是普通的參數,而這些普通的參數中的數量可能跟你傳遞的不一致就會導致報錯。
>>> person('Jack', 24, 'Beijing', 'Engineer') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: person() takes 2 positional arguments but 4 were given
如果在函數定義的參數中已經有了一個可變參數,后面跟著的命名關鍵字參數就不再需要一個特殊分隔符*了。但如果沒有可變參數,就必須加一個*作為特殊分隔符。如果缺少*,Python解釋器將無法識別位置參數和命名關鍵字參數。
def person(name, age, *args, city, job): print(name, age, args, city, job)
命名關鍵字參數可以有缺省值,從而簡化調用:
def person(name, age, *, city='Beijing', job): print(name, age, city, job) #由于命名關鍵字參數city具有默認值,調用時,可不傳入city參數: >>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer
1.7、Python中函數的各種參數的順序
在Python中定義函數,可以用必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。
比如定義一個函數,包含上述若干種參數:
def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函數調用的時候,Python解釋器自動按照參數位置和參數名把對應的參數傳進去。
>>> f1(1, 2) a = 1 b = 2 c = 0 args = () kw = {} >>> f1(1, 2, c=3) a = 1 b = 2 c = 3 args = () kw = {} >>> f1(1, 2, 3, 'a', 'b') a = 1 b = 2 c = 3 args = ('a', 'b') kw = {} >>> f1(1, 2, 3, 'a', 'b', x=99) a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99} >>> f2(1, 2, d=99, ext=None) a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
通過一個tuple和dict,你也可以調用上述函數:
>>> args = (1, 2, 3, 4) >>> kw = {'d': 99, 'x': '#'} >>> f1(*args, **kw) a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'} >>> args = (1, 2, 3) >>> kw = {'d': 88, 'x': '#'} >>> f2(*args, **kw) a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
對于任意函數,都可以通過類似func(*args, **kw)的形式調用它,無論它的參數是如何定義的。
2、迭代器(Iterator)
迭代是Python最強大的功能之一,是訪問集合元素的一種方式。
迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會后退。
迭代器有兩個基本的方法:iter() 和 next()。字符串,列表或元組對象都可用于創建迭代器:
list=[1,2,3,4] it = iter(list) # 創建迭代器對象 print (next(it)) # 輸出迭代器的下一個元素 1 print (next(it)) #2
迭代器對象可以使用常規for語句進行遍歷:
#!/usr/bin/python3 list=[1,2,3,4] it = iter(list) # 創建迭代器對象 for x in it: print (x, end=" ") #將輸出 1 2 3 4
也可以使用 next() 函數進行遍歷:
#!/usr/bin/python3 import sys # 引入 sys 模塊 list=[1,2,3,4] it = iter(list) # 創建迭代器對象 while True: try: print (next(it)) #將輸出 1 2 3 4 except StopIteration: sys.exit()
可以直接作用于for循環的數據類型有以下幾種:一類是集合數據類型,如list、tuple、dict、set、str等;一類是generator,包括生成器和帶yield的generator function。
這些可以直接作用于for循環的對象統稱為可迭代對象:Iterable。可以使用isinstance()判斷一個對象是否是Iterable對象:
from collections.abc import Iterable print(isinstance([], Iterable)) #true print(isinstance({}, Iterable)) #true print(isinstance('abc', Iterable)) #true print(isinstance((x for x in range(10)), Iterable)) #true print(isinstance(100, Iterable)) #false
而生成器不但可以作用于for循環,還可以被next()函數不斷調用并返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。
可以被next()函數調用并不斷返回下一個值的對象稱為迭代器:Iterator。可以使用isinstance()判斷一個對象是否是Iterator對象:
from collections.abc import Iterator isinstance((x for x in range(10)), Iterator) #True isinstance([], Iterator) #False isinstance({}, Iterator) #False isinstance('abc', Iterator) #False
生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。把list、dict、str等Iterable變成Iterator可以使用iter()函數:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True
Python的for循環實際上就是通過不斷調用next()函數實現的,它會先將循環的對象轉換為Iterator對象,例如:
for x in [1, 2, 3, 4, 5]: pass #實際上完全等價于以下: # 首先獲得Iterator對象: it = iter([1, 2, 3, 4, 5]) # 循環: while True: try: # 獲得下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出循環 break
3、生成器(generator)
在 Python 中,使用了 yield 的函數被稱為生成器(generator)。
跟普通函數不同的是,生成器是一個返回迭代器的函數,只能用于迭代操作,更簡單點理解生成器就是一個迭代器。在調用生成器運行的過程中,每次遇到 yield 時函數會暫停并保存當前所有的運行信息,返回 yield 的值, 并在下一次執行 next() 方法時從當前位置繼續運行。
調用一個生成器函數,返回的是一個迭代器對象。
以下實例使用 yield 實現斐波那契數列:
#!/usr/bin/python3 import sys def fibonacci(n): # 生成器函數 a, b, counter = 0, 1, 0 while True: if (counter > n): return yield a a, b = b, a + b counter += 1
f = fibonacci(10) # f 是一個迭代器,由生成器返回生成 while True: try: print (next(f), end=" ") #這里將依次輸出 0 1 1 2 3 5 8 13 21 34 55 except StopIteration:
sys.exit()
要創建一個generator,可以把一個列表生成式的[]改成(),這樣就創建了一個generator:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
如果要一個一個打印出來,可以通過next()函數獲得generator的下一個返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
但一般會使用for循環,因為generator也是可迭代對象,使用for循環將不會出現StopIteration的錯誤。
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
要把一個函數變成generator,只需要在需要返回的值前面加 yield 符號就可以了:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator:
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。
比如:
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
調用該generator時,首先要生成一個generator對象,然后用next()函數不斷獲得下一個返回值:
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
把函數改成generator后,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來迭代:
>>> for n in fib(6):
... print(n)
...
1
1
2
3
5
8
用for循環調用generator時我們會發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
4、模塊和包
4.1、模塊的基本介紹
在Python中,一個.py文件就稱之為一個模塊(Module)。當一個模塊編寫完畢,就可以被其他地方引用。我們在編寫程序的時候,也經常引用其他模塊,包括Python內置的模塊和來自第三方的模塊。
使用模塊還可以避免函數名和變量名沖突。模塊的變量和函數不會對其他模塊產生沖突,因此,我們自己在編寫模塊時,不必考慮函數名字會與其他模塊沖突。但是注意盡量不要與內置函數名字沖突。
在這里可以查看Python的所有內置函數:鏈接。
4.2、模塊的命名
4.3、包
為了避免模塊名沖突,Python又引入了按目錄來組織模塊的方法,稱為包(Package),一個擁有__init__.py文件的文件夾即可稱之為一個包。
不同包下的模塊可以同名,同一個包下模塊同名才會沖突。每一個包目錄下面都會有一個__init__.py的文件,這個文件是必須存在的,否則,Python就把這個目錄當成普通目錄,而不是一個包。__init__.py可以是空文件,也可以有Python代碼,因為__init__.py本身就是一個模塊,而它的模塊名就是該文件夾名,比如下面的mycompany包:

上面組織目錄當中,abc.py模塊的名字就變成了mycompany.abc,類似的,xyz.py的模塊名變成了mycompany.xyz。

上面目錄當中,文件www.py的模塊名就是mycompany.web.www,兩個文件utils.py的模塊名分別是mycompany.utils和mycompany.web.utils。
4.4、如何引入一個模塊(import)
Python本身就內置了很多非常有用的模塊,只要安裝完畢,這些模塊就可以立刻使用。另外,還可以通過 pip 來安裝第三方模塊。
例如,使用 sys 模塊:
# !/usr/bin/python3 # 假設當前文件名: test01.py import sys print('命令行參數如下:') for i in sys.argv: print(i) print('Python 路徑為:', sys.path)
sys.argv 是一個包含命令行參數的列表,sys.path 包含了一個 Python 解釋器自動查找所需模塊的路徑的列表。上面代碼將依次輸出:
命令行參數如下:
F:/pycharmWorkSpace/pythonTest01/test01.py
Python 路徑為: ['F:\\pycharmWorkSpace\\pythonTest01', 'F:\\pycharmWorkSpace\\pythonTest01', 'F:\\pycharmWorkSpace\\pythonTest01\\venv\\Scripts\\python37.zip', 'E:\\develop\\Python37\\DLLs', 'E:\\develop\\Python37\\lib', 'E:\\develop\\Python37', 'F:\\pycharmWorkSpace\\pythonTest01\\venv', 'F:\\pycharmWorkSpace\\pythonTest01\\venv\\lib\\site-packages']
4.4.1、python中對模塊的搜索路徑
當我們使用import語句的時候,Python解釋器是根據Python的搜索路徑來依次搜索模塊的。搜索路徑是由一系列目錄名組成的,Python解釋器就依次從這些目錄中去尋找所引入的模塊。
搜索路徑就被存儲在sys模塊中的path變量:
import sys print('python的搜索路徑為:', sys.path) #上面將輸出 Python 的搜索路徑為: ['F:\\pycharmWorkSpace\\pythonTest01', 'F:\\pycharmWorkSpace\\pythonTest01', 'F:\\pycharmWorkSpace\\pythonTest01\\venv\\Scripts\\python37.zip', 'E:\\develop\\Python37\\DLLs', 'E:\\develop\\Python37\\lib', 'E:\\develop\\Python37', 'F:\\pycharmWorkSpace\\pythonTest01\\venv', 'F:\\pycharmWorkSpace\\pythonTest01\\venv\\lib\\site-packages']
其中第一項為當前運行腳本的路徑。
4.5、import 和 from...import... 語法
在 python 用 import 或者 from...import 來導入相應的模塊。對于同一個模塊,不管你執行了多少次import,一個模塊只會被導入一次,這樣可以防止導入模塊被一遍又一遍地執行。模塊中的代碼在被導入時會執行,并且只有在第一次被導入時才會被執行。
導入模塊的語法:
將整個模塊(somemodule)導入,格式為: import somemodule 此時我們要想使用該模塊下的變量,應該這樣使用: somemodule.name
從某個模塊中導入某個函數,格式為: from somemodule import somefunction
從某個模塊中導入多個函數,格式為: from somemodule import firstfunc, secondfunc, thirdfunc
將某個模塊中的全部函數導入,格式為: from somemodule import *
# 導入 sys 模塊 import sys for i in sys.argv: print (i) print ('\n python 路徑為',sys.path)
# 導入 sys 模塊的 argv,path 成員 from sys import argv,path # 導入特定的成員print('path:',path) # 因為已經導入path成員,所以此處引用時不需要加sys.path
導入指定包下的模塊:
導入某個包下的某個模塊:import somepackage.somemodule 此時我們要想使用該模塊下的變量,應該這樣使用: somepackage.somemodule.name (這種形式的語法只允許導入包或者模塊,不允許直接導入包的變量或者函數等)
導入某個包下的某個模塊的另一種寫法:比如導入包 a 下的包 b 下的模塊 c : from a.b import c 此時我們要想使用 c 模塊下的變量,可以直接寫 c 模塊,而不需要帶上包名: c.name
導入某個包下的某個模塊的某個函數:比如導入包 a 下的模塊 b 下的 show 函數: from a.b import show 此時可以直接使用函數名 show()
導入包時用 * 語法:
比如導入 a 包:from a import * 。此時 * 所指的模塊是由包的 __init__.py 文件中的 __all__ 變量定義的。__init__.py 文件的另外一個作用就是定義 包 中的__all__,用來模糊導入,如__init__.py:
__all__ = ["bmodule","cmodule"]
在包外面導入:
from a import * show = bmodule.show show()
如果 __all__ 沒有定義,那么使用from a import *這種語法的時候,就不會導入包 a 里的任何子模塊。但此時會運行 __init__.py 文件,并且把該文件中定義的變量和函數給引進來。(如果有定義了__all__,那么此時就只會導入__all__指向的模塊,不會導入該文件中定義的變量)

浙公網安備 33010602011771號