Python基礎注意點
字串是以單引號'或雙引號"括起來的任意文本,比如'abc',"xyz"等等。
如果'本身也是一個字元,那就可以用""括起來,比如"I'm OK"包含的字元是I,',m,空格,O,K這6個字元
如果字串內部既包含'又包含"怎麼辦?可以用逸出字元\來標識,比如:
1
'I\'m \"OK\"!'
表示的字串內容是:
1
I'm "OK"!
空值是Python裡一個特殊的值,用None表示。None不能理解為0,因為0是有意義的,而None是一個特殊的空值
list與tuple
list是一種有序的集合,可以隨時添加和刪除其中的元素,表示如classmates = ['Michael', 'Bob', 'Tracy']
tuple和list非常類似,但是tuple一旦初始化就不能修改,表示如classmates = ('Michael', 'Bob', 'Tracy')
不可變的tuple有什麼意義?因為tuple不可變,所以代碼更安全。如果可能,能用tuple代替list就盡量用tuple。
只有1個元素的tuple定義時必須加一個逗號,,不然定義的不是tuple,是元素本身
Python函數注意點
必選參數在前,預設參數在後,否則Python的解譯器會報錯
可變參數允許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple,方式為*args
而關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個dict,方式為**kw
使用命名關鍵字參數時,要特別注意,如果沒有可變參數,就必須加一個*作為特殊分隔字元。如果缺少*,Python解譯器將無法識別位置參數和命名關鍵字參數
參數定義的順序必須是:必選參數、預設參數、可變參數、命名關鍵字參數和關鍵字參數
對於任意函數,都可以通過類似func(*args, **kw)的形式調用它,無論它的參數是如何定義的
進階特性
產生器
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>
如果一個函數定義中包含yield關鍵字,那麼這個函數就不再是一個普通函數,而是一個generator
generator和函數的執行流程不一樣。函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成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
迭代器
凡是可作用於for迴圈的對象都是Iterable類型;
凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
集合資料類型如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象
高階函數
把函數作為參數傳入,這樣的函數稱為高階函數,函數式編程就是指這種高度抽象的編程範式
def add(x, y, f):
return f(x) + f(y)
>>> add(-5, 6, abs)
11
map/reduce
map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次作用到序列的每個元素,並把結果作為新的Iterator返回
把list所有數字轉為字串:
1
2
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
如把str轉換為int:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579
filter
`filter()`把傳入的函數依次作用於每個元素,然後根據傳回值是`True`還是`False`決定保留還是丟棄該元素
例如,在一個list中,刪掉偶數,只保留奇數,可以這麼寫:
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 結果: [1, 5, 9, 15]
注意到filter()函數返回的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要用list()函數獲得所有結果並返回list
sorted
按絕對值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
返回函數
一個函數可以返回一個計算結果,也可以返回一個函數
返回一個函數時,牢記該函數並未執行,返回函數中不要引用任何可能會變化的變數。
閉包:
def foo():
m=3
n=5
def bar():
a=4
return m+n+a
return bar
>>>bar = foo()
>>>bar()
12
bar在foo函數的代碼塊中定義。我們稱bar是foo的內建函式。
在bar的局部範圍中可以直接存取foo局部範圍中定義的m、n變數。
簡單的說,這種內建函式可以使用外部函數變數的行為,就叫閉包
偏函數
當函數的參數個數太多,需要簡化時,使用`functools.partial`可以建立一個新的函數,這個新函數可以固定住原函數的部分參數,從而在調用時更簡單
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
模組
在Python中,一個.py檔案就稱之為一個模組(Module)
每一個包目錄下面都會有一個__init__.py的檔案,__init__.py本身就是一個模組,模組名為包名
類似_xxx和__xxx這樣的函數或變數就是非公開的(private),不應該被直接引用,比如_abc,__abc等
之所以我們說,private函數和變數“不應該”被直接引用,而不是“不能”被直接引用,是因為Python並沒有一種方法可以完全限制訪問private函數或變數,但是,從編程習慣上不應該引用private函數或變數
物件導向
在Python中,執行個體的變數名如果以__開頭,就變成了一個私人變數(private),只有內部可以訪問,外部不能訪問
限制執行個體的屬性:定義一個特殊的__slots__變數,來限制該class執行個體能添加的屬性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
然後,我們試試:
>>> s = Student() # 建立新的執行個體
>>> s.name = 'Michael' # 綁定屬性'name'
>>> s.age = 25 # 綁定屬性'age'
>>> s.score = 99 # 綁定屬性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
把一個getter方法變成屬性,可以加上@property,把一個setter方法變成屬性賦值可以加上@score.setter
如:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
外部調用
>>> s = Student()
>>> s.score = 60 # OK,實際轉化為s.set_score(60)
>>> s.score # OK,實際轉化為s.get_score()
Python允許使用多重繼承,這種設計通常稱之為MixIn
動態建立類type()
>>> def fn(self, name='world'): # 先定義函數
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
要建立一個class對象,type()函數依次傳入3個參數:
class的名稱;
繼承的父類集合,注意Python支援多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
class的方法名稱與函數綁定,這裡我們把函數fn綁定到方法名hello上。
IO編程
檔案讀寫
在Python中,檔案讀寫是通過open()函數開啟的檔案對象完成的。在Python中這種對象統稱為file-like Object。除了file外,還可以是記憶體的位元組流,網路流,自訂流等等
使用with語句操作檔案IO是個好習慣,它自動幫我們調用close()方法,如:
with open('/path/to/file', 'r') as f:
print(f.read())
多進程
Unix/Linux作業系統提供了一個fork()系統調用,它非常特殊。普通的函數調用,調用一次,返回一次,但是fork()調用一次,返回兩次,因為作業系統自動把當前進程(稱為父進程)複製了一份(稱為子進程),然後,分別在父進程和子進程內返回。
子進程永遠返回0,而父進程返回子進程的ID。這樣做的理由是,一個父進程可以fork出很多子進程,所以,父進程要記下每個子進程的ID,而子進程只需要調用getppid()就可以拿到父進程的ID。