Python的基本用法
---恢復內容開始---
一、函式
1.1 預設引數
想要計算一個數x的n次方,可以定義如下的函式。但是有時候我們僅僅只需要計算x^2,所以只想使用一個引數即power(x),這時如果仍用如下程式碼會報錯
def power(x, n): s = 1 while n > 0: n = n - 1 s = s * x return s
def power(x,n=2)
可以改成上面的形式,不傳入引數,即預設n=2。同時,當有多個預設引數時,我們可以指定為哪個預設引數賦值如下
def fun(a,b=2,c=3):pass fun(4,c=6)
有一點需要注意:預設引數必須是不變物件,不能是list這樣的可變物件
def add_end(L=[]): L.append('END') return L >>> add_end() ['END'] >>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END']
函式定義後,預設引數的值就被計算出來了。如果預設引數是可變的,那麼每次呼叫函式後,預設引數的值就會發生改變。
1.2 可變引數
如果引數的個數是不確定的,可以使用list或者tuple進行封裝,也可以使用可變引數,方法如下:
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum
""" 使用list作為可變引數時,可以使用下面的簡單方法
>>> nums = [1, 2, 3]
>>> calc(*nums) 14
1.3 關鍵字引數
可變引數允許你傳入0個或任意個引數,這些可變引數在函式呼叫時自動組裝為一個tuple。而關鍵字引數允許你傳入0個或任意個含引數名的引數,這些關鍵字引數在函式內部自動組裝為一個dict。
defperson(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) """ 函式除了接收兩個必填引數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'}
1.4 命名關鍵字引數
如果要限制關鍵字引數的名字,就可以用命名關鍵字引數,例如,只接收city
和job
作為關鍵字引數。這種方式定義的函式如下:
def person(name, age, *, city, job): print(name, age, city, job)
如果函式定義中已經有了一個可變引數,後面跟著的命名關鍵字引數就不再需要一個特殊分隔符*
了:
def person(name, age, *args, city, job): print(name, age, args, city, job)
二 List和Tuple
2.1 List和Tuple相當於一個數組,但是數組裡可以存放的內容是不受限制的,也不需要全部相同,同時一個List裡可以有一個List。List是可變的物件,而Tuple是不可變的物件
對於List和Tuple的訪問,可以使用切片技術:
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] >>> L[1:3] ['Sarah', 'Tracy'] """ L[i:j]表示從索引i開始,取到索引j-1的內容
2.2 遍歷元素時,C或其他語言是按照下標進行的,而Python的遍歷可以不需要下邊,只要變數是可以迭代的即可,如dict就可以迭代
>>> d = {'a': 1, 'b': 2, 'c': 3} >>> for key in d: ... print(key) ... a c b """ 遍歷key和value >>> for k,v in d.items(): ... print(k,v) ... a 1 b 2 c 3
如何判斷一個物件是可迭代物件呢?方法是通過collections模組的Iterable型別判斷:
>>> from collections import Iterable >>> isinstance('abc', Iterable) # str是否可迭代 True >>> isinstance([1,2,3], Iterable) # list是否可迭代 True >>> isinstance(123, Iterable) # 整數是否可迭代 False
最後一個小問題,如果要對list實現類似Java那樣的下標迴圈怎麼辦?Python內建的enumerate
函式可以把一個list變成索引-元素對,這樣就可以在for
迴圈中同時迭代索引和元素本身:
>>> for i, value in enumerate(['A', 'B', 'C']): ... print(i, value) ... 0 A 1 B 2 C
2.3 列表生成式:
如果要建立一個列表,可以使用for迴圈,同時也可以有更簡單的方法進行建立
>>> list(range(1, 11)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
""" for迴圈後可以增加if條件
>>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100]
""" 可以使用兩層迴圈
>>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
2.4 列表生成器
通過列表生成式,我們可以直接建立一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?這樣就不必建立完整的list,從而節省大量的空間。在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator。
要建立一個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>
""" 如果要訪問生成器g的元素,可以使用for迴圈,也可以使用next(g)一個個訪問
>>> g = (x * x for x in range(10))
>>> for n in g: ... print(n) ... 0 1 4 9 16 25 36 49 64 81
生成器類似於函式,儲存的是演算法,所以可以在列印輸出時通過演算法進行計算。
定義一個計算斐波拉契數列的函式
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'
>>> fib(6)
1
1
2 3 5 8 'done'
此時,只要把print(b)寫成yield b就可以把函式fib改成一個generator了
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' >>> f = fib(6) >>> f <generator object fib at 0x104feaaa0>
2.5 迭代器
可用next函式不斷訪問下一個元素的物件是迭代器,如generator就是一個迭代器
凡是可作用於for
迴圈的物件都是Iterable
型別;
凡是可作用於next()
函式的物件都是Iterator
型別,它們表示一個惰性計算的序列;
集合資料型別如list
、dict
、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函式獲得一個Iterator
物件。
三、 函數語言程式設計
3.1 高階函式
map()
函式接收兩個引數,一個是函式,一個是Iterable
,map
將傳入的函式依次作用到序列的每個元素,並把結果作為新的Iterator
返回。
>>> def f(x): ... return x * x ... >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81]
再看reduce
的用法。reduce
把一個函式作用在一個序列[x1, x2, x3, ...]
上,這個函式必須接收兩個引數,reduce
把結果繼續和序列的下一個元素做累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
filter()函式接收一個函式和一個序列,filter()函式將函式作用於序列上,根據作用的結果是True or False來決定保留還是捨棄。
注意到filter()
函式返回的是一個Iterator
,也就是一個惰性序列,所以要強迫filter()
完成計算結果,需要用list()
函式獲得所有結果並返回list。
Python實現素數的篩選:
"""定義一個生成器,生成一個3開始的無限奇數序列
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
定義一個篩選函式
def _not_divisible(n):
return lambda x: x % n > 0
定義一個生成器,不斷返回下一個元素
def primes(): yield 2 it = _odd_iter() # 初始序列 while True: n = next(it) # 返回序列的第一個數 yield n it = filter(_not_divisible(n), it) # 構造新序列
列印輸出前1000的素數
for n in primes():
if n < 1000:
print(n)
else: break
排序函式可以使用sorted()這個函式
>>> sorted([36, 5, -12, 9, -21]) [-21, -12, 5, 9, 36] 可以自定義一個函式進行排序,傳入函式到key >>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36] 對字串進行排序,預設是按照ASCII進行排序 >>> sorted(['bob', 'about', 'Zoo', 'Credit']) ['Credit', 'Zoo', 'about', 'bob'] 忽略大小寫: >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) ['about', 'bob', 'Credit', 'Zoo'] 預設是從小到大,如果是從大到小 >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) ['Zoo', 'Credit', 'bob', 'about']
3.2 函式作為返回值
Python中不僅函式可以作為引數傳入,同時,函式也可以作為結果輸出
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum >>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90> >>> f() 25
注意到返回的函式在其定義內部引用了局部變數args
,所以,當一個函式返回了一個函式後,其內部的區域性變數還被新函式引用,所以,閉包用起來簡單,實現起來可不容易。
另一個需要注意的問題是,返回的函式並沒有立刻執行,而是直到呼叫了f()
才執行。我們來看一個例子
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count()
f1(),f2(),f3()的結果全為9,原因是f1(),f2(),f3()是一起執行的。
返回閉包時牢記一點:返回函式不要引用任何迴圈變數,或者後續會發生變化的變數。
匿名函式lambda是對函式的一種簡化
def f(x): return x * x
可以寫成
>>> f = lambda x: x * x
函式模組中的作用域問題:
在一個模組中,我們可能會定義很多函式和變數,但有的函式和變數我們希望給別人使用,有的函式和變數我們希望僅僅在模組內部使用。在Python中,是通過_
字首來實現的。
正常的函式和變數名是公開的(public),可以被直接引用,比如:abc
,x123
,PI
等;
類似__xxx__
這樣的變數是特殊變數,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊變數,hello
模組定義的文件註釋也可以用特殊變數__doc__
訪問,我們自己的變數一般不要用這種變數名;
類似_xxx
和__xxx
這樣的函式或變數就是非公開的(private),不應該被直接引用,比如_abc
,__abc
等;
之所以我們說,private函式和變數“不應該”被直接引用,而不是“不能”被直接引用,是因為Python並沒有一種方法可以完全限制訪問private函式或變數,但是,從程式設計習慣上不應該引用private函式或變數。