Python常用基礎(chǔ)語法知識點大全

記得我是數(shù)學(xué)系的,大二時候因為參加數(shù)學(xué)建模,學(xué)習(xí)Python爬蟲,去圖書館借了一本Python基礎(chǔ)書,不厚,因為有matlabC語言基礎(chǔ),這本書一個星期看完了,學(xué)完后感覺Python入門很快,然后要開始學(xué)爬蟲和矩陣計算,學(xué)習(xí)一下對應(yīng)的包就行了,感覺很方便,愛上了這門語言,雖然畢業(yè)后做了Java,但是平時工作中也會用一些Python。

不過初學(xué)者有很多基礎(chǔ)知識點記不住,因為用得少,這里總結(jié)記錄一下。更多Python知識,可以前往我的個人博客網(wǎng)站 等待下一個秋-Python

python-basic.png

介紹

Python 是一門獨特的語言,快速瀏覽一下他的要點:

  • 面向?qū)ο螅好恳粋€變量都是一個類,有其自己的屬性(attribute)與方法(method)。
  • 語法塊:用縮進(四個空格)而不是分號、花括號等符號來標(biāo)記。因此,行首的空格不能隨意書寫。
  • 注釋:行內(nèi)用“#”號,行間注釋寫在兩組連續(xù)三單引號之間:’’’
  • 續(xù)行:行尾輸入一個反斜杠加一個空格(’\ ‘),再換行。如果行尾語法明顯未完成(比如以逗號結(jié)尾),可以直接續(xù)行。
  • 打印與輸入: 函數(shù) print() 與 input(),注意 print() 的 sep 與 end 參數(shù)。
  • 變量:無需指定變量類型,也不需要提前聲明變量。
    • 刪除變量:del()
    • 復(fù)制變量:直接將變量a賦值給b,有時僅僅復(fù)制了一個“引用”。此后 b 與 a 的改動仍會互相影響。必要時使用 a is b 來判斷是否同址。
  • 模塊:通過 import pandas 的方式加載模塊(或者 import pandas as pd),并用形如 pandas.DataFrame(或 pd.DataFrame)的方式調(diào)用模塊內(nèi)的方法。也可以使用 from pandas import DataFrame 的方式,這樣在下文可以直接使用 DataFrame 作為調(diào)用名。
  • 幫助:配合使用 dir() 與 help() 命令;其中前者是輸出變量所有的成員。以及查閱 官網(wǎng)頁面。

變量復(fù)制的一個例子。

a = [1, 2]
b = a
print(id(a) - id(b))  # 地址差為 0,表示實質(zhì)是同址的
0
b.append(3)
print(a)  # 只改動了 b,但 a 也跟著變動了
[1, 2, 3]
a is b
True

使用切片來重新分配空間:

a is a[:]
False

數(shù)據(jù)結(jié)構(gòu)

Python 原生的數(shù)據(jù)結(jié)構(gòu)包括:

數(shù)字(num)

細分為整數(shù)(int)與浮點數(shù)(float)兩種。

  • 四則運算:+, -, *, / ,乘方: **
  • 整除: 5 // 2 = 2,取余:5 % 2 = 1
  • 自運算: a += 1 (四則與乘方均可類似自運算)
    以及一些細節(jié):
  • 運算兩數(shù)中只要有一個浮點數(shù),結(jié)果就是浮點數(shù);
  • 整數(shù)相除,即使能除盡,結(jié)果也是浮點數(shù);
  • Python 內(nèi)部的機制解決了整數(shù)溢出的問題,不用擔(dān)心。

布爾(bool)與邏輯

首字母大寫 True / False.

  • 邏輯運算符:與 A and B,或 A or B,非 not A
  • 邏輯關(guān)系符:等于 ==, 不等于 !=. 其他不贅述。
  • 幾種邏輯判斷例子:
變量 x x = [] x = 0 x = 2
bool(x) False False True
if x: … False False True
if x is None: … False False False

序列(sequence)

序列主要包括字符串(str)、列表(list)與元祖(tuple)三類。

  • 序列索引規(guī)則:
    • 索引從0開始,到 N-1 結(jié)束。
    • 切片:切片的索引是左閉右開的。
      • seq[0:2](從 0 到 1)
      • seq[2:](從 2 到尾)
      • seq[:3] (從頭到 2)
      • seq[:](全部)
      • seq[:10:2](從頭到9,每兩個取一個)
      • seq[::2](全部,每兩個取一個)
    • 索引允許負數(shù):seq(-1) 與 seq(N - 1) 等同,seq(-3:-1)與 seq(N-3:N-1) 等同。
  • 序列通用函數(shù):
    • len():返回序列長度。
    • +/* :加號用于連接兩個序列,乘號重復(fù)排列若干次再連接。
    • seq1 in seq2:如果 seq1 這個片段可以在 seq2 中被找到,返回 True.
    • index:在 seq1 in seq2 為 True 時使用,seq2.index(seq1) 表示 seq1 首次出現(xiàn)于 seq2 中的位置。
    • max()/min():返回序列中的最值。如果不是數(shù)字,則按 ASCII 碼順序返回。
    • cmp(seq1, seq2):比較大小。結(jié)果為負,則表示 seq1 較小。

字符串(str)

寫于一對雙引號或單引號內(nèi)。用 str() 可以強制轉(zhuǎn)換為字符串。

  • 轉(zhuǎn)義:反斜杠。如果強制不解釋字符串,在左引號前加字母 r 即可: r"c:\new".
  • 分割與連接:**.split()**.join().
s = " I love Python"  # 首位是空格
lst = s.split(' ')
lst1 = '-'.join(lst)

print(lst, '\n', lst1)
['', 'I', 'love', 'Python'] 
 -I-love-Python
  • 緊切:strip() 去掉字符串首尾兩端的空格。方法 lstrip()/rstrip() 則只切除首端/尾端的空格。
s.strip()
'I love Python'
  • 大小寫轉(zhuǎn)換:如下幾個方法:
    • 首字母大寫:s.title()
    • 全大寫:s.upper()
    • 全小寫:s.lower()
    • 句首大寫:s.capitalize()
  • 格式化:字符串格式化是一種實用功能。通過 .format() 成員函數(shù)完成。
'I like {} and {}'.format('Python', 'you')
'I like Python and you'
'{0} + {2} = {1}'.format (10, 20, 'Python ')  # 按順序引用
'10 + Python  = 20'
'{0} * {1} = {0}'.format (10, 'Python ')  # 編號反復(fù)引用
'10 * Python  = 10'

格式化控制碼:

控制碼 含義 控制碼 含義
:s 字符串 :c 單個字符
:b/o/x/d 二、八、十六、十進制數(shù) :e/f 科學(xué)計數(shù)法/浮點數(shù)

一些復(fù)雜控制的例子:

例子 含義 例子 含義
:.2f/:+.2f 兩位小數(shù)/帶符號兩位小數(shù) : .2f 正數(shù)前補空格的兩位小數(shù)
:, 逗號分隔符 :.2% 百分比兩位小數(shù)
:.2e 科學(xué)計數(shù)法兩位小數(shù) :^4d 總寬四位居中對齊
:>4d/<4d 總寬四位左/右對齊 :0>4d 總寬四位左側(cè)補零

舉例:

"{:0>7.2f} is an odd number".format(123.4)  # 總寬 7 位小數(shù)點后 2 位,左側(cè)補零
'0123.40 is an odd number'

其他實用的字符串函數(shù):

  • str.replace(old, new[, times]):將字符串中前 times 個 old 子串替換為 new。Times 不指定時默認替換全部。
  • str.isdigit():判斷字符串是否每一位都是數(shù)字,返回 True 或者 False。
    字符串中正則表達式的內(nèi)容參見本文附錄。

列表(list)

中括號式的結(jié)構(gòu)。list() 用于強制轉(zhuǎn)換類型。

lst = [1, 2, 3]
print(lst)
[1, 2, 3]
# 【反轉(zhuǎn)】:其中第二種方式會更改現(xiàn)有的列表
lst1 = list(reversed(lst))
lst.reverse()
print(lst1, lst)
[3, 2, 1] [3, 2, 1]
# 【追加】:元素 append(),另一個列表:extend()
lst.append(4)
print(lst)
[3, 2, 1, 4]
lst.extend(lst1)
print(lst)
[3, 2, 1, 4, 3, 2, 1]
# 【插入】:lst.insert(idx, obj) 會在 lst[idx] 處插入 obj,然后依次后移原有項
lst.insert(1, 100)
print(lst)
[3, 100, 2, 1, 4, 3, 2, 1]
# 【刪除】:lst.remove(obj) 會刪除首個匹配值,若無匹配會報錯;
#           lst.pop(idx) 會返回 lst[idx],并將其刪除。如果不指定 idx,默認為列表尾
lst.remove(2)
print(lst)
[3, 100, 1, 4, 3, 2, 1]
tmp = lst.pop()
print(lst, "\n", tmp)
[3, 100, 1, 4, 3, 2] 
 1
# 【搜索】:使用序列通用函數(shù)即可。用 count(obj) 可以計算頻數(shù)。
# 【排序】:sort() 方法。如果指定 reverse 參數(shù),可降序排序。
lst.sort(reverse=True)
print(lst)
[100, 4, 3, 3, 2, 1]
# 【清空】:clear()
lst.clear()
print(lst)
[]

元組(tuple)

圓括號式的結(jié)構(gòu),是一種不可變序列。

a = (1, 'string ', [1 ,2])
print(a)
(1, 'string ', [1, 2])

Note: 定義一個空的元組用(),定義只有一個元組的元組,需要加,,否則就不是元組了,如下:

>>> tuple1 = ()
>>> type(tuple1)
<type 'tuple'>
>>> tuple2 = (1)
>>> type(tuple2)
<type 'int'>
>>> tuple3 = (1,)
>>> type(tuple3)
<type 'tuple'>

字典(dict)

字典是一種類哈希表的數(shù)據(jù)結(jié)構(gòu),內(nèi)部無序,通過鍵值對(key: value)的形式存儲數(shù)據(jù)。幾種字典初始化的方式:

# 小字典直接賦值
d1 = {"name": "wklchris", "gender": "male"}
# 利用字典增加鍵值對的方法
d2 = {}
d2['name'] = 'wklchris'
# 一個值賦給多個鍵
d3 = {}.fromkeys(("name", "gender"), "NA")
# 強制格式轉(zhuǎn)換
d4 = dict(name="wklchris", gender="male")

print(d1, d2, d3, d4, sep="\n")
{'name': 'wklchris', 'gender': 'male'}
{'name': 'wklchris'}
{'name': 'NA', 'gender': 'NA'}
{'name': 'wklchris', 'gender': 'male'}

字典的操作方法:

len(d1)
2
# 【復(fù)制】:
dd = d1.copy()
dd is d1
False
# 【查找鍵名稱】:
"name" in dd
True
# 【刪除鍵值對】
del(dd["name"])
# 【get】
dd.get("name", "Nothing")  # 如果鍵不存在,返回“Nothing”
'Nothing'
# 【setdefault】
dd.setdefault("name", "wklchris")  # 如果鍵不存在,就新建該鍵,并賦值
'wklchris'
print(dd)
{'name': 'wklchris', 'gender': 'male'}
# 【輸出鍵值】:
list(dd.items())
[('name', 'wklchris'), ('gender', 'male')]
list(dd.keys())
['name', 'gender']
list(dd.values())
['wklchris', 'male']
# 【彈出鍵值對】:pop(key) / popitem(key)
# 其中,后者會隨機彈出一個鍵值對
tmp = dd.pop("gender")
print(dd, tmp)
{'name': 'wklchris'} male
# 【更新】:update(ref_dict) 以 ref_dict 為準(zhǔn),更新當(dāng)前字典
d4 = {"name": "Test", "Age": 3}
dd.update(d4)
print(dd)
{'name': 'Test', 'Age': 3}

集合(set)

本文只討論可變集合,關(guān)于不可變集合的內(nèi)容,參考 help(frozenset)。

集合是一種無序的數(shù)據(jù)存儲方式,且內(nèi)部元素具有唯一性。集合與字典一樣都可以用花括號的形式創(chuàng)立。但在書寫 a={} 時,Python 會將其識別為字典類型。

  • 增添:add() / update()
  • 刪除:remove() / discard(),區(qū)別在于后者搜索無結(jié)果會報錯。
  • 從屬:a.issubset(b) 集合 a 是否是 b 的子集;a.issuperset(b) 集合 a 是否是 b 的父集。a == b 兩集合是否全等。
  • 集合運算:集合運算不會改變參與運算的集合本身。
    • 并集: a | b 或者 a.union(b)
    • 交集: a & b 或者 a.intersection(b)
    • 補集: a - b 或者 a.difference(b)
      注意:在字符串強制轉(zhuǎn)換為集合時,必要時使用中括號先轉(zhuǎn)為列表(否則字符串會被拆分為單個字符后再進行轉(zhuǎn)換)。例如:
ss = {"a", "b", "c"}
ss | set("de")
{'a', 'b', 'c', 'd', 'e'}
ss | set(["de"])
{'a', 'b', 'c', 'de'}

基本語句

同大多數(shù)程序語言一樣,Python 擁有 if, for, while語句。什么?switch 語句?使用字典就好。

if 語句與三元操作

在 Python 中,else if 被縮寫為單個關(guān)鍵詞 elif.

if 1.0 > 1:
    a = 1
elif 1.0 < 1:
    a = 2
else:
    a = 3
    
a
3

值得一提的是,Python 中的 if 語句支持鏈?zhǔn)奖容^,形如 a < x < b, a < x >= b 等:

a = 0
if 1 < 2 > 1.5:
    a = 1
a
1

三元操作實質(zhì)是高度簡化的 if 環(huán)境,形如 X = a if flag else b

a = 1 if 2 < 1 else 2
a
2

for 語句

Python 的循環(huán)語句中,像其他語言一樣,有 break(跳出循環(huán)體) 與 continue(循環(huán)步進) 關(guān)鍵詞可以使用。

for 語句借助關(guān)鍵詞 in 使用:(函數(shù) range(N, M=0, s=1) 是一個生成等差數(shù)列的函數(shù),位于左閉右開區(qū)間[M,N)上且公差為 s)。

for i in range(3):
    print(i)
0
1
2

注意到字典的 d.items(), d.keys(), d.values() 命令也常常用于 for 語句:

d = {"a": 1, "b": 2, "c": 3}
for k, v in d.items():
    print(k, v)
b 2
c 3
a 1

以上等價于:

for k in d.keys():
    print(k, d[k])
b 2
c 3
a 1

Python 中的 for 語句可選 else 語法塊,表示 for 語句正常結(jié)束后執(zhí)行的內(nèi)容(中途 break 不屬于正常結(jié)束)。這對于處理一些 break 操作很有幫助。例如:

a = 0
flag = 0
for i in range(5):
    if i > 2:
        flag = 1
        break
if flag == 1:
    a = 1
a
1

這在 Python 中顯得太復(fù)雜了,直接使用 for…else…即可:

a = 1
for i in range(5):
    if i > 1:
        break
else:
    a = 0
a
1

while 語句

while 語句的 else 語法塊,指明了退出 while 循環(huán)后立刻執(zhí)行的內(nèi)容;它不是必需的。

如果你想要將 while 語句內(nèi)部的參數(shù)傳出(比如下例的計數(shù)器終值),這是一個不錯的方案。

count = 1
while count < 5:
    a = count
    count *= 2
else:
    b = count

print(a, b)
4 8

列表解析

列表解析是一種創(chuàng)建列表的高度縮寫方式:

lst = [x ** 2 for x in range(4)]
lst
[0, 1, 4, 9]

也可以配合 if 語句:

lst = [x ** 2 for x in range(4) if x > 0]
lst
[1, 4, 9]

類似的,也有字典解析,以及下文會介紹的生成器,也有生成器解析(把外圍的括號換成圓括號即可):

{n: n ** 2 for n in range(3)}
{0: 0, 1: 1, 2: 4}

函數(shù)

本節(jié)介紹 Python 函數(shù)的基礎(chǔ)特點,以及一些實用函數(shù)。

函數(shù)定義與判斷

使用 def 關(guān)鍵字。三連雙引號間的內(nèi)容被視為函數(shù)的幫助字符串,可以通過 help() 命令查看。

def func(a, b=0):
    """
    This is a function that can meow.
    """
    return " ".join(["meow"] * (a + b))

調(diào)用函數(shù):

func(2)  # 單參數(shù),僅 a 
'meow meow'
func(2, 3)  # 雙參數(shù), a 與 b 都被傳入
'meow meow meow meow meow'
help(func)
Help on function func in module __main__:

func(a, b=0)
    This is a function that can meow.

通過 callable() 可以判斷一個對象是否是一個可調(diào)用的函數(shù):

callable(func)
True

不定參函數(shù)

利用序列(或元組)與字典,向函數(shù)傳參。前者在傳入時需要加上一個星號,后者需要兩個。

lst = [1, 3, 4]
d = {"a": 2, "b": 3, "c": 5}
print("{}+{}={}".format(*lst), "{a}+{b}={c}".format(**d))
1+3=4 2+3=5

zip 函數(shù)

zip() 函數(shù)的作用是“合并”多個列表為一個。其返回值是一個列表,列表內(nèi)的元素類型是元組。如果待合并的列表長度不同,以最短的為準(zhǔn)。

a = [1, 2, 3, 4]
b = [5 ,6, 7]
c = "abcd"
list(zip(a, b, c))
[(1, 5, 'a'), (2, 6, 'b'), (3, 7, 'c')]

它比較常用于交換字典的鍵與值:

dict(zip(d.values(), d.keys()))
{2: 'a', 3: 'b', 5: 'c'}

lambda 函數(shù)

一種匿名函數(shù)的聲明方式。如果你使用過 MATLAB,你可能熟悉這一類概念。

func = lambda x, y: x + y
func(2, 5)
7

map 函數(shù)

map() 能夠?qū)魅氲男蛄羞M行依次操作,并將結(jié)果返回為一個可轉(zhuǎn)換為列表的 map 對象。通常列表解析(或生成器解析)可以實現(xiàn)與其同樣的工作。

lst = list(map(lambda x: x + 1, range (5)))
print(lst)
[1, 2, 3, 4, 5]
f = lambda x: x + 1
[f(x) for x in range(5)]
[1, 2, 3, 4, 5]

filter 函數(shù)

給定序列,對于滿足某規(guī)則的部分(即 True),予以返回。

list(filter(lambda x: x > 0, range(-3, 3)))
[1, 2]

reduce 函數(shù)

該函數(shù)在 Python 2 中是可以直接調(diào)用的,但在 Python 3 中需要從 functools 模塊進行調(diào)用。

from functools import reduce
reduce(lambda x, y: x + y, range (5))  # 0+1+2+3+4
10

enumerate 函數(shù)

它允許你像 d.items() 那樣,用類似的方式操作列表:

a = [1, 3, 5]
for i, v in enumerate(a):
    print("lst[{}] = {}".format(i, v))

lst[0] = 1
lst[1] = 3
lst[2] = 5

裝飾器:算子

裝飾器是函數(shù)的函數(shù)——傳入的參數(shù)是一個函數(shù),返回的值也是一個函數(shù)。相當(dāng)于一個函數(shù)集到另一個函數(shù)集的映射,可以理解為數(shù)學(xué)意義上的算子。

首先來看一個簡單的例子:函數(shù)可以被賦值給一個變量。

def pyrint(data="Python"):
    return data.upper()

f = pyrint
f()
'PYTHON'

還可以通過 __name__ 來得到當(dāng)前函數(shù)的名稱:

f.__name__
'pyrint'

那什么時候需要裝飾器呢?比如在函數(shù)需要被重用、但又不能直接改寫 def的場合(在維護中應(yīng)該不少見吧!)。例如,我們希望在返回值之前,把函數(shù)名也打印出來:

def showname(func):
    def subfunc(*args, **kwarg):
        print("FUNCTION {} called.".format(func.__name__))
        return func(*args, **kwarg)
    return subfunc

這樣如果我們通過 showname(pyrint) 這種形式,就能夠在 pyrint 函數(shù)被調(diào)用之前,額外打印一行內(nèi)容。

想要改動該函數(shù),不需要改動 def 語句以下的內(nèi)容,只需要用 @showname 命令來應(yīng)用這個裝飾器:

@showname
def pyrint(data="Python"):
    return data.upper()
pyrint()
FUNCTION pyrint called.

'PYTHON'

如果裝飾器需要傳遞參數(shù),那么,需要在定義時,外層再嵌套一個函數(shù):

def showname(num=1):
    def decorator(func):
        def subfunc(*args, **kwarg):
            print("Call time: {}. FUNCTION {} called.".format(num, func.__name__))
            return func(*args, **kwarg)
        return subfunc
    return decorator

@showname(2)
def pyrint(data="Python"):
    return data.upper()

pyrint()
Call time: 2. FUNCTION pyrint called.

'PYTHON'

不過裝飾器被應(yīng)用于函數(shù)定義之前時,函數(shù)的 __name__ 屬性會改變。比如上例:

pyrint.__name__
'subfunc'
使用模塊 functools 來解決這一問題:

import functools

def showname(num=1):
    def decorator(func):
        @functools.wraps(func)  # 加上這一行
        def subfunc(*args, **kwarg):
            print("Call time: {}. FUNCTION {} called.".format(num, func.__name__))
            return func(*args, **kwarg)
        return subfunc
    return decorator

@showname(2)
def pyrint(data="Python"):
    return data.upper()

pyrint.__name__
'pyrint'

迭代器 [itertools]

迭代器與生成器在內(nèi)存優(yōu)化上很有意義。

迭代器

迭代器最顯著的特征是擁有 __iter__()__next__() 方法;它像一個鏈表。如果它指向末尾,那么再次執(zhí)行 __next__() 時會報錯。一個例子:

a = [1, 2, 3]
b = iter(a)
print(b.__next__(), b.__next__())  # 或者使用 next(b)
1 2

實際上,Python 3 內(nèi)置了一個 itertools 的庫,里面有諸如 cyclecount 等適用于迭代器的函數(shù):

import itertools

# count: 給定首項與公差的無窮等差數(shù)列
p = itertools.count(start = 1, step = 0.5)
print(p.__next__(), p.__next__())

# cycle: 周期循環(huán)的無窮序列
p = itertools.cycle(list("AB"))
print(next(p), next(p), next(p))

# islice: 從無窮序列中切片
p = itertools.cycle(list("AB"))
print(list(itertools.islice(p, 0, 4)))
1 1.5
A B A
['A', 'B', 'A', 'B']

請時刻注意當(dāng)前指向的迭代器位置——失之毫厘,謬以千里。

生成器

生成器是迭代器的一種,其實質(zhì)是定義中含有 yield 關(guān)鍵詞的函數(shù)。它沒有 return() 語句。

生成器可以直接使用類似列表解析的方式,稱為生成器解析。例如:(i for i in range(10)。

def Fib(N):  # 斐波那契數(shù)列
    n, former, later = 0, 0, 1
    while n < N:
        yield later
        former, later = later, later + former
        n += 1

list(Fib(5))
[1, 1, 2, 3, 5]

上例與普通的寫法看上去差別不大,但實際上可以將 while 語句改寫為 while True,刪除變量 n,在外部借助 itertools 的 islice 函數(shù)來截取。這在函數(shù)定義時對代碼的壓縮是顯然的。

def iterFib():
    former, later = 0, 1
    while True:
        yield later
        former, later = later, later + former

list(itertools.islice(iterFib(), 0, 5))
[1, 1, 2, 3, 5]

錯誤:try() 語句

常見的錯誤有以下幾種:

  • ZeroDivisionError: 除數(shù)為 0.
  • SyntaxError:語法錯誤。
  • IndexError:索引超界。
  • KeyError:字典鍵不存在。
  • IOError:讀寫錯誤。
    try() 語句的常見寫法:
try:
    a = 1 
except ZeroDivisionError as e:
    print(e)
    exit()
else:  # 如果無錯誤,執(zhí)行
    print(a)
finally:  # 不管有無錯誤均執(zhí)行
    print("-- End --")
1
-- End --

其中,elsefinally 語句都不是必需的。如果不想輸出錯誤信息、或不能預(yù)先判斷可能的錯誤類型,可以使用僅含 exit() 語句的 except 塊。

多個 except 塊

一個 try 語法塊是可以跟著多個 except 的;如果靠前的 except 捕獲了錯誤,之后的就不會運行。 這也就是說,如果錯誤之間有繼承關(guān)系時,子錯誤需要放在父錯誤之前嘗試 except,否則子錯誤永遠也不可能被捕獲。

比如上一節(jié)的例子中,ZeroDivisionErrorArithmeticError 下的子錯誤,而 ArithmeticError 又是 Exception 下的子錯誤(當(dāng)不清楚錯誤的類型時,Exception 可以捕獲絕大多數(shù)錯誤)。關(guān)于錯誤的繼承關(guān)系,參考:Python - Exception Hierarchy 官方頁面。

一個例子:

try:
    a = 1 / 0
except Exception:
    print("Exception")
    exit()
except ZeroDivisionError:
    print("ZeroDivisionError")
    exit()
else:
    print("No error.")
finally:
    print("-- End --")
輸出 Exception 與 – End –。

錯誤的捕獲

錯誤在很多地方都可能發(fā)生,那是否需要在可能的地方都加上 try 語句呢?當(dāng)然不是。建議只在主代碼中加入 try 語句,因為 Python 會自動跟蹤到錯誤產(chǎn)生的源頭何在。

錯誤的拋出及上拋

有時候我們想人為拋出一個錯誤,這是使用 raise 即可:

# raise TypeError("Wrong type.")

如果在函數(shù)中沒有處理錯誤的語句,可能在捕獲錯誤后將其上拋。記住,捕獲錯誤只是為了記錄錯誤的產(chǎn)生,并不意味者必須原地解決錯誤。

def makeerror(n):
    if n == 0:
        raise ValueError("Divided by zero.")
    return 1 / n

def callerror():
    try:
        makeerror(0)
    except ValueError as e:
        print("ValueError detected.")
        raise

# 輸出 "ValueError detected." 并打印錯誤日志
# callerror()

上面的 raise 命令沒有緊跟任何參數(shù),表示將錯誤原樣上拋。你也可以手動指定上拋的錯誤類型,并不需要與原錯誤類型一致。甚至你可以定義一個錯誤(繼承某一錯誤類):

class MyError(ValueError):
    print("This is MyError.")

# raise MyError
This is MyError.

文件讀寫

open() 函數(shù)用于文件的讀寫操作。一般我們會在操作文件時,引入 os 模塊(os 模塊的用法參考“常用模塊”一節(jié)的內(nèi)容)。

import os

open() 函數(shù)常常配合 with 語法塊進行使用,它會在語法塊結(jié)束時自動關(guān)閉文件。該函數(shù):

open(file, mode="r", encoding=None)

第一參數(shù)是包含文件名的路徑(傳入基于當(dāng)前目錄的相對路徑,傳入或者絕對路徑),mode 參數(shù)是讀寫操作方式;encoding 是編碼類型,一般取”utf8”。其中,讀寫操作方式常用的有:

參數(shù) 含義
“r” (默認)讀。
“w” 寫。該模式會覆蓋原有內(nèi)容;如文件不存在,會自動新建。
“x” 創(chuàng)建新文件并寫入。
“a” 在已有文件的尾部追加。

一般讀寫操作:read() / readlines()

函數(shù) read() 將整個文件讀為一個字符串,來看一個例子:

datapath = os.path.join(os.getcwd(), "data", "iris.data.csv")
with open(datapath, "r", encoding="utf8") as f:
    rawtext = f.read()

rawtext[:200]
'5.1,3.5,1.4,0.2,Iris-setosa\n4.9,3.0,1.4,0.2,Iris-setosa\n4.7,3.2,1.3,0.2,Iris-setosa\n4.6,3.1,1.5,0.2,Iris-setosa\n5.0,3.6,1.4,0.2,Iris-setosa\n5.4,3.9,1.7,0.4,Iris-setosa\n4.6,3.4,1.4,0.3,Iris-setosa\n5.0,'

函數(shù) readlines() 將整個文件讀為一個列表,文件的每一行對應(yīng)列表的一個元素。

with open(datapath, "r", encoding="utf8") as f:
    rawtext = f.readlines()

rawtext[:3]
['5.1,3.5,1.4,0.2,Iris-setosa\n',
 '4.9,3.0,1.4,0.2,Iris-setosa\n',
 '4.7,3.2,1.3,0.2,Iris-setosa\n']

上述的 readlines() 函數(shù)實質(zhì)等同于列表解析:

with open(datapath, "r", encoding="utf8") as f:
    rawtext = [line for line in f]

rawtext[:3]
['5.1,3.5,1.4,0.2,Iris-setosa\n',
 '4.9,3.0,1.4,0.2,Iris-setosa\n',
 '4.7,3.2,1.3,0.2,Iris-setosa\n']

文件寫入,使用 write() 函數(shù)。一個簡單的例子:

with open(datapath, "w") as f:
   f.write("Sometimes naive.")

大文件讀取:readline()

如果文件比較大,使用 read()/readlines() 函數(shù)直接讀入可能會占用太多內(nèi)存。推薦使用函數(shù) readline(),一種迭代器式的讀取方法。

with open(datapath, "r", encoding="utf8") as f:
    print(f.readline().strip())
    print(f.readline().strip())
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa

你會發(fā)現(xiàn)兩次結(jié)果是不同的,這是因為迭代器內(nèi)部的“指針”向后移動了。

怎樣獲取 / 移動“指針”的位置呢?使用 tell() / seek() 命令。

with open(datapath, "r", encoding="utf8") as f:
    print(f.tell(), f.readline().strip())
    print(f.tell(), f.readline().strip())
    f.seek(0)  # 回到文件頭
    print(f.tell(), f.readline().strip())
0 5.1,3.5,1.4,0.2,Iris-setosa
28 4.9,3.0,1.4,0.2,Iris-setosa
0 5.1,3.5,1.4,0.2,Iris-setosa

類的成員包括屬性(attribute)與方法(method)兩種。例子:

class MyClass:
    """
    This is a class that can meow!
    """
    animal = "cat"  # An attribute
    def talk(self):  # A method
        return "Meow"

# An instance of the class
a = MyClass()
print(a.animal, a.talk())
cat Meow

上例中的 self 表示類的實例,所有類內(nèi)部的方法都需要把該參數(shù)放在首位(你也不可不用 self 而使用 this 等,但是 self 是慣例)。例如,self.animal 就表示了實例的 animal 屬性。這與 C# 等語言中的“this.animal”是類似的。

下例證明了 self 代表的實質(zhì)是類的實例,而不是類本身。

class EgClass:
    def __init__(self):
        print(self)  # 實例,有對應(yīng)地址
        print(self.__class__)  # 類

a = EgClass()
<__main__.EgClass object at 0x000002531C0AF860>
<class '__main__.EgClass'>

構(gòu)造函數(shù):__init__()

類的構(gòu)造函數(shù)是 __init__() (左右均為雙下劃線),用于初始化實例。在聲明實例時,該函數(shù)自動被調(diào)用。

class MyClass2:
    def __init__(self, animal="cat"):
        self.animal = animal

a = MyClass2("dog")
a.animal
'dog'

封裝

類的重要特性是封裝性,即部分變量只能在其內(nèi)部修改或訪問,不能從類的外部進行處理。Python 中的封裝非常簡單,只要把屬性或方法的名稱前綴設(shè)置為雙下劃線即可。

由此可見,構(gòu)造函數(shù) __init__() 是最基本的一個私有方法。一個例子:

class MyClass3:
    def __init__(self, animal="cat"):
        self.__animal = animal
        self.__foo()
    def __foo(self):
        self.__animal = "rabbit"
    def show(self):
        print(self.__animal)

a = MyClass3("dog")
a.show()
rabbit

如果想直接調(diào)用 __foo() 或者 __animal,都會被禁止,產(chǎn)生 AttributeError

# a.__animal  # AttributeError

要注意,前后均添加了雙下劃線的屬性,如 name ,表示特殊屬性而不是私有屬性,是可以從外部訪問的。

繼承

下面是一個著名的貓與狗的例子;類 Cat 與 Dog 都繼承自 Animal,同時也都重載了方法 talk()。

class Animal:
    def talk(self):
        pass # 表示定義留空

class Cat(Animal): # 從Animal 繼承
    def talk(self): # 重寫talk()
        print('Meow')

class Dog(Animal):
    def talk(self):
        print('Woof')

a, b = Cat(), Dog()
a.talk() # 'Meow'
b.talk() # 'Woof'
Meow
Woof

通過 isinstance() 函數(shù)可以判斷一個對象是否是某個類(或其子類)的實例:

print(isinstance(a, Cat), isinstance(a, Animal))
True True
或者:

type(a).__name__
'Cat'

當(dāng)然,類也可以多繼承。寫在左側(cè)的類的屬性與方法,在繼承時會被優(yōu)先采用。例如:

class Pet:
    def talk(self):
        print("Pet")

class Cat2(Pet, Cat):
    pass

a = Cat2()
a.talk()
Pet

@property 裝飾器

裝飾器 @property可以被用于限制類屬性的讀寫行為。比如,一個普通的類,如果想封裝一個屬性,卻允許從外部讀取它的值,一般我們用 getter 函數(shù)實現(xiàn):

class Person:
    def __init__(self):
        self.__name = "Py"
    def get_name(self):
        return self.__name
a = Person()
a.get_name()
'Py'

不得不說這實在是麻煩了,代碼里一堆 get 函數(shù)滿天飛并不令人愉快。而且還不能忘記它是一個函數(shù),需要在尾部加上括號。

裝飾器 @property 可以將一個方法偽裝成同名的屬性,因此裝飾了 getter 函數(shù)后,調(diào)用時就不用加上尾部的括號了:

class Person:
    def __init__(self):
        self.__name = "Py"
        
    @property
    def name(self):
        return self.__name
a = Person()
a.name
'Py'

而且,如果你想從外部修改該屬性的值,會產(chǎn)生錯誤:

a.name = 1
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-97-8c607f2aa25b> in <module>()
----> 1 a.name = 1


AttributeError: can't set attribute

但同時,我們也可以指定其 setter 函數(shù)(該裝飾器 @age.setter 在用 @property 裝飾 age 方法后會自動生成),讓屬性修改成為可能,甚至附加修改條件:

class Person:
    def __init__(self):
        self.__age = 20
        
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise ValueError("Age should be an integer.")
        else:
            self.__age = value
a = Person()
a.age = 30
a.age
30

不傳入整數(shù)會報錯:

a.age = 0.5
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-100-001bfa8fe26b> in <module>()
----> 1 a.age = 0.5


<ipython-input-98-83364d5faa13> in age(self, value)
     10     def age(self, value):
     11         if not isinstance(value, int):
---> 12             raise ValueError("Age should be an integer.")
     13         else:
     14             self.__age = value


ValueError: Age should be an integer.

類的特殊屬性與方法

屬性 __dict__

首先是 __dict__屬性,用于查看類的屬性與方法,返回一個字典。

a = MyClass()
MyClass.__dict__
mappingproxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,
              '__doc__': '\n    This is a class that can meow!\n    ',
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
              'animal': 'cat',
              'talk': <function __main__.MyClass.talk>})

需要注意的是,此時實例 a 的屬性沒有被更改過,實例的 dict 是一個空字典:

print(a.__dict__, a.animal)
{} cat

類的 __dict__ 方法下的同名鍵,與實例具有相同值。

MyClass.__dict__["animal"]
'cat'

一旦被從外部更改,實例 a 的 dict 字典就不再為空。

a.animal = "dog"
print(a.__dict__, a.animal)
{'animal': 'dog'} dog

屬性 __slots__

從上面可以看到,非私有的類屬性可以從外部更改值,而且屬性還能直接從外部增加。slots 屬性的作用就在于使類的屬性不能從外部進行更改、追加。它能夠限制屬性濫用,并在優(yōu)化內(nèi)存上也有意義。

class MySlotClass():
    __slots__ = ("meow", "woof")
    def __init__(self):
        self.meow = "Meow"
        self.woof = "Woof"
    
a = MySlotClass()
MySlotClass.__dict__
mappingproxy({'__doc__': None,
              '__init__': <function __main__.MySlotClass.__init__>,
              '__module__': '__main__',
              '__slots__': ('meow', 'woof'),
              'meow': <member 'meow' of 'MySlotClass' objects>,
              'woof': <member 'woof' of 'MySlotClass' objects>})

此時,如果使用 a.__dict__,結(jié)果不會返回空字典,而是會報錯。

運算符重載

特別地,Python 提供了運算符重載的功能。常用的對應(yīng)如下(參考 官方頁面):

方法 含義 應(yīng)用
一元運算符
len 長度 len(a)
bool 邏輯值 bool(a)
neg 取負值 -a
str / repr 字符串形式 repr(a) / str(a), print(a)
二元運算符
add 加 a + b, a += b
sub 減 a - b, a -= b
mul 乘 a * b, a *= b
div 除 a / b, a /= b
pow 乘方 a ** b, a **= b
radd 左加 … + a
二元關(guān)系符
lt / le 小于 / 小于等于 a < b, a <= b
gt / ge 大于 / 大于等于 a > b, a >= b
eq / ne 等于 / 不等于 a == b, a != b
比如下例中,對多個運算進行了重載,完成了二維向量在加減法上與向量、與數(shù)運算的基本定義。

class Vector:
    def __init__(self, a, b):
        self.a = a
        self.b = b
   
    def __add__(self, another):
        if isinstance(another, Vector):
            c, d = another.a, another.b
        else:
            c, d = another, another
        return Vector(self.a + c, self.b + d)
    
    def __radd__(self, another):
        return self.__add__(another)
    
    def __neg__(self):
        return Vector(-self.a, -self.b)
    
    def __sub__(self, another):
        return self.__add__(-another)
    
    def __str__(self):
        return "Vector({},{})".format(self.a, self.b)

v1 = Vector(0,3)
v2 = Vector(5,-2)
print(v1 - 1, -v2, v1 + v2, v1 - v2)
Vector(-1,2) Vector(-5,2) Vector(5,1) Vector(-5,5)

其中,__repr__()__str__() 的主要區(qū)別在于,前者在交互式步驟中顯示結(jié)果,后者在 print 函數(shù)中顯示結(jié)果。

例如上例,如果直接輸入 v1,不會以 “Vector(0,3)”的形式顯示。

v1  # 在類中附加定義: __repr__ = __str__ 即可解決問題。
<__main__.Vector at 0x2531c129c88>

迭代行為

在類中也能定義迭代行為,需要 iter() 與 next() 方法。

# 該例改編自官方文檔
class MyClass4:
    def __init__(self, lst):
        self.data = lst
        self.__index = len(lst)
    def __iter__(self):
        return self
    def __next__(self):
        if self.__index == 0:
            raise StopIteration
        self.__index -= 1
        return self.data[self.__index]

a = MyClass4("Meow")
for char in a:
    print(char)
w
o
e
M

常用模塊

下面介紹幾個常用的 Python 標(biāo)準(zhǔn)模塊(即隨 Python 安裝的模塊)。更多的第三方模塊,例如 NumPy, pandas, matplotlib,可以參考本系列博文的其他文章。

os 模塊

這個模塊應(yīng)該是 Python 自帶模塊中使用率最高的一個了。一些例子:

# import os
#
# ----- 文件操作 -----
# os.rename("old.py", "new.py")  # 重命名
# os.remove("a.py")  # 刪除
# os.stat("b.py")  # 查看文件屬性
#
# ----- 路徑操作 -----
# os.getcwd()  # 獲取當(dāng)前目錄
# os.chdir(r"d:\list")  # 更改當(dāng)前目錄為
# os.chdir(os.pardir)  # 返回上一級目錄
# os.mkdir('newfolder ')  # 在當(dāng)前目錄新建一個文件夾
# os.listdir('c:\list')  # 列出文件夾下所有文件的列表
# os.removedirs('thefolder ')  # 刪除空文件夾
# os.path.isfile/ispath("f")  # 檢查路徑是文件或是目錄
# os.path.exists("f")  # 檢查路徑是否存在
# 
# ----- 操作平臺相關(guān) -----
# os.sep  # 當(dāng)前操作系統(tǒng)的路徑分隔符
# os.linesep  # 當(dāng)前操作系統(tǒng)的換行符
# os.path.join(r"c:\abc", "d")  # 連接字串成為路徑

sys 模塊

一般我很少用到這個模塊。可能有這么幾個命令會用到:

  • sys.argv:能夠傳遞從命令行接受的參數(shù)到代碼內(nèi)。
  • sys.platform:當(dāng)前操作系統(tǒng)平臺。
  • sys.exit():無參數(shù)時拋出 SystemExit 錯誤并退出;有參數(shù)時會在退出前輸出對應(yīng)的字符串到屏幕。
import sys
sys.platform
'win32'

一個 sys.argv 的例子:

sys.argv
['e:\\python\\lib\\site-packages\\ipykernel_launcher.py',
 '-f',
 'C:\\Users\\wklchris\\AppData\\Roaming\\jupyter\\runtime\\kernel-3724c4c9-2130-485d-b388-7a84379fd043.json']

以上不是典型的例子,因為并不是在命令行下運行的。命令行下通常有如下格式:

python test.py hello

此時,sys.argv[0] = test.py,sys.argv[1] = hello.

re 模塊:正則表達式

參考本文附錄。

其他模塊

  • collection 模塊:
    • 提供了一種雙端列表 deque,可以用 appendleft, extendleft, popleft 等方法從 deque 的左側(cè)(也就是lst[0])進行操作。注意,deque 的更新操作比 list 更快,但讀取操作比 list 慢。
    • 提供了一種缺省字典defaultdict,可以直接操作鍵值(即使這個鍵先前未定義);首次操作時會賦一個合理的初值,比如首次調(diào)用 d["a"] += 1 而字典本身沒有 “a” 鍵時,會自動初始化 “a” 鍵并賦初值 0。
  • calendar 模塊:判斷星期、閏年,輸出日歷等等。
  • itertools 模塊:在本文“迭代器”小節(jié)已進行了簡要介紹。
  • logging 模塊:在調(diào)試中可能會使用。
  • urllib 模塊:這是一個 HTML 請求模塊,常用于爬蟲。

調(diào)試與測試

Python 中有一些內(nèi)置的辦法進行調(diào)試與測試。

斷言:assert

斷言的含義在于,如果斷言失敗(False),那么代碼會被終止(拋出一個AssertionError)。比如:

n = 0
assert(n != 0)
1 / n
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-112-e53f92f6c644> in <module>()
      1 n = 0
----> 2 assert(n != 0)
      3 1 / n


AssertionError: 

與大家一貫喜歡使用的 print 調(diào)試法相比,斷言語句可以用命令行參數(shù) -O 忽略。這樣所有的 assert() 語句都不會被執(zhí)行。

$ python -O main.py

日志調(diào)試:logging 模塊

logging 模塊支持將錯誤日志輸出(到控制臺或者到文件)。

此乃調(diào)試神器。延伸閱讀: logging 官方基礎(chǔ)教程。

import logging
# 該行也可能通過控制臺:$ python main.py --log=WARNING 的方式實現(xiàn)
logging.basicConfig(level=logging.WARNING)
n = 0
logging.warning("n = {}".format(n))
WARNING:root:n = 0

logging 模塊的靈活之處在于你可以記錄信息的級別(DEBUG,INFO,WARNING,ERROR,CRITICAL),各級別的作用如下:

  • DEBUG:最詳細的級別,所有詳細日志都會被輸出。
  • INFO:檢測代碼是否按照預(yù)期執(zhí)行。
  • WARNING:非預(yù)期的事件發(fā)生了,或者可能在近期發(fā)生(例如:低磁盤空間)。但代碼仍然執(zhí)行。
  • ERROR:發(fā)生了級別更高的問題,某些功能無法正常實現(xiàn)。
  • CRITICAL:嚴(yán)重錯誤,代碼可能無法繼續(xù)運行。
    通過 filename 參數(shù),可以將日志寫入到文件。一般使用 DEBUG 級別,即輸出所有信息。
# logging.basicConfig(filename="log.log", level=logging.DEBUG)

默認會將日志追加到文件末尾,如果想要覆寫文件而不是追加,使用 filemode 參數(shù):

# logging.basicConfig(filename="log.log", filemode="w", level=logging.DEBUG)

更改日志格格式,使用 format 參數(shù)。一般來說,常用的格式碼(格式碼后加 s 表示字符串)有:

  • %(levelname):當(dāng)前日志字串級別。
  • %(message):當(dāng)前日志字串。
  • %(asctime):當(dāng)前時間。默認 datefmt 參數(shù)為 %Y-%m-%d %I:%M:%S
    例子。下例會輸出形如:”01/23/1900 08:05:05 PM is when this event was logged.” 這樣的格式。
# logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
# logging.warning('is when this event was logged.')
# 常用的格式:
# logging.basicConfig(format="%(levelname)s: %(message)s")

還可以通過配置文件來代替 basicConfig 命令,并進行設(shè)置 logger 等更高級的配置。這部分可以參考:此處。

# import logging.config

# logging.config.fileConfig('logging.conf')

附錄:正則表達式

正則表達式的基礎(chǔ)內(nèi)容參考本博客的這篇博文:正則表達式。注意:如果要保存一個正則表達式供多次使用,請存儲其 compile 后的結(jié)果,避免反復(fù)編譯。

  • re.compile(exp):編譯正則表達式。
  • re.compile(exp).match(str):判斷正則表達式能否匹配一個字串。可以 bool() 結(jié)果來獲知是否匹配。
    • re.compile(exp).match(str).groups():將匹配結(jié)果返回為單個字符串(無子組時)或元組(有子組時)。
    • re.compile(exp).findall(str):找出字符串中所有匹配表達式的子串。返回列表。
  • re.split(exp, str):用表達式來分割字符串,相當(dāng)于 str.split() 的增強版。
import re
bool(re.match(r"\d", "1"))
True
phone_re = re.compile(r'\d{3,4}-\d{7,8}')
phone_re.match('010-12345678').group()
'010-12345678'
# 如果在正則表達式中添加了子組(小括號),那么會返回子組依順序組成的一個元組
phone_re = re.compile(r'(\d{3,4})-(\d{7,8})')
phone_re.match('010-12345678').groups()
('010', '12345678')
phone_re = re.compile(r'\d{3,4}-\d{7,8}')  # 尋找所有子串
phone_set = '010-12345678, 021-65439876 '
phone_re.findall(phone_set)
['010-12345678', '021-65439876']
s = 'a b   c'  # 用 re.split() 處理連續(xù)的空格
print(s.split(' '), re.split(r"\s+", s))
['a', 'b', '', '', 'c'] ['a', 'b', 'c']
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容