人生苦短我用Python,本文助你快速入門

友情提示:本文針對的是非編程零基礎的朋友,可以幫助我們快速了解Python語法,接著就可以快樂的投入到實戰環節了。如果是零基礎,還是老老實實看書最為穩妥。

前言

​ 偶然在知乎上看到了一些好玩的Python項目(學 Python 都用來幹嘛的?),讓我對Python產生了些許興趣。距離北漂實習還有兩個月時間,正好可以在這段空閑時間裡學一學。如果能做出些小工具,說不定對工作還有幫助,何樂而不為呢?

​ 關於環境的安裝和IDE就不多說了,網上有很多教程。這裡貼出一篇部落格,大家按裡面的步驟安裝就行:VSCode搭建Python開發環境。使用VSCode主要是因為免費,而且有大量插件可以下載,大家可以盡情的訂製自己的IDE。如果曾經沒有使用過VSCode,最好多了解下哪些必須的插件,優化自己的Coding體驗。比如:Python插件推薦

​ 環境搭建好後,就可以愉快地敲程式碼了。VSCode需要自己創建Python文件,以.py為後綴。Ctrl+F5運行程式,F5調試程式。

Python基礎

注釋

​ 單行注釋:#

​ 多行注釋:”’ (三個英文單引號開頭,三個英文單引號結尾)

# 這是單行注釋

'''
這是多行注釋
'''

變數

​ Python的變數定義不需要顯式指明數據類型,直接【變數名=值】即可。注意變數名分大小寫,如Name和name不是同一個變數。

name = "小王"
print(name) # 輸出 小王

數據類型

​ Python提供6種基礎的數據類型:數字類型(number)、字元串類型(string)、列表(list)、元組(tuple)、字典(dictionary)、集合(set)。其中數字類型還包括三種數值類型:整型(int)、浮點型(float)、複數類型(complex)。

​ 列表、元組那些我們留在容器那一節裡面講,先看看數字類型。

浮點型

​ 浮點型表示小數,我們創建一個浮點型變數,再通過type函數看一看它的類型:

pi = 3.1415926
print(type(pi)) # 輸出<class 'float'>

int整數型就不說了,其為Integer的縮寫。

複數類型

​ 複數類型,所謂複數就是我們中學學的,實數+虛數,比如:

x = 10+1.2j # 虛數以j或J結尾
print(type(x)) # 輸出<class 'complex'>

​ 剛開始接觸複數時,很納悶為啥會有這種類型,到底有啥實際作用,遂百度了一番:

mzy0324:微電子方面的運算基本全部都是複數運算。

hilevel:至少複數用來計算向量的旋轉要比矩陣方便多了。科學計算和物理應該會用得到吧。PS:我經常把Python當帶編程功能的計算器用,用來調試純粹的數學演算法挺方便的。

morris88:Python 的一大應用領域,主要是科學計算,主要用於太空宇航、銀行等。

​ 聯想到Python平時在演算法、科學研究等領域應用頗多,所以也就明白了,只是自己沒使用的需求而已。

字元串

​ 字元串類型的變數定義用一對雙引號或者單引號括起來。如:

x = "Hello Python"
y = 'Hello Python'
print(x,y) # 輸出Hello Python Hello Python

​ 字元串內置函數:

函數 作用
find(str[,start,end]) 在字元串中查找子串str,可選參數start和end可以限定範圍
count(str[,start,end]) 在字元串中統計子串str的個數,可選參數start和end可以限定範圍
replace(old,new[,count]) 在字元串中用new子串替換old子串,可選參數count代表替換個數,默認全部替換
split(sep[,maxsplit]) 用指定分隔符sep分割字元,返回一個列表,可選參數maxsplit代表分割幾次,默認全部
upper()、lower() 轉換大小寫
join(序列) 把序列中的元素用指定字元隔開並生成一個字元串。
startwith(prefix[,start,end]) 判斷字元串中是否以prefix開頭,返回bool類型。還有一個endwith,判斷結尾的。
strip([,str]) 去掉字元串開頭和結尾的空白字元(包括\n、\t這些),可選參數代表可以去掉指定字元

布爾類型

​ 順便再說一下布爾類型,不過與Java不同的是,布爾類型的True和False,首字母必須大寫:

x = True
print(type(x)) # 輸出<class 'bool'>

類型轉換

​ 說完幾個基本的數據類型,不免要提到類型轉換。Python內置一些類型轉換的函數:

函數名 作用
int(x) 將x轉換為整型(小數轉整型會去掉小數部分)
float(x) 將x轉換為浮點型
str(x) 將x轉換為字元串
tuple(x) 將x轉換為元組
list(x) 將x轉換為列表
set(x) 將x轉換為集合,並去重

輸入與輸出

​ 輸入函數為input。input函數返回用戶輸入的資訊為字元串類型。所以如果你輸入的是數字類型,記得類型轉換

x = input("請輸入數字")
print(type(x),x) # 輸出<class 'str'> 10

​ 輸出前面已經演示了很多次了,函數為print,可以直接輸出變數與值。一次輸出多個變數可以用逗號隔開,就想上面的演示一樣,既要輸出類型,也要輸出值。不換行輸出,可以在print函數里加上end=””這個參數,因為print默認end=”\n”,\n就是換行的意思。如果想輸出特殊字元,可能需要用到轉義字元:\。

x = 10
y = 20
print(x,y,end="") # 輸出10 20 加上end="" 不換行
print("Hello \\n Python") # 輸出 Hello \n Python

​ 在輸出時,還可以格式化輸出內容:%s代表字元串格式、%d代表整型、%f代表浮點型

z = 1.2
print("%f"%z) # 輸出 1.200000

​ 除了格式化,%d等還可以當作佔位符:

name = "小明"
age = 18
print("姓名:%s,年齡:%d"%(name,age)) # 姓名:小明,年齡:18

​ 如果你閑這個佔位符麻煩,還可以使用format函數,佔位符只用寫一對{}:

print("姓名:{},年齡:{}".format(name,age)) # 姓名:小明,年齡:18

運算符

算術運算符

​ 除了加減乘除,還有冪(**)、取模(%)、取整(//)

x = 3 ** 2 # x=9 即3的2次方 
y = 5 % 3 # y=2 即5除以3餘2
z = 5 // 2 # z=2 即5除以2,整數部分為2

比較運算符

​ 和其他常用程式語言基本一模一樣,不等於(!=)、大於等於(>=)、等於(==)。

賦值運算符

​ Python也支援+=、*=等形式的賦值運算。除此之外,當然也支援前面說到的冪、取模等算術運算符,如取整並賦值(//=)、取模並賦值(%=)。

x = 10
x %= 3
print(x) # 輸出1 ,x%=3 意為 x = x%3

邏輯運算符

​ 非(not)、與(and)、或(or)

x = True
print(not x) # 輸出 False

if、while、for

​ 這三個和其他程式語言基本沒差,就是寫法上有點區別。首先沒了大括弧,條件語句後以冒號開頭;程式碼快有嚴格的縮進要求,因為沒了大括弧,縮進就是條件語句判斷自己程式碼快範圍的依據。其他的基本一樣,比如continue跳過當次循環,break跳出整個循環體。下面看三個簡單的例子就明白了:

a = 10
# if或else後面是冒號,程式碼塊還需要縮進
if a >= 10:
    print("你好啊老大")
else:
    print("滾蛋")
    
# 同樣的while後面也需要冒號,程式碼塊必須縮進。(Python沒有num++,得寫成num+=1)
# print想不換行列印,最後得加個end="",因為默認有一個end="\n"
# " "*(j-i),代表j-i個空格
i = 1
j = 4
while i <= j:
    print(" "*(j-i), end="")
    n = 1
    while n <= 2*i-1:
        print("*", end="")
        n += 1
    print("")
    i += 1

# 語法:for 變數 in 序列 ,還沒講序列,暫時用range表示,代表1-21的序列
# continue略過當次循環,break跳出整個循環
for i in range(1, 21):
    if i % 2 == 0:
        if(i % 10 == 0):
            continue
        if(i >= 15):
            break
        print(i)

容器

列表

​ 列表使用一對[]定義,每個元素用逗號隔開,元素類型不強求相同,通過索引獲取列表元素。具體的我們看下面的程式碼:

info_list = ["小紅", 18, "男"] #可以不是同一類型
info_list[2] = "女" # 修改指定索引位置的元素
del info_list[1] # 刪除指定索引位置的元素
info_list.remove("女") # 刪除列表中指定的值
for att in info_list:   # 遍曆元素
    print(att)

​ 上面的示例程式碼演示了部分列表的用法,下面再列出一些其他的常用函數或語法:

函數或語法 作用
list.append(element) 向列表list結尾添加元素(這個元素也可以是個列表)
list.insert(index,element) 向列表指定位置添加元素
list.extend(new_list) 向列表list添加new_list的所有元素
list.pop([,index]) 彈出最後一個元素,可選參數index,彈出指定位置元素
list.sort([,reverse=True]) 對列表排序,可選參數reverse=True表示降序
list[start:end] 對列表分片,start和end代表起始結束索引
list1+list2 拼接兩個列表

元組

​ 元組用一對()定義。元組也是有序的,它和列表的區別就是,列表可以修改元素,元組不行。正是因為這個特點,元組佔用的記憶體也比列表小。

name_list=("小紅","小王")

字典

​ 字典使用一對{}定義,元素是鍵值對。用法示例如下:

user_info_dict = {"name": "小王", "age": "18", "gender": "男"}
name = user_info_dict["name"] # 直接用key獲取value
age = user_info_dict.get("age") # 也可以用get(key)獲取value
user_info_dict["tel"] = "13866663333" # 當key不存在,就是往字典添加鍵值對,如果存在就是修改value
del user_info_dict["tel"] # 刪除指定鍵值對

​ 以上就是常用語法和函數。字典也可以遍歷,只是遍歷時,需要指定遍歷的是key還是value,比如:

for k in dict.keys(): # 遍歷所有key
for v in dict.values(): # 遍歷所有value
for item in dict.items(): # 也可以直接遍歷鍵值對

集合

​ 集合是無序的,也用一對{}定義,但不是鍵值對了,是單獨且不重複的元素。部分用法如下:

user_id_set = {"1111","22222","3333"} # 元素不重複
print(type(user_id_set)) # 輸出<class 'set'>
# 除了直接用{}定義,還可以用set函數傳入一個序列,其會為list去重,並返回一個集合(如果是字元串,字元串會被拆成字元)
new_user_id_set = set(list) 

​ 上面演示了部分用法,下面我們用一個表格展示一些常用的函數或語法:

函數或語法 作用
element in set 判斷元素是否在集合中,返回布爾類型
element not in set 判斷元素是否不在集合中
set.add(element) 向集合添加元素
set.update(list,…..) 將序列中的每個元素去重並添加到集合中,如果有多個序列,用逗號隔開
set.remove(element) 刪除指定元素,如果元素不存在就會報錯
set.discard(element) 刪除指定元素,如果元素不存在也不會報錯
set.pop() 隨機刪除集合中的元素,並返回被刪除的元素
set1 & set2 或set1 intersection set2 求兩個集合的交集,兩種用法結果一樣
set1 | set2 或set1 union set2 求兩個集合的並集
set1 – set2 或set1.difference(set2) 求兩個集合的差集,注意順序。set1-set2代表set1有set2沒有的元素

函數

函數的定義

​ Python中函數用def定義,格式為:

def function_name(參數列表): # 參數可為空,多個參數用逗號隔開
	函數體
	return 返回值 #可選

# 函數的調用
function_name(參數列表)

預設參數

​ 和循環體一樣的,因為沒有了大括弧,所以縮進是嚴格要求的。除了上面那種比較常見的格式,Python函數的參數中,還有一種預設參數,即帶有默認值的參數。調用帶有預設參數的函數時,可以不用傳入預設參數的值,如果傳入了預設參數的值,則會使用傳入的值。

def num_add(x,y=10): # y為預設函數,如果調用這個函數只傳入了x的值,那麼y默認為10

命名參數

​ 一般情況下,調用函數傳入實參時,都會遵循參數列表的順序。而命名參數的意思就是,調用函數時,通過參數名傳入實參,這樣可以不用按照參數定義的順序傳入實參。

def num_add(x, y):
    print("x:{},y:{}".format(x, y))
    return x+y
# 輸出:
# x:10,y:5
# 15
print(num_add(y=5, x=10))

不定長參數

​ 不定長參數可以接收任意多個參數,Python中有兩種方法接收:1.在參數前加一個*,傳入的參數會放到元組裡;2.在參數前加兩個**,代表接收的是鍵值對形式的參數。

# 一個*
def eachNum(*args):
    print(type(args))
    for num in args:
        print(num)
# 輸出:
# <class 'tuple'>『
# (1, 2, 3, 4, 5)
eachNum(1,2,3,4,5)

## 兩個**。這個other是想告訴你,在使用不定長參數時,也可以搭配普通的參數
def user_info(other,**info):
    print(type(info))
    print("其他資訊:{}".format(other))
    for key in info.keys():
        print("{} : {}".format(key,info[key]))
# 傳入參數時,不用像定義字典一樣,加個大括弧再添加鍵值對,直接當命名參數傳入即可
# 輸出:
# <class 'dict'>
# 其他資訊:管理員
# 略...
user_info("管理員",name="趙四",age=18,gender="男")

​ 上面示例程式碼中的注釋說到了,當使用不定長參數時,不用像字典或者元組的定義那樣,直接傳入參數即可。但有時候,可能會遇到想把字典、元組等容器中的元素傳入到不定長參數的函數中,這個時候就需要用到拆包了。

​ 所謂拆包,其實就是在傳入參數時,在容器前面加上一個或兩個*。還是以上面的user_info函數為例:

user_info_dict={"name":"趙四","age":18,"gender":"男"}
user_info("管理員",**user_info_dict) # 效果和上面一樣

​ 注意,如果接收方的不定長參數只用了一個 * 定義,那麼傳入實參時,也只能用一個 *。

匿名函數

​ 匿名函數,即沒有名字的函數。在定義匿名函數時,既不需要名稱,也不需要def關鍵字。語法如下:

lambda 參數列表: 表達式

​ 多個參數用逗號隔開,匿名函數會自動把表達式的結果return。在使用時,一般會用一個變數接收匿名函數,或者直接把匿名函數當參數傳入。

sum = lambda x,y : x+y
print(sum(1,2)) # 輸出3

閉包和裝飾器

​ 在Python中,函數內還可以定義函數,外面這個函數我們就稱為外部函數,裡面的函數我們就稱為內部函數。而外部函數的返回值是內部函數的引用,這種表達方式就是閉包。內部函數可以調用外部函數的變數,我們看一個示例:

# 外部函數
def sum_closure(x):
    # 內部函數
    def sum_inner(y):
        return x+y
    return sum_inner # 返回內部函數
# 獲取了內部函數
var1 = sum_closure(1)
print(var1) # 輸出<function sum_closure.<locals>.sum_inner at 0x000001D82900E0D0>,是個函數類型
print(var1(2)) # 輸出3

​ 說完閉包的用法,接著了解一下裝飾器。不知道大家了解過AOP沒,即面向切面編程。說人話就是在目標函數前後加上一些公共函數,比如記錄日誌、許可權判斷等。Python中當然也提供了實現切面編程的方法,那就是裝飾器。裝飾器和閉包一起,可以很靈活的實現類似功能,下面看示例:

import datetime #如果沒有這個包,在終端里輸入pip3 install datetime
# 外部函數,其參數是目標函數
def log(func):
    #內部函數,參數得和目標函數一致。也可以使用不定長參數,進一步提升程式靈活性
    def do(x, y):
        # 假裝記錄日誌,執行切面函數。(第一次datetime是模組、第二個是類、now是方法。在下一節講到模組)
        print("時間:{}".format(datetime.datetime.now()))
        print("記錄日誌")
        # 執行目標函數
        func(x, y)
    return do

# @就是裝飾器的語法糖,log外部函數
@ log
def something(x, y):
    print(x+y)
    
# 調用目標函數
# 輸出:
# 時間:2021-01-06 16:17:00.677198
# 記錄日誌
# 30
something(10, 20)

​ 函數相關的就說到這裡了,其實還有一些知識沒說到,比如變數的作用域、返回值等。這部分內容和其他語言幾乎無異,一點區別無非就是返回值不用在乎類型了,畢竟定義函數時也沒指定函數返回值類型,這一點各位老司機應該也會想到。

包和模組

​ Python中包與普通文件夾的區別就是,包內要創建一個__init__.py文件,來標識它是一個包。這個文件可以是空白的,也可以定義一些初始化操作。當其他包下的模組調用本包下的模組時,會自動的執行__init__.py文件的內容

模組

​ 一個Python文件就是一個模組,不同包下的模組可以重名,在使用的時候以「包名.模組名」區別。導入其他模組用import關鍵字,前面的示例程式碼中也演示過一次。導入多個模組可以用逗號隔開,也可以直接分開寫。除了導入整個模組,還可以導入模組中指定的函數或類:

from model_name import func_name(or class_name)

​ 導入函數或類後,就不要使用模組名了,直接調用導入的類或函數即可

面向對象

類和對象

​ Python是一種面向對象的解釋型程式語言。面向對象的關鍵就在於類和對象。Python中類的定義用class關鍵字,如下:

class 類名:
	def 方法名(self[,參數列表])
	...

​ 定義在類裡面的函數叫做方法,只是與類外部的函數做個區分,不用在意叫法。類裡面的方法,參數列表中會有一個默認的參數,表示當前對象,你可以當作Java中的this。因為一個類可以創建多個對象,有了self,Python就知道自己在操作哪個對象了。我們在調用這個方法時,不需要手動傳入self。示例程式碼:

class Demo:
    def do(self):
        print(self)
# 創建兩個Demmo類型的對象
demo1=Demo()
demo1.do() # 輸出<__main__.Demo object at 0x0000019C78106FA0>
demo2=Demo() 
demo2.do() # 輸出<__main__.Demo object at 0x0000019C77FE8640>
print(type(demo1)) # <class '__main__.Demo'>

構造方法

​ 構造方法的作用是在創建一個類的對象時,對對象進行初始化操作。Python中類的構造方法的名稱是__init__(兩邊分別兩個下劃線)。在創建對象時,__init__方法自動執行。和普通方法一樣的,如果你想自定義構造方法,也要接收self參數。示例程式碼:

class Demo:
    # 構造方法,還可以傳入其他參數化
    def __init__(self,var1,var2):
        # 把參數設置到當前對象上,即使類中沒有屬性也可以設置
        self.var1=var1
        self.var2=var2
        print("初始化完成")
    def do(self):
        print("Working...")
# 通過構造方法傳入實參
demo1=Demo(66,77)
demo1.do()
# 通過當前對象,獲取剛剛設置的參數
print(demo1.var1)
print(demo1.var2)

訪問許可權

​ Java或C#中有好幾種訪問許可權,在Python中,屬性和方法前添加兩個下劃線即為私有,反之就是共公有。具有私有訪問許可權的屬性和方法,只能在類的內部方法,外部無法訪問。和其他語言一樣,私有的目的是為了保證屬性的準確性和安全性,示例程式碼如下:

class Demo:
    # 為了方便理解,我們顯示的設置一個私有屬性
    __num = 10
    # 公有的操作方法,裡面加上判斷,保證數據的準確性
    def do(self, temp):
        if temp > 10:
            self.__set(temp)
	# 私有的設置方法,不讓外部直接設置屬性
    def __set(self, temp):
        self.__num = temp
	# 公有的get方法
    def get(self):
        print(self.__num)

demo1 = Demo()
demo1.do(11)
demo1.get() # 輸出 11

​ 一堆self.剛開始看時還有點暈乎,把它當作this就好。

繼承

​ 繼承是面向對象編程里另一大利器,好處之一就是程式碼重用。子類只能繼承父類的公有屬性和方法,Python的語法如下:

class SonClass(FatherClass):

​ 當我們創建一個SonClass對象時,直接可以用該對象調用FatherClass的公有方法。Python還支援多繼承,如果是多繼承就在小括弧里把父類用逗號隔開。

​ 如果想在子類裡面調用父類的方法,一般有兩種方式:1.父類名.方法名(self[,參數列表])。此時的self是子類的self,且需要顯示傳入;2.super().方法名()。第二種方式因為沒有指定父類,所以在多繼承的情況下,如果調用了這些父類中同名的方法,Python實際會執行小括弧里寫在前面的父類中的方法。

​ 如果子類定義了與父類同名的方法,子類的方法就會覆蓋父類的方法,這就是重寫

異常處理

捕獲異常

​ 捕獲異常的語法如下:

try: 
    程式碼快 # 可能發生異常的程式碼
except (異常類型,...) as err: # 多個異常類型用逗號隔開,如果只有一個異常類型可以不要小括弧。err是取的別名
    異常處理 
finally:
    程式碼快 # 無論如何都會執行

​ 在try程式碼塊中,錯誤程式碼之後的程式碼是不會執行的,但不會影響到try … except之外的程式碼。看個示例程式碼:

try:
    open("123.txt") #打開不存在的文件,發生異常
    print("hi") # 這行程式碼不會執行
except FileNotFoundError as err:
    print("發生異常:{}".format(err)) # 異常處理

print("我是try except之外的程式碼") #正常執行

​ 雖然上面的內容和其他語言相差不大,但是剛剛接觸Python鬼知道有哪些異常類型,有沒有類似Java的Exception異常類型呢?肯定是有的。Python同樣提供了Exception異常類型來捕獲全部異常。

​ 那如果發生異常的程式碼沒有用try except捕獲呢?這種情況要麼直接報錯,程式停止運行。要麼會被外部的try except捕獲到,也就是說異常是可以傳遞的。比如func1發生異常沒有捕獲,func2調用了func1並用了try except,那麼func1的異常會被傳遞到func2這裡。是不是和Java的throws差不多?

拋出異常

​ Python中拋出異常的關鍵字是raise,其作用和Java的throw new差不多。示例程式碼如下:

def do(x):
    if(x>3): # 如果大於3就拋出異常
        raise Exception("不能大於3") # 拋出異常,如果你知道具體的異常最好,後面的小括弧可以寫上異常資訊
    else:
        print(x)

try:
    do(4)
except Exception as err:
    print("發生異常:{}".format(err)) # 輸出 發生異常:不能大於3

文件操作

讀寫文件

​ 想要操作一個文件,首先得打開它。Python中有個內置的函數:open。使用open打開文件可以有三種模式,分別為:只讀(默認的模式,只能讀取文件內容,r表示)、只寫(會覆蓋原文本內容,w表示)、追加(新內容追加到末尾,a表示)。示例如下:

f = open("text.txt","a") # 用追加的方式獲取文件對象

​ 因為text.txt和程式碼在同一目錄所以只寫了文件名,如果不在同一目錄需要寫好相對路徑或絕對路徑。

​ 獲取到文件對象後,接下來就可以操作了,反正就是些API,直接看示例:

f = open("text.txt","a",encoding="utf-8") # 以追加的方式打開文件,並設置編碼方式,因為接下來要寫入中文
f.write("234567\n") # 寫入數據,最後的\n是換行符,實現換行
f.writelines(["張三\n","趙四\n","王五\n"]) # write只能寫一個字元串,writelines可以寫入一列表的字元串
f.close() # 操作完記得關閉

​ 以上是寫文件的兩個方法。最後記得關閉文件,因為作業系統會把寫入的內容快取起來,萬一系統崩潰,寫入的數據就會丟失。雖然程式執行完文件會自動關閉,但是實際項目中,肯定不止這點程式碼。Python也很貼心,防止我們忘了close,提供了一種安全打開文件的方式,語法是 with open() as 別名:,示例如下

with open("test.txt","w") as f: # 安全打開文件,不需要close。
    f.write("123")

​ 寫完了,該讀一讀了。示例如下:

f = open("text.txt","r",encoding="utf-8")
data = f.read() # read會一次性讀出所有內容
print(data)
f.close()

​ 除了一次性讀取完,還可以按行的方式返回全部內容,並用一個列表裝起來,這樣我們就可以進行遍歷了。方法是readlines,示例如下:

f = open("text.txt","r",encoding="utf-8")
lines = f.readlines() # lines是個列表
for line in lines:
    print(line)
f.close()

文件管理

​ 在操作文件的時候,肯定不止讀寫這麼簡單,可能還會涉及文件的刪除、重命名、創建等等。在用Python的函數操作文件之前,需要導入os模式:import os 。下面簡單的演示一下重命名的函數,其他的函數我們以表格的形式展現。

import os
os.rename("text.txt","123.txt") # 把text.txt改名為123.txt
函數 作用
os.remove(path) 刪除指定文件
os.mkdir(path) 在指定路徑下創建新文件
os.getcwd() 獲取程式運行的絕對路徑
os.listdir(path) 獲取指定路徑下的文件列表,包含文件和文件夾
os.redir(path) 刪除指定路徑下的空文件夾(如果不是空文件夾就會報錯)

操作JSON

​ 學了前面的容器,會發現JSON的格式和Python的字典有點像,都是鍵值對形式的。雖然格式很像,但還是有點小區別,比如:Python的元組和列表在JSON中都是列表、Python的True和Flase會被轉換成小寫、空類型None會被轉換成null。下面我們來看一些具體的函數把。

​ 在Python中操作JSON格式的數據需要導入json模組。同樣的,我這裡只演示一個函數,其他常用的用表格列出來。

import json
user_info={"name":"張三","age":18,"gender":"男","hobby":("唱歌","跳舞","打籃球"),"other":None} # 創建一個字典
json_str=json.dumps(user_info,ensure_ascii=False) # dumps函數會把字典轉換為json字元串
# 輸出 {"name": "張三", "age": 18, "gender": "男", "hobby": ["唱歌", "跳舞", "打籃球"], "other": null}
print(json_str)

​ 需要注意如果數據存在中文,需要在dumps函數加上ensure_ascii=False

函數 作用
json.loads(json_str) 把json字元串轉換為Python數據結構
json.dump(user_info,file) 把Python數據寫入到json文件,要先獲取文件,那個file就是文件對象
json.load(file) 把json文件中的數據轉為成Python數據結構,同樣需要獲取文件

​ 關於JSON的操作就說這些。通用的數據格式不止JSON一種,比如還有xml、csv等。為了節約篇幅,就不再贅述了,大家可以根據自己的需求查對應的API即可。

正則表達式

​ 最後一節講正則表達式,一是因為這也算個基礎知識,在很多地方都有可能用到。二是因為後面的爬蟲實戰,肯定會用到正則表達式來解析各種數據。

​ Python中內置了re模組來處理正常表達式,有了這個模組我們就可以很方便的對字元串進行各種規則匹配檢查。不過正則表達式真正難的是表達式的書寫,函數主要就一個:re.match(pattern,string),其中pattren就是正則表達式,stirng就是待匹配字元串。如果匹配成功就會返回一個Match對象,否則就返回None。匹配是從左往右,如果不匹配就直接返回None,不會接著匹配下去。示例如下:

import re
res=re.match("asd","asdabcqwe") # 匹配字元串中是否有asd(如果asd不在開頭就會返回None)
print(res) # 輸出 <re.Match object; span=(0, 3), match='asd'>
print(res.group()) # 輸出 asd 如果想獲取匹配的子字元就用這個函數

​ 秉著幫人幫到底的精神,下面就簡單的介紹下正則表達式的一些規則。

單字元匹配

​ 單字元匹配,顧名思義就是匹配一個字元。除了直接使用某個具體的字元,還可以使用以下符號來進行匹配:

符號 作用
. 匹配除」\n「以外的任意單個字元
\d 匹配0-9之間的一個數字,等價於[0-9]
\D 匹配一個非數字字元,等價於[^0-9]
\s 匹配任意空白字元,如空格、\t、\n等
\S 匹配任意非空白字元
\w 匹配單詞字元,包括字母、數字、下劃線
\W 匹配非單詞字元
[] 匹配[]中列舉的字元,比如[abc],只要出現這三個字母中的一個即可匹配

​ 以防有的朋友從未接觸過正則表達式,不知道怎麼用,下面我來做個簡答的演示。假如我想匹配三個字元:第一個是數字、第二個是空格、第三個是字母,一起來看看怎麼寫這個正則表達式吧:

import re
pattern = "\d\s\w" # \d匹配數字、\s匹配空格、\w匹配字母(切記是從左往右依次匹配的,只要有一個字元匹配不上就直接返回None)
string = "2 z你好"
res=re.match(pattern,string)
print(res.group()) # 輸出:2 z

​ 看到這你可能會想,非得一個個字元匹配,那多麻煩啊,有沒有更靈活的規則?當然有了,接著看。

數量表示

​ 如果我們只想匹配字母,但不限制有多少個,該怎麼寫呢?看下面的表格就知道了:

符號 作用
* 匹配一個字元出現0次或多次
+ 匹配一個字元至少出現一次,等價於{,1}
? 匹配一個字元出現0次或1次,等價於{1,2}
{m} 匹配一個字元出現m次
{m,} 匹配一個字元至少出現m次
{m,n} 匹配一個字元出現m到n次

​ 數量匹配的符號後面如果加上?就會儘可能少的去匹配字元,在Python裡面叫非貪婪模式,反之默認的就是貪婪模式。比如{m,}會儘可能多的去匹配字元,而{m,}?在滿足至少有m個的情況下儘可能少的去匹配字元。其他的同理。

​ 來看一個例子,我想匹配開頭是任意個小寫字母,接著是1到5個2-6的數字,最後是至少一個空格:

import re
pat = r"[a-z]*[2-6]{1,5}\s+"
str = "abc423  你好"
res=re.match(pat,str) 
print(res) #輸出 abc423  

​ 我們來解析下這個正則表達式,pat字元串開頭的r是告訴Python這是個正則表達式,不要轉義裡面的\,建議寫表達式時都加上。[a-z]代表任意小寫字母,不用\w的原因是,\w還包括數字、下劃線,沒有嚴格符合我們的要求。加上個*就代表任意數量。這裡強調一下單字元匹配和數量表示之間的邏輯關係,以[a-z]*為例,其表達的是任意個[a-z],而不是某個字母有任意個。明白了這個邏輯後,其他的也好理解了。

​ 前面的例子都是我隨意編的,其實學了這些,已經可以寫出一個有實際作用的表達式了,比如我們來匹配一個手機號。首先手機號只有11位,第一個數字必須是1,第二個是3、5、7、8中的一個。知道了這三個個規律,我們來寫一下表達式:1[3578]\d{9}。看上去好像可以,但是仔細一想,前面不是說了正則表達式是從左往右匹配,只要符合了就會返回結果,也不會管字元串匹配完全沒有。如果最後有10個數字,這個表達式也會匹配成功。關於這個問題我們接著看。

邊界表示

​ 邊界表示符有兩個:開頭^和結尾$。使用起來也很簡單,還是以上面的手機號為例,我們再來完善一下:^1[3578]\d{9}$。其中^1表示以1開頭,\d{9}$表示以9個數字結尾。其實這個^1可有可無,畢竟是從左往右的,字元串不是1開頭的話直接就會返回None,但是這個結尾符是必須的。

轉義字元

​ 假如我們想匹配的字元與正則表達式規定的這些字元一樣該怎麼辦?比如我們想單純的匹配.這個字元,但是這個字元在正則表達式中表示的是任意字元。這時候就要用到轉義字元\了。其實這個轉義字元在很多語言里都是一樣的。那麼前面的例子就可以寫出\.。我們再演示個匹配郵箱的例子:

import re
pat = r"^\w{4,10}@qq\.com" # 如果.前面不加\,就代表任意字元了
str = "[email protected]"
res=re.match(pat,str)
print(res)

匹配分組

​ 看到上面的匹配郵箱例子,是不是有個疑問,如果我想不止匹配QQ郵箱該怎麼辦呢。那就要用到分組了,其可以實現匹配多種情況。分組符號如下:

符號 作用
() 將括弧里的內容當作一個分組,每個分組會有一個編號,從1開始
| 連接多個表達式,表達式之間是「或」的關係,可與()一起使用
\num 引用分組,num代表分組編號
(?P…) 給分組取別名,別名寫在表達式前面,name不用打引號
(?P=name) 根據別名使用分組中的正則表達式

​ 那麼我們把上面的例子稍微修改下:^\w{4,10}@(qq|163|outlook|gmail)\.com。這樣就可以匹配多種郵箱了。

​ 簡單的演示了下|的用法,大家可能對其他的分組符號還有點疑惑,下面我們再來演示一下這些符號:

import re
pat = r"<(.+)><(.+)>.*<(/\2)><(/\1)>" 
str = "<body><div></div></body>"
res=re.match(pat,str)
print(res)

​ 這個表達式匹配的是由兩個標籤組成的html字元串。第一眼看上去有點麻煩,實際很簡單。再次強調一下,普通字元也可以當表達式來匹配的,比如上面的< >就是普通字元而已。

​ 我們來分析一下這個表達式,首先一對小括弧表示一個分組,裡面的.+表示只有一個非\n字元。中間的.*用來匹配標籤內的內容。/\2中,第一個斜杠與前面的html標籤組成一對,/2表示引用第二個分組的內容。這裡為什麼要使用分組呢?因為我們還要保證html標籤正確匹配。如果後面也使用.+,大家可以試著把/div/body交換位置,表達式依舊匹配成功,但這顯然不符合html的語法。

操作函數

​ 正則表達式的一些規則符號終於講完了,最後再列舉幾個Python中操作正則表達式的函數:(re為導入的模組)

函數 作用
re.compile(patt) 封裝正則表達式,並返回一個表達式對象
re.search(patt,str) 從左往右搜索第一個配正則表達式匹配的子字元串
re.findall(patt,str) 在字元串中查找正則表達式匹配到的所有子字元串,並返回一個列表
re.finditer(patt,str) 在字元串中查找正則表達式匹配到的所有子字元串,並返回一個Iterator對象
re.sub(patt,newstr,str) 將字元串中被正則表達式匹配到的子字元串替換成newstr,並返回新的字元串,原字元串不變

​ Python的第一篇文章就到這裡了。接下來會邊學邊寫,做一些好玩的Python項目,再一起分享出來。如有錯誤,感謝指出!

參考資料:《Python 3快速入門與實戰》

Tags: